1use crate::circuit::{
28 Circuit, ClassicalControlOp, Instruction, Operation, Parameter, ParameterValue, Qubit,
29 StandardGate, ValueClassicalControlOp, ValueControlBody, ValueInstruction, ValueOperation,
30 ValueSwitchCase,
31};
32use crate::compile::CompilerError;
33use crate::compile::knowledge::rule::{Rule, RuleItem};
34use crate::compile::knowledge::{
35 KnowledgeInstructionKey, MatchedReplacement, RuleId, RuleKind, RuleLibrary,
36};
37use crate::compile::transform::analysis::WorkflowCircuitAnalysis;
38use crate::compile::transform::decompose::unitary::euler_1q::{
39 Euler1qCandidate, synthesize_euler_1q_candidates,
40};
41use crate::compile::transform::decompose::unitary::synthesize_numeric_1q_unitary;
42use crate::compile::transform::lowering_support::{
43 LowerableOperation, LoweringTarget, OperationSequenceLowerer,
44};
45use crate::compile::transform::rebuild::{CircuitRebuildContext, ClassicalRemap};
46use crate::compile::transform::transformer::{PassApplicability, WorkflowPass};
47use crate::compile::transform::{
48 Canonicalizer, CircuitAnalysis, OptimizeOneQubitRuns, TransformOutcome, Transformer,
49};
50use smallvec::{SmallVec, smallvec};
51use std::collections::{HashMap, HashSet};
52use std::sync::Arc;
53
54const MAX_PLANNING_ROUNDS: usize = 128;
55
56#[derive(Debug, Clone)]
58pub struct TargetBasisLowerer {
59 target_basis: Arc<[Instruction]>,
60 plans: Arc<LoweringPlans>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Hash)]
69pub struct TargetBasisSignature {
70 gates: Vec<u8>,
71}
72
73#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
78pub struct TargetBasisCost {
79 pub two_qubit_ops: usize,
80 pub depth: usize,
81 pub total_ops: usize,
82 pub parameterized_ops: usize,
83}
84
85#[derive(Debug, Clone)]
92pub struct TargetBasisCostModel {
93 signature: TargetBasisSignature,
94 lowerer: Arc<TargetBasisLowerer>,
95}
96
97impl PartialEq for TargetBasisCostModel {
98 fn eq(&self, other: &Self) -> bool {
99 self.signature == other.signature
100 }
101}
102
103impl Eq for TargetBasisCostModel {}
104
105#[derive(Debug, Clone)]
106struct LoweringPlans {
107 physical_keys: HashSet<KnowledgeInstructionKey>,
108 plan_by_key: HashMap<KnowledgeInstructionKey, GatePlan>,
109 target_display: String,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113struct GatePlan {
114 rule_id: RuleId,
115 cost: PlanCost,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
119struct PlanCost {
120 rank: usize,
121 total_ops: usize,
122 two_qubit_ops: usize,
123 parameterized_ops: usize,
124 rule_id: usize,
125}
126
127struct CircuitLowerer<'a> {
128 source: &'a Circuit,
129 plans: &'a LoweringPlans,
130 library: &'a RuleLibrary,
131 rebuild: CircuitRebuildContext,
132 changed: bool,
133}
134
135impl TargetBasisLowerer {
136 pub fn new(target_basis: Vec<Instruction>) -> Result<Self, CompilerError> {
138 Self::from_shared_basis(target_basis.into())
139 }
140
141 pub(crate) fn from_shared_basis(
143 target_basis: Arc<[Instruction]>,
144 ) -> Result<Self, CompilerError> {
145 if target_basis.is_empty() {
146 return Err(CompilerError::InvalidInput(
147 "target-basis lowering requires a non-empty target basis".to_string(),
148 ));
149 }
150
151 let library = RuleLibrary::builtin_rules()
152 .map_err(|err| CompilerError::InvariantViolation(err.to_string()))?;
153 let plans = LoweringPlans::build(&target_basis, library)?;
154
155 Ok(Self {
156 target_basis,
157 plans: Arc::new(plans),
158 })
159 }
160
161 pub fn target_basis(&self) -> &[Instruction] {
163 self.target_basis.as_ref()
164 }
165
166 pub fn requires_lowering(&self, circuit: &Circuit) -> bool {
174 operations_require_lowering(circuit.operations(), self.plans.as_ref())
175 }
176}
177
178impl WorkflowPass for TargetBasisLowerer {
179 fn applicability(&self, analysis: &WorkflowCircuitAnalysis) -> PassApplicability {
180 let requires_lowering = analysis.has_extended_gate_like_operations()
181 || analysis.standard_gates().any(|gate| {
182 gate == StandardGate::GPhase
183 || !self
184 .plans
185 .is_physical(&KnowledgeInstructionKey::Standard(gate))
186 });
187 if requires_lowering {
188 PassApplicability::Run
189 } else {
190 PassApplicability::ProvenNoOp("circuit already satisfies the explicit target basis")
191 }
192 }
193}
194
195fn operations_require_lowering(operations: &[Operation], plans: &LoweringPlans) -> bool {
196 operations
197 .iter()
198 .any(|operation| instruction_requires_lowering(&operation.instruction, plans))
199}
200
201fn instruction_requires_lowering(instruction: &Instruction, plans: &LoweringPlans) -> bool {
202 match instruction {
203 Instruction::Standard(StandardGate::GPhase) => true,
204 Instruction::Standard(gate) => {
205 !plans.is_physical(&KnowledgeInstructionKey::Standard(*gate))
206 }
207 Instruction::McGate(_) | Instruction::UnitaryGate(_) | Instruction::CircuitGate(_) => true,
212 Instruction::ClassicalControl(control) => control_flow_requires_lowering(control, plans),
213 Instruction::ClassicalData(_) | Instruction::Directive(_) | Instruction::Delay => false,
214 }
215}
216
217fn control_flow_requires_lowering(control: &ClassicalControlOp, plans: &LoweringPlans) -> bool {
218 control
219 .bodies()
220 .any(|body| operations_require_lowering(body.operations(), plans))
221}
222
223impl TargetBasisSignature {
224 pub fn from_standard_gates(gates: &[StandardGate]) -> Self {
226 let mut gates = gates
227 .iter()
228 .filter(|gate| **gate != StandardGate::GPhase)
229 .map(|gate| *gate as u8)
230 .collect::<Vec<_>>();
231 gates.sort_unstable();
232 gates.dedup();
233 Self { gates }
234 }
235}
236
237impl TargetBasisCostModel {
238 pub fn new(target_basis: Vec<Instruction>) -> Result<Self, CompilerError> {
240 let lowerer = Arc::new(TargetBasisLowerer::new(target_basis)?);
241 Self::from_lowerer(lowerer)
242 }
243
244 pub fn from_lowerer(lowerer: Arc<TargetBasisLowerer>) -> Result<Self, CompilerError> {
246 let gates = lowerer
247 .target_basis()
248 .iter()
249 .map(|instruction| match instruction {
250 Instruction::Standard(gate) => Ok(*gate),
251 _ => Err(CompilerError::InvalidInput(format!(
252 "target-basis cost model requires standard instructions, got {instruction:?}"
253 ))),
254 })
255 .collect::<Result<Vec<_>, _>>()?;
256 Ok(Self {
257 signature: TargetBasisSignature::from_standard_gates(&gates),
258 lowerer,
259 })
260 }
261
262 pub const fn signature(&self) -> &TargetBasisSignature {
264 &self.signature
265 }
266
267 pub fn target_basis(&self) -> &[Instruction] {
269 self.lowerer.target_basis()
270 }
271
272 pub fn cost_of_fixed_operations(
279 &self,
280 qubits: Vec<Qubit>,
281 operations: Vec<ValueOperation>,
282 ) -> Result<TargetBasisCost, CompilerError> {
283 Self::count_lowered(&self.lower_fixed_operations(qubits, operations)?)
284 }
285
286 pub(crate) fn cost_after_local_cleanup(
289 &self,
290 qubits: Vec<Qubit>,
291 operations: Vec<ValueOperation>,
292 ) -> Result<TargetBasisCost, CompilerError> {
293 let lowered = self.lower_fixed_operations(qubits, operations)?;
294 Self::count_lowered(&self.cleanup_lowered(lowered)?)
295 }
296
297 fn lower_fixed_operations(
298 &self,
299 qubits: Vec<Qubit>,
300 operations: Vec<ValueOperation>,
301 ) -> Result<Circuit, CompilerError> {
302 let source = Circuit::from_operations(qubits, operations, None, None)
303 .map_err(CompilerError::Circuit)?;
304 let lowered = match self.lowerer.transform(&source, None)? {
305 TransformOutcome::Unchanged => source,
306 TransformOutcome::Changed(lowered) => lowered,
307 };
308 Ok(lowered)
309 }
310
311 fn cleanup_lowered(&self, mut circuit: Circuit) -> Result<Circuit, CompilerError> {
312 let canonicalizer = Canonicalizer::production();
313 let optimizer = OptimizeOneQubitRuns::basis_with_cost_model(Arc::new(self.clone()));
314 for pass in [&canonicalizer as &dyn Transformer, &optimizer] {
315 if let TransformOutcome::Changed(next) = pass.transform(&circuit, None)? {
316 circuit = next;
317 }
318 }
319 if self.lowerer.requires_lowering(&circuit)
320 && let TransformOutcome::Changed(next) = self.lowerer.transform(&circuit, None)?
321 {
322 circuit = next;
323 }
324 if let TransformOutcome::Changed(next) = canonicalizer.transform(&circuit, None)? {
325 circuit = next;
326 }
327 Ok(circuit)
328 }
329
330 fn count_lowered(lowered: &Circuit) -> Result<TargetBasisCost, CompilerError> {
331 let mut cost = TargetBasisCost::default();
332 let mut depths = HashMap::new();
333 for operation in lowered.operations() {
334 let Instruction::Standard(gate) = operation.instruction else {
335 return Err(CompilerError::InvariantViolation(
336 "target-basis lowering emitted a non-standard operation while estimating cost"
337 .to_string(),
338 ));
339 };
340 if gate == StandardGate::GPhase {
341 continue;
342 }
343 cost.total_ops += 1;
344 if !operation.params.is_empty() {
345 cost.parameterized_ops += 1;
346 }
347 if operation.qubits.len() == 2 {
348 cost.two_qubit_ops += 1;
349 }
350 if operation.qubits.is_empty() {
351 continue;
352 }
353 let next_depth = operation
354 .qubits
355 .iter()
356 .filter_map(|qubit| depths.get(qubit))
357 .max()
358 .copied()
359 .unwrap_or(0)
360 + 1;
361 for qubit in &operation.qubits {
362 depths.insert(*qubit, next_depth);
363 }
364 cost.depth = cost.depth.max(next_depth);
365 }
366 Ok(cost)
367 }
368}
369
370impl Transformer for TargetBasisLowerer {
371 fn name(&self) -> &'static str {
372 "target_basis_lowering"
373 }
374
375 fn transform(
376 &self,
377 circuit: &Circuit,
378 _analysis: Option<&CircuitAnalysis>,
379 ) -> Result<TransformOutcome, CompilerError> {
380 let library = RuleLibrary::builtin_rules()
381 .map_err(|err| CompilerError::InvariantViolation(err.to_string()))?;
382 CircuitLowerer::run(circuit, self.plans.as_ref(), library)
383 }
384}
385
386impl LoweringPlans {
387 fn build(target_basis: &[Instruction], library: &RuleLibrary) -> Result<Self, CompilerError> {
388 let mut physical_keys = HashSet::with_capacity(target_basis.len());
389 for instruction in target_basis {
390 let Some(key) = KnowledgeInstructionKey::from_instruction(instruction) else {
391 return Err(CompilerError::InvalidInput(format!(
392 "unsupported target-basis instruction {instruction:?}"
393 )));
394 };
395 physical_keys.insert(key);
396 }
397
398 let target_display = target_basis
399 .iter()
400 .map(Instruction::to_string)
401 .collect::<Vec<_>>()
402 .join(", ");
403 let mut cost_by_key = physical_keys
404 .iter()
405 .cloned()
406 .map(|key| {
407 let cost = if key.is_implicit() {
408 PlanCost::zero(key_rule_sort_value(&key))
409 } else {
410 PlanCost {
411 rank: 0,
412 total_ops: 1,
413 two_qubit_ops: key.num_qubits().is_some_and(|num| num == 2) as usize,
414 parameterized_ops: key.num_params().is_some_and(|num_params| num_params > 0)
415 as usize,
416 rule_id: key_rule_sort_value(&key),
417 }
418 };
419 (key, cost)
420 })
421 .collect::<HashMap<_, _>>();
422 cost_by_key.insert(
423 KnowledgeInstructionKey::Standard(StandardGate::GPhase),
424 PlanCost::zero(usize::MAX),
425 );
426
427 let candidates = collect_candidate_rules(library);
428 let mut plan_by_key = HashMap::new();
429
430 for _ in 0..MAX_PLANNING_ROUNDS {
431 let mut changed = false;
432 for &rule_id in &candidates {
433 let rule = library.get(rule_id).ok_or_else(|| {
434 CompilerError::InvariantViolation(format!(
435 "missing target-basis lowering rule id {}",
436 rule_id.as_usize()
437 ))
438 })?;
439 let source_item = &rule.operations[0];
440 let source_key =
441 KnowledgeInstructionKey::from_instruction(&source_item.instruction)
442 .ok_or_else(|| {
443 CompilerError::InvariantViolation(format!(
444 "unsupported source instruction in target-basis rule {:?}",
445 source_item.instruction
446 ))
447 })?;
448 if physical_keys.contains(&source_key) || source_key.is_implicit() {
449 continue;
450 }
451
452 let Some(candidate_cost) =
453 rewrite_cost(&rule.target, &cost_by_key, rule_id.as_usize())
454 else {
455 continue;
456 };
457 let current = cost_by_key.get(&source_key).copied();
458 if current.is_none_or(|current| candidate_cost < current) {
459 cost_by_key.insert(source_key.clone(), candidate_cost);
460 plan_by_key.insert(
461 source_key,
462 GatePlan {
463 rule_id,
464 cost: candidate_cost,
465 },
466 );
467 changed = true;
468 }
469 }
470
471 if !changed {
472 return Ok(Self {
473 physical_keys,
474 plan_by_key,
475 target_display,
476 });
477 }
478 }
479
480 Err(CompilerError::InvariantViolation(
481 "target-basis lowering planning did not converge".to_string(),
482 ))
483 }
484
485 fn is_physical(&self, key: &KnowledgeInstructionKey) -> bool {
486 self.physical_keys.contains(key)
487 }
488
489 fn plan_for(&self, key: &KnowledgeInstructionKey) -> Option<GatePlan> {
490 self.plan_by_key.get(key).copied()
491 }
492}
493
494impl PlanCost {
495 const fn zero(rule_id: usize) -> Self {
496 Self {
497 rank: 0,
498 total_ops: 0,
499 two_qubit_ops: 0,
500 parameterized_ops: 0,
501 rule_id,
502 }
503 }
504}
505
506fn compare_dynamic_candidates(
509 left: &Euler1qCandidate,
510 right: &Euler1qCandidate,
511) -> std::cmp::Ordering {
512 let (left_total, left_parameterized) = left.physical_cost();
513 let (right_total, right_parameterized) = right.physical_cost();
514 (left_total, left_parameterized, left.family).cmp(&(
515 right_total,
516 right_parameterized,
517 right.family,
518 ))
519}
520
521fn dynamic_strictly_beats_static(candidate: &Euler1qCandidate, static_cost: &PlanCost) -> bool {
526 let (total_ops, parameterized_ops) = candidate.physical_cost();
527 (
528 0usize, total_ops,
530 parameterized_ops,
531 ) < (
532 static_cost.two_qubit_ops,
533 static_cost.total_ops,
534 static_cost.parameterized_ops,
535 )
536}
537
538impl<'a> CircuitLowerer<'a> {
539 fn run(
540 source: &'a Circuit,
541 plans: &'a LoweringPlans,
542 library: &'a RuleLibrary,
543 ) -> Result<TransformOutcome, CompilerError> {
544 let rebuild = CircuitRebuildContext::new(source);
545 let root_classical = rebuild.root_classical().clone();
546 let mut lowerer = Self {
547 source,
548 plans,
549 library,
550 rebuild,
551 changed: false,
552 };
553 let mut phase_delta = Parameter::from(0.0);
554 let mut operations = Vec::with_capacity(source.operations().len());
555 lowerer.lower_sequence(
556 source.operations(),
557 &root_classical,
558 LoweringTarget::top_level(&mut operations, &mut phase_delta),
559 )?;
560
561 let global_phase = &source.global_phase() + &phase_delta;
562 let circuit = lowerer
563 .rebuild
564 .finish(source.qubits(), operations, global_phase)?;
565
566 Ok(if lowerer.changed {
567 TransformOutcome::Changed(circuit)
568 } else {
569 TransformOutcome::Unchanged
570 })
571 }
572
573 fn lower_operation(
574 &mut self,
575 operation: &Operation,
576 classical_remap: &ClassicalRemap,
577 target: &mut LoweringTarget<'_>,
578 ) -> Result<(), CompilerError> {
579 match &operation.instruction {
580 Instruction::Standard(_) | Instruction::McGate(_) => {
581 let params =
582 CircuitRebuildContext::resolve_source_params(self.source, &operation.params)?;
583 let op = LowerableOperation {
584 instruction: operation.instruction.clone(),
585 qubits: operation.qubits.clone(),
586 params,
587 label: operation.label.clone(),
588 };
589 self.lower_gate_like(op, target)
590 }
591 Instruction::ClassicalControl(control) => {
592 let instruction = self.lower_control_flow(control, classical_remap)?;
593 let qubits = instruction.used_qubits().into_iter().collect();
594 target.push(ValueOperation {
595 instruction: ValueInstruction::ClassicalControl(instruction),
596 qubits,
597 params: SmallVec::new(),
598 label: operation.label.clone(),
599 });
600 Ok(())
601 }
602 Instruction::UnitaryGate(_) | Instruction::CircuitGate(_) => {
603 Err(CompilerError::InvalidInput(format!(
604 "cannot lower {} to target basis {{{}}}: definitions and unitaries must be decomposed before target-basis translation",
605 operation.instruction, self.plans.target_display
606 )))
607 }
608 Instruction::ClassicalData(_) | Instruction::Directive(_) | Instruction::Delay => {
609 let operation = self.rebuild.remap_preserved_operation(
610 self.source,
611 operation,
612 classical_remap,
613 )?;
614 target.push(operation);
615 Ok(())
616 }
617 }
618 }
619
620 fn lower_gate_like(
621 &mut self,
622 operation: LowerableOperation,
623 target: &mut LoweringTarget<'_>,
624 ) -> Result<(), CompilerError> {
625 let key =
626 KnowledgeInstructionKey::from_instruction(&operation.instruction).ok_or_else(|| {
627 CompilerError::InvariantViolation(format!(
628 "missing target-basis key for gate-like instruction {}",
629 operation.instruction
630 ))
631 })?;
632 if key.is_implicit() {
633 self.changed = true;
634 target.accumulate_phase(operation.gphase_param()?);
635 return Ok(());
636 }
637 if self.plans.is_physical(&key) {
638 self.emit_physical_gate(operation, target);
639 return Ok(());
640 }
641 if matches!(
642 operation.instruction,
643 Instruction::Standard(StandardGate::U)
644 ) && self.try_lower_fixed_u(&operation, &key, target)?
645 {
646 return Ok(());
647 }
648
649 let Some(plan) = self.plans.plan_for(&key) else {
650 return Err(CompilerError::InvalidInput(format!(
651 "cannot lower {} to target basis {{{}}}: no decomposition plan",
652 operation.instruction, self.plans.target_display
653 )));
654 };
655 let rule = self.library.get(plan.rule_id).ok_or_else(|| {
656 CompilerError::InvariantViolation(format!(
657 "missing target-basis lowering rule id {}",
658 plan.rule_id.as_usize()
659 ))
660 })?;
661 let replacements = instantiate_single_source_rule(rule, &operation)?;
662 self.changed = true;
663 for replacement in replacements {
664 self.lower_replacement(replacement, target)?;
665 }
666 Ok(())
667 }
668
669 fn lower_replacement(
670 &mut self,
671 replacement: MatchedReplacement,
672 target: &mut LoweringTarget<'_>,
673 ) -> Result<(), CompilerError> {
674 self.lower_gate_like(
675 LowerableOperation {
676 instruction: replacement.instruction,
677 qubits: replacement.qubits,
678 params: SmallVec::from_vec(replacement.params.into_vec()),
679 label: None,
680 },
681 target,
682 )
683 }
684
685 fn emit_physical_gate(
686 &mut self,
687 operation: LowerableOperation,
688 target: &mut LoweringTarget<'_>,
689 ) {
690 target.push(ValueOperation {
691 instruction: ValueInstruction::from_instruction(operation.instruction),
692 qubits: operation.qubits,
693 params: operation.params,
694 label: operation.label,
695 });
696 }
697
698 fn try_lower_fixed_u(
703 &mut self,
704 operation: &LowerableOperation,
705 key: &KnowledgeInstructionKey,
706 target: &mut LoweringTarget<'_>,
707 ) -> Result<bool, CompilerError> {
708 let mut angles = [0.0; 3];
709 if operation.params.len() != 3 {
710 return Ok(false);
711 }
712 for (slot, param) in angles.iter_mut().zip(&operation.params) {
713 let ParameterValue::Fixed(value) = param else {
714 return Ok(false);
715 };
716 if !value.is_finite() {
717 return Ok(false);
718 }
719 *slot = *value;
720 }
721 let matrix = StandardGate::U
722 .matrix(&angles)
723 .map_err(CompilerError::Circuit)?;
724 let decomposition = synthesize_numeric_1q_unitary(&matrix)?;
725 let is_available = |gate: StandardGate| {
726 self.plans
727 .physical_keys
728 .contains(&KnowledgeInstructionKey::Standard(gate))
729 };
730 let best = synthesize_euler_1q_candidates(decomposition, &is_available)?
731 .into_iter()
732 .min_by(compare_dynamic_candidates);
733 let Some(best) = best else {
734 return Ok(false);
735 };
736 if let Some(plan) = self.plans.plan_for(key) {
737 debug_assert_eq!(
738 plan.cost.two_qubit_ops, 0,
739 "single-qubit U lowering plan must not contain two-qubit gates"
740 );
741 if !dynamic_strictly_beats_static(&best, &plan.cost) {
742 return Ok(false);
743 }
744 }
745
746 self.changed = true;
747 for gate in &best.gates {
748 debug_assert!(
749 is_available(gate.gate),
750 "euler synthesis emitted {} outside the target basis",
751 gate.gate
752 );
753 let params = match gate.param {
754 Some(value) => smallvec![ParameterValue::Fixed(value)],
755 None => SmallVec::new(),
756 };
757 target.push(ValueOperation {
758 instruction: ValueInstruction::from_instruction(Instruction::Standard(gate.gate)),
759 qubits: operation.qubits.clone(),
760 params,
761 label: None,
764 });
765 }
766 target.accumulate_phase(Parameter::from(best.global_phase));
767 Ok(true)
768 }
769
770 fn lower_control_flow(
771 &mut self,
772 control: &ClassicalControlOp,
773 classical_remap: &ClassicalRemap,
774 ) -> Result<ValueClassicalControlOp, CompilerError> {
775 Ok(match control {
776 ClassicalControlOp::If(op) => {
777 let mut then_body = Vec::with_capacity(op.then_body().operations().len());
778 let mut then_phase = Parameter::from(0.0);
779 self.lower_sequence(
780 op.then_body().operations(),
781 classical_remap,
782 LoweringTarget::control_flow_body(&mut then_body, &mut then_phase),
783 )?;
784 self.prepend_body_phase(&mut then_body, then_phase);
785
786 let else_body = op
787 .else_body()
788 .map(|body| {
789 let mut lowered = Vec::with_capacity(body.operations().len());
790 let mut phase = Parameter::from(0.0);
791 self.lower_sequence(
792 body.operations(),
793 classical_remap,
794 LoweringTarget::control_flow_body(&mut lowered, &mut phase),
795 )?;
796 self.prepend_body_phase(&mut lowered, phase);
797 Ok::<_, CompilerError>(ValueControlBody::new(lowered))
798 })
799 .transpose()?;
800
801 ValueClassicalControlOp::If {
802 condition: classical_remap.remap_expr(op.condition())?,
803 then_body: ValueControlBody::new(then_body),
804 else_body,
805 }
806 }
807 ClassicalControlOp::While(op) => {
808 let mut body = Vec::with_capacity(op.body().operations().len());
809 let mut phase = Parameter::from(0.0);
810 self.lower_sequence(
811 op.body().operations(),
812 classical_remap,
813 LoweringTarget::control_flow_body(&mut body, &mut phase),
814 )?;
815 self.prepend_body_phase(&mut body, phase);
816 ValueClassicalControlOp::While {
817 condition: classical_remap.remap_expr(op.condition())?,
818 body: ValueControlBody::new(body),
819 }
820 }
821 ClassicalControlOp::For(op) => {
822 let mut body = Vec::with_capacity(op.body().operations().len());
823 let mut phase = Parameter::from(0.0);
824 self.lower_sequence(
825 op.body().operations(),
826 classical_remap,
827 LoweringTarget::control_flow_body(&mut body, &mut phase),
828 )?;
829 self.prepend_body_phase(&mut body, phase);
830 ValueClassicalControlOp::For {
831 var: classical_remap.remap_var(op.var())?,
832 start: classical_remap.remap_expr(op.start())?,
833 stop: classical_remap.remap_expr(op.stop())?,
834 step: classical_remap.remap_expr(op.step())?,
835 body: ValueControlBody::new(body),
836 }
837 }
838 ClassicalControlOp::Switch(op) => {
839 let cases = op
840 .cases()
841 .iter()
842 .map(|case| {
843 let mut lowered = Vec::with_capacity(case.body().operations().len());
844 let mut phase = Parameter::from(0.0);
845 self.lower_sequence(
846 case.body().operations(),
847 classical_remap,
848 LoweringTarget::control_flow_body(&mut lowered, &mut phase),
849 )?;
850 self.prepend_body_phase(&mut lowered, phase);
851 Ok::<_, CompilerError>(ValueSwitchCase::new(
852 case.value(),
853 ValueControlBody::new(lowered),
854 ))
855 })
856 .collect::<Result<Vec<_>, _>>()?;
857 let default = op
858 .default()
859 .map(|body| {
860 let mut lowered = Vec::with_capacity(body.operations().len());
861 let mut phase = Parameter::from(0.0);
862 self.lower_sequence(
863 body.operations(),
864 classical_remap,
865 LoweringTarget::control_flow_body(&mut lowered, &mut phase),
866 )?;
867 self.prepend_body_phase(&mut lowered, phase);
868 Ok::<_, CompilerError>(ValueControlBody::new(lowered))
869 })
870 .transpose()?;
871 ValueClassicalControlOp::Switch {
872 target: classical_remap.remap_expr(op.target())?,
873 cases,
874 default,
875 }
876 }
877 ClassicalControlOp::Break => ValueClassicalControlOp::Break,
878 ClassicalControlOp::Continue => ValueClassicalControlOp::Continue,
879 })
880 }
881
882 fn prepend_body_phase(&mut self, body: &mut Vec<ValueOperation>, phase: Parameter) {
883 if phase.is_zero() {
884 return;
885 }
886 self.changed = true;
887 body.insert(
888 0,
889 ValueOperation {
890 instruction: ValueInstruction::from_instruction(Instruction::Standard(
891 StandardGate::GPhase,
892 )),
893 qubits: SmallVec::new(),
894 params: smallvec![ParameterValue::from(phase)],
895 label: None,
896 },
897 );
898 }
899}
900
901impl OperationSequenceLowerer for CircuitLowerer<'_> {
902 fn lower_one_operation(
903 &mut self,
904 operation: &Operation,
905 classical_remap: &ClassicalRemap,
906 target: &mut LoweringTarget<'_>,
907 ) -> Result<(), CompilerError> {
908 self.lower_operation(operation, classical_remap, target)
909 }
910}
911
912fn collect_candidate_rules(library: &RuleLibrary) -> Vec<RuleId> {
913 [RuleKind::Decompose, RuleKind::HardwareNative]
914 .into_iter()
915 .flat_map(|kind| library.rules_by_kind(kind).iter().copied())
916 .filter(|rule_id| {
917 let Some(rule) = library.get(*rule_id) else {
918 return false;
919 };
920 rule.operations.len() == 1
921 && rule
922 .conditions
923 .as_ref()
924 .is_none_or(|conditions| conditions.is_empty())
925 && source_params_are_directly_instantiable(&rule.operations[0])
926 })
927 .collect()
928}
929
930fn rewrite_cost(
931 target: &[crate::compile::knowledge::rule::RuleItem],
932 cost_by_key: &HashMap<KnowledgeInstructionKey, PlanCost>,
933 rule_id: usize,
934) -> Option<PlanCost> {
935 let mut rank = 0usize;
936 let mut total_ops = 0usize;
937 let mut two_qubit_ops = 0usize;
938 let mut parameterized_ops = 0usize;
939
940 for item in target {
941 let key = KnowledgeInstructionKey::from_instruction(&item.instruction)?;
942 if key.is_implicit() {
943 continue;
944 }
945 let cost = cost_by_key.get(&key).copied()?;
946 rank = rank.max(cost.rank);
947 total_ops = total_ops.saturating_add(cost.total_ops);
948 two_qubit_ops = two_qubit_ops.saturating_add(cost.two_qubit_ops);
949 parameterized_ops = parameterized_ops.saturating_add(cost.parameterized_ops);
950 }
951
952 Some(PlanCost {
953 rank: rank.saturating_add(1),
954 total_ops,
955 two_qubit_ops,
956 parameterized_ops,
957 rule_id,
958 })
959}
960
961fn source_params_are_directly_instantiable(source: &RuleItem) -> bool {
962 source
963 .params
964 .as_deref()
965 .unwrap_or(&[])
966 .iter()
967 .all(|param| match param {
968 ParameterValue::Fixed(_) => false,
969 ParameterValue::Param(parameter) => parameter.as_symbol().is_some(),
970 })
971}
972
973fn instantiate_single_source_rule(
974 rule: &Rule,
975 operation: &LowerableOperation,
976) -> Result<Vec<MatchedReplacement>, CompilerError> {
977 debug_assert_eq!(rule.operations.len(), 1);
978 let source = rule.operations.first().ok_or_else(|| {
979 CompilerError::InvariantViolation(format!(
980 "target-basis lowering rule {} has no source operation",
981 rule.name
982 ))
983 })?;
984
985 let mut qubit_bindings = HashMap::with_capacity(source.qubits.len());
986 for (&rule_qubit, &actual_qubit) in source.qubits.iter().zip(&operation.qubits) {
987 qubit_bindings.insert(rule_qubit, actual_qubit);
988 }
989
990 let source_params = source.params.as_deref().unwrap_or(&[]);
991 if source_params.len() != operation.params.len() {
992 return Err(CompilerError::InvariantViolation(format!(
993 "planned target-basis rule {} parameter arity does not match {}",
994 rule.name, operation.instruction
995 )));
996 }
997
998 let mut parameter_bindings: HashMap<String, Parameter> = HashMap::new();
999 for (pattern, actual) in source_params.iter().zip(&operation.params) {
1000 match pattern {
1001 ParameterValue::Fixed(expected) => {
1002 let actual = Parameter::from(actual);
1003 if !Parameter::from(*expected)
1004 .provably_equal(&actual, crate::compile::PARAMETER_EQ_TOLERANCE)
1005 {
1006 return Err(CompilerError::InvariantViolation(format!(
1007 "planned target-basis rule {} fixed parameter does not match {}",
1008 rule.name, operation.instruction
1009 )));
1010 }
1011 }
1012 ParameterValue::Param(pattern) => {
1013 let Some(symbol) = pattern.as_symbol() else {
1014 return Err(CompilerError::InvariantViolation(format!(
1015 "target-basis rule {} uses a non-symbol source parameter",
1016 rule.name
1017 )));
1018 };
1019 let actual = Parameter::from(actual);
1020 if let Some(bound) = parameter_bindings.get(&symbol) {
1021 if !bound.provably_equal(&actual, crate::compile::PARAMETER_EQ_TOLERANCE) {
1022 return Err(CompilerError::InvariantViolation(format!(
1023 "planned target-basis rule {} repeated parameter does not match {}",
1024 rule.name, operation.instruction
1025 )));
1026 }
1027 } else {
1028 parameter_bindings.insert(symbol, actual);
1029 }
1030 }
1031 }
1032 }
1033
1034 let mut replacements = Vec::with_capacity(rule.target.len());
1035 for item in &rule.target {
1036 let key =
1037 KnowledgeInstructionKey::from_instruction(&item.instruction).ok_or_else(|| {
1038 CompilerError::InvariantViolation(format!(
1039 "unsupported target instruction in target-basis rule {:?}",
1040 item.instruction
1041 ))
1042 })?;
1043 let qubits = item
1044 .qubits
1045 .iter()
1046 .map(|rule_qubit| {
1047 qubit_bindings.get(rule_qubit).copied().ok_or_else(|| {
1048 CompilerError::InvariantViolation(format!(
1049 "target-basis rule {} references unbound qubit {}",
1050 rule.name, rule_qubit
1051 ))
1052 })
1053 })
1054 .collect::<Result<SmallVec<[Qubit; 3]>, _>>()?;
1055 let params = item
1056 .params
1057 .as_deref()
1058 .unwrap_or(&[])
1059 .iter()
1060 .map(|param| instantiate_rule_param(param, ¶meter_bindings, &rule.name))
1061 .collect::<Result<SmallVec<[ParameterValue; 3]>, _>>()?;
1062
1063 replacements.push(MatchedReplacement {
1064 instruction: item.instruction.clone(),
1065 qubits,
1066 params,
1067 key,
1068 });
1069 }
1070
1071 Ok(replacements)
1072}
1073
1074fn instantiate_rule_param(
1075 param: &ParameterValue,
1076 bindings: &HashMap<String, Parameter>,
1077 rule_name: &str,
1078) -> Result<ParameterValue, CompilerError> {
1079 let parameter = match param {
1080 ParameterValue::Fixed(value) => Parameter::from(*value),
1081 ParameterValue::Param(parameter) => {
1082 for symbol in parameter.get_symbols() {
1083 if !bindings.contains_key(&symbol) {
1084 return Err(CompilerError::InvariantViolation(format!(
1085 "target-basis rule {rule_name} references unbound parameter {symbol}"
1086 )));
1087 }
1088 }
1089 parameter.substitute_many(bindings)
1090 }
1091 };
1092 Ok(ParameterValue::from(parameter))
1093}
1094
1095fn key_rule_sort_value(key: &KnowledgeInstructionKey) -> usize {
1096 match key {
1097 KnowledgeInstructionKey::Standard(gate) => *gate as usize,
1098 KnowledgeInstructionKey::McGate(gate) => 10_000 + gate.num_qubits(),
1099 }
1100}
1101
1102#[cfg(test)]
1103#[path = "./target_basis_test.rs"]
1104mod target_basis_test;