1use crate::circuit::{
32 Circuit, ClassicalControlOp, Instruction, Operation, Parameter, ParameterValue, Qubit,
33 StandardGate, ValueClassicalControlOp, ValueControlBody, ValueInstruction, ValueOperation,
34 ValueSwitchCase,
35};
36use crate::compile::CompilerError;
37use crate::compile::device_planning::{
38 DeviceGateState, DevicePhysicalCost, DevicePlanSnapshot, DevicePlanningSession,
39 DirectionTemplate, NativePlanAvailability, NativePlanLeaf, PlanChoice, PlanTemplate,
40 SelectedNativePlan,
41};
42use crate::compile::knowledge::{
43 ConcreteOperationView, KnowledgeInstructionKey, RuleLibrary, instantiate_target,
44 rule_matches_operations,
45};
46use crate::compile::numeric_matrix::one_qubit_run_matrix;
47use crate::compile::transform::decompose::unitary::synthesize_numeric_1q_unitary;
48use crate::compile::transform::lowering_support::{
49 LowerableOperation, LoweringTarget, OperationSequenceLowerer,
50};
51use crate::compile::transform::rebuild::{CircuitRebuildContext, ClassicalRemap};
52use crate::compile::transform::{CircuitAnalysis, TransformOutcome, Transformer};
53use crate::device::{Device, PhysicalQubit};
54use smallvec::{SmallVec, smallvec};
55use std::collections::{BTreeMap, HashSet};
56use std::f64::consts::{FRAC_PI_2, PI};
57use std::sync::Arc;
58
59const PHASE_EPS: f64 = 1e-12;
60
61#[derive(Debug, Clone, Copy)]
64pub struct DeviceLowerer<'a> {
65 device: &'a Device,
66 planning_session: Option<&'a DevicePlanningSession>,
67}
68
69impl<'a> DeviceLowerer<'a> {
70 pub const fn new(device: &'a Device) -> Self {
72 Self {
73 device,
74 planning_session: None,
75 }
76 }
77
78 pub(crate) const fn with_session(
80 device: &'a Device,
81 planning_session: &'a DevicePlanningSession,
82 ) -> Self {
83 Self {
84 device,
85 planning_session: Some(planning_session),
86 }
87 }
88
89 pub const fn device(&self) -> &'a Device {
91 self.device
92 }
93}
94
95impl Transformer for DeviceLowerer<'_> {
96 fn name(&self) -> &'static str {
97 "device_lowering"
98 }
99
100 fn transform(
101 &self,
102 circuit: &Circuit,
103 _analysis: Option<&CircuitAnalysis>,
104 ) -> Result<TransformOutcome, CompilerError> {
105 let library = RuleLibrary::builtin_rules()
106 .map_err(|error| CompilerError::InvariantViolation(error.to_string()))?;
107 let scan = collect_root_states(circuit)?;
108 let mut roots = scan.states;
109 roots.sort();
110 roots.dedup();
111 let mut planner_roots = roots.clone();
119 for qubit in &scan.used_qubits {
120 for gate in [StandardGate::RZ, StandardGate::U] {
121 if let Some(instruction) =
122 KnowledgeInstructionKey::from_instruction(&Instruction::Standard(gate))
123 {
124 planner_roots.push(DeviceGateState {
125 instruction,
126 ordered_qargs: smallvec![PhysicalQubit::from_qubit(*qubit)],
127 });
128 }
129 }
130 }
131 planner_roots.sort();
132 planner_roots.dedup();
133 let owned_session;
134 let planning_session = if let Some(session) = self.planning_session {
135 session
136 } else {
137 owned_session = DevicePlanningSession::new(self.device);
138 &owned_session
139 };
140 let plans = planning_session.prepare(planner_roots.iter().cloned())?;
141 let all_roots_native = roots.iter().all(|state| {
148 matches!(
149 plans.selected_plan(state).map(|plan| plan.choice),
150 Some(PlanChoice::Native)
151 )
152 });
153 if !scan.has_gphase && all_roots_native && !has_fusible_one_qubit_run(circuit) {
154 return Ok(TransformOutcome::Unchanged);
155 }
156 DeviceCircuitLowerer::run(circuit, self.device, library, &plans)
157 }
158}
159
160struct DeviceCircuitLowerer<'a> {
161 source: &'a Circuit,
162 device: &'a Device,
163 library: &'a RuleLibrary,
164 plans: &'a DevicePlanSnapshot,
165 rebuild: CircuitRebuildContext,
166 changed: bool,
167 pending: BTreeMap<Qubit, Vec<ValueOperation>>,
169 emit_fused: bool,
172}
173
174impl<'a> DeviceCircuitLowerer<'a> {
175 fn run(
176 source: &'a Circuit,
177 device: &'a Device,
178 library: &'a RuleLibrary,
179 plans: &'a DevicePlanSnapshot,
180 ) -> Result<TransformOutcome, CompilerError> {
181 let rebuild = CircuitRebuildContext::new(source);
182 let root_classical = rebuild.root_classical().clone();
183 let mut lowerer = Self {
184 source,
185 device,
186 library,
187 plans,
188 rebuild,
189 changed: false,
190 pending: BTreeMap::new(),
191 emit_fused: false,
192 };
193 let mut operations = Vec::with_capacity(source.operations().len());
194 let mut phase_delta = Parameter::from(0.0);
195 lowerer.lower_sequence(
196 source.operations(),
197 &root_classical,
198 LoweringTarget::top_level(&mut operations, &mut phase_delta),
199 )?;
200 let mut target = LoweringTarget::top_level(&mut operations, &mut phase_delta);
201 lowerer.flush_all_pending(&mut target)?;
202 let global_phase = &source.global_phase() + &phase_delta;
203 let circuit = lowerer
204 .rebuild
205 .finish(source.qubits(), operations, global_phase)?;
206 Ok(if lowerer.changed {
207 TransformOutcome::Changed(circuit)
208 } else {
209 TransformOutcome::Unchanged
210 })
211 }
212
213 fn lower_operation(
214 &mut self,
215 operation: &Operation,
216 classical_remap: &ClassicalRemap,
217 target: &mut LoweringTarget<'_>,
218 ) -> Result<(), CompilerError> {
219 match &operation.instruction {
220 Instruction::Standard(_) | Instruction::McGate(_) => {
221 let params =
222 CircuitRebuildContext::resolve_source_params(self.source, &operation.params)?;
223 self.lower_gate_like(
224 LowerableOperation {
225 instruction: operation.instruction.clone(),
226 qubits: operation.qubits.clone(),
227 params,
228 label: operation.label.clone(),
229 },
230 target,
231 )
232 }
233 Instruction::ClassicalControl(control) => {
234 self.flush_all_pending(target)?;
238 let instruction = self.lower_control_flow(control, classical_remap)?;
239 self.push_operation(
240 ValueOperation {
241 qubits: instruction.used_qubits().into_iter().collect(),
242 instruction: ValueInstruction::ClassicalControl(instruction),
243 params: SmallVec::new(),
244 label: operation.label.clone(),
245 },
246 target,
247 )?;
248 Ok(())
249 }
250 Instruction::UnitaryGate(_) | Instruction::CircuitGate(_) => {
251 Err(CompilerError::InvalidInput(format!(
252 "cannot lower {} to device instructions: definitions and unitaries must be decomposed first",
253 operation.instruction
254 )))
255 }
256 Instruction::ClassicalData(_) | Instruction::Directive(_) | Instruction::Delay => {
257 let operation = self.rebuild.remap_preserved_operation(
258 self.source,
259 operation,
260 classical_remap,
261 )?;
262 self.push_operation(operation, target)?;
263 Ok(())
264 }
265 }
266 }
267
268 fn lower_gate_like(
269 &mut self,
270 operation: LowerableOperation,
271 target: &mut LoweringTarget<'_>,
272 ) -> Result<(), CompilerError> {
273 if matches!(
274 operation.instruction,
275 Instruction::Standard(StandardGate::GPhase)
276 ) {
277 self.changed = true;
278 target.accumulate_phase(operation.gphase_param()?);
279 return Ok(());
280 }
281
282 let ordered_qargs = operation
283 .qubits
284 .iter()
285 .copied()
286 .map(PhysicalQubit::from_qubit)
287 .collect();
288 let state = DeviceGateState::from_instruction(&operation.instruction, ordered_qargs)
289 .ok_or_else(|| {
290 CompilerError::InvariantViolation(format!(
291 "missing device-lowering key for {}",
292 operation.instruction
293 ))
294 })?;
295 let Some(plan) = self.plans.selected_plan(&state) else {
296 return match self.plans.availability(&state) {
297 Some(NativePlanAvailability::Unsupported(failure)) => Err(
298 CompilerError::DeviceLoweringFailed(failure.as_ref().clone()),
299 ),
300 _ => Err(CompilerError::InvariantViolation(format!(
301 "prepared device state has no selected plan or failure: {state:?}"
302 ))),
303 };
304 };
305 self.lower_gate_like_with_plan(operation, state, plan, target)
306 }
307
308 fn lower_gate_like_with_plan(
309 &mut self,
310 operation: LowerableOperation,
311 state: DeviceGateState,
312 plan: Arc<SelectedNativePlan>,
313 target: &mut LoweringTarget<'_>,
314 ) -> Result<(), CompilerError> {
315 if plan.state != state {
316 return Err(CompilerError::InvariantViolation(format!(
317 "device plan targets {:?}, but lowering requested {state:?}",
318 plan.state
319 )));
320 }
321 match plan.choice {
322 PlanChoice::Native => {
323 if !self
324 .device
325 .supports_native_instruction(&operation.instruction, &state.ordered_qargs)
326 {
327 return Err(CompilerError::InvariantViolation(format!(
328 "device planner selected unsupported native leaf {} on {:?}",
329 operation.instruction, state.ordered_qargs
330 )));
331 }
332 self.push_operation(
333 ValueOperation {
334 instruction: ValueInstruction::from_instruction(operation.instruction),
335 qubits: operation.qubits,
336 params: operation.params,
337 label: operation.label,
338 },
339 target,
340 )?;
341 }
342 PlanChoice::Template(template) => {
343 self.changed = true;
344 let child_plans = plan.children.to_vec();
345 let replacements = match template {
346 PlanTemplate::Rule(rule_id) => {
347 let rule = self.library.get(rule_id).ok_or_else(|| {
348 CompilerError::InvariantViolation(format!(
349 "missing selected device-lowering rule {rule_id:?}"
350 ))
351 })?;
352 instantiate_rule(rule, &operation)?
353 }
354 PlanTemplate::Direction(template) => {
355 instantiate_direction_template(template, &operation)
356 }
357 };
358 let mut children = child_plans.into_iter();
359 for replacement in replacements {
360 if matches!(
361 replacement.instruction,
362 Instruction::Standard(StandardGate::GPhase)
363 ) {
364 self.lower_gate_like(replacement, target)?;
365 continue;
366 }
367 let child = children.next().ok_or_else(|| {
368 CompilerError::InvariantViolation(format!(
369 "device plan for {state:?} emitted more non-phase children than planned"
370 ))
371 })?;
372 let ordered_qargs = replacement
373 .qubits
374 .iter()
375 .copied()
376 .map(PhysicalQubit::from_qubit)
377 .collect();
378 let child_state =
379 DeviceGateState::from_instruction(&replacement.instruction, ordered_qargs)
380 .ok_or_else(|| {
381 CompilerError::InvariantViolation(format!(
382 "missing device-lowering key for planned child {}",
383 replacement.instruction
384 ))
385 })?;
386 self.lower_gate_like_with_plan(replacement, child_state, child, target)?;
387 }
388 if children.next().is_some() {
389 return Err(CompilerError::InvariantViolation(format!(
390 "device plan for {state:?} emitted fewer non-phase children than planned"
391 )));
392 }
393 }
394 }
395 Ok(())
396 }
397
398 fn lower_control_flow(
399 &mut self,
400 control: &ClassicalControlOp,
401 classical_remap: &ClassicalRemap,
402 ) -> Result<ValueClassicalControlOp, CompilerError> {
403 Ok(match control {
404 ClassicalControlOp::If(op) => ValueClassicalControlOp::If {
405 condition: classical_remap.remap_expr(op.condition())?,
406 then_body: self.lower_body(op.then_body(), classical_remap)?,
407 else_body: op
408 .else_body()
409 .map(|body| self.lower_body(body, classical_remap))
410 .transpose()?,
411 },
412 ClassicalControlOp::While(op) => ValueClassicalControlOp::While {
413 condition: classical_remap.remap_expr(op.condition())?,
414 body: self.lower_body(op.body(), classical_remap)?,
415 },
416 ClassicalControlOp::For(op) => ValueClassicalControlOp::For {
417 var: classical_remap.remap_var(op.var())?,
418 start: classical_remap.remap_expr(op.start())?,
419 stop: classical_remap.remap_expr(op.stop())?,
420 step: classical_remap.remap_expr(op.step())?,
421 body: self.lower_body(op.body(), classical_remap)?,
422 },
423 ClassicalControlOp::Switch(op) => ValueClassicalControlOp::Switch {
424 target: classical_remap.remap_expr(op.target())?,
425 cases: op
426 .cases()
427 .iter()
428 .map(|case| {
429 Ok(ValueSwitchCase::new(
430 case.value(),
431 self.lower_body(case.body(), classical_remap)?,
432 ))
433 })
434 .collect::<Result<_, CompilerError>>()?,
435 default: op
436 .default()
437 .map(|body| self.lower_body(body, classical_remap))
438 .transpose()?,
439 },
440 ClassicalControlOp::Break => ValueClassicalControlOp::Break,
441 ClassicalControlOp::Continue => ValueClassicalControlOp::Continue,
442 })
443 }
444
445 fn lower_body(
446 &mut self,
447 body: &crate::circuit::ControlBody,
448 classical_remap: &ClassicalRemap,
449 ) -> Result<ValueControlBody, CompilerError> {
450 let mut output = Vec::with_capacity(body.operations().len());
451 let mut phase_delta = Parameter::from(0.0);
452 self.lower_sequence(
453 body.operations(),
454 classical_remap,
455 LoweringTarget::control_flow_body(&mut output, &mut phase_delta),
456 )?;
457 self.flush_all_pending(&mut LoweringTarget::control_flow_body(
458 &mut output,
459 &mut phase_delta,
460 ))?;
461 self.prepend_body_phase(&mut output, phase_delta);
462 Ok(ValueControlBody::new(output))
463 }
464
465 fn prepend_body_phase(&mut self, body: &mut Vec<ValueOperation>, phase: Parameter) {
466 if phase.is_zero() {
467 return;
468 }
469 body.insert(
470 0,
471 ValueOperation {
472 instruction: ValueInstruction::from_instruction(Instruction::Standard(
473 StandardGate::GPhase,
474 )),
475 qubits: SmallVec::new(),
476 params: smallvec![ParameterValue::from(phase)],
477 label: None,
478 },
479 );
480 }
481
482 fn push_operation(
490 &mut self,
491 operation: ValueOperation,
492 target: &mut LoweringTarget<'_>,
493 ) -> Result<(), CompilerError> {
494 if self.emit_fused {
495 self.push_direct(operation, target);
496 return Ok(());
497 }
498 if let Some(qubit) = bufferable_one_qubit_leaf(&operation) {
499 self.pending.entry(qubit).or_default().push(operation);
500 return Ok(());
501 }
502 match boundary_qubits(&operation) {
503 BoundaryQubits::Touched(qubits) => {
504 for qubit in qubits {
505 self.flush_qubit(qubit, target)?;
506 }
507 }
508 BoundaryQubits::All => self.flush_all_pending(target)?,
509 }
510 self.push_direct(operation, target);
511 Ok(())
512 }
513
514 fn push_direct(&mut self, operation: ValueOperation, target: &mut LoweringTarget<'_>) {
515 target.push(operation);
516 }
517
518 fn flush_qubit(
519 &mut self,
520 qubit: Qubit,
521 target: &mut LoweringTarget<'_>,
522 ) -> Result<(), CompilerError> {
523 let Some(run) = self.pending.remove(&qubit) else {
524 return Ok(());
525 };
526 self.emit_fused_run(qubit, run, target)
527 }
528
529 fn flush_all_pending(&mut self, target: &mut LoweringTarget<'_>) -> Result<(), CompilerError> {
530 let qubits = self.pending.keys().copied().collect::<Vec<_>>();
531 for qubit in qubits {
532 self.flush_qubit(qubit, target)?;
533 }
534 Ok(())
535 }
536
537 fn emit_fused_run(
545 &mut self,
546 qubit: Qubit,
547 run: Vec<ValueOperation>,
548 target: &mut LoweringTarget<'_>,
549 ) -> Result<(), CompilerError> {
550 let run = self.peephole_merge_run(qubit, run, target);
551 if run.is_empty() {
552 return Ok(());
553 }
554 if run.len() == 1 {
555 self.push_direct(run.into_iter().next().expect("one operation"), target);
556 return Ok(());
557 }
558 let candidate = one_qubit_run_matrix(&run)
559 .and_then(|matrix| synthesize_numeric_1q_unitary(&matrix).ok())
560 .and_then(|decomposition| self.fused_form(qubit, decomposition, &run));
561 match candidate {
562 Some(FusedForm::Identity { phase }) => {
563 self.changed = true;
564 target.accumulate_phase(Parameter::from(phase));
565 }
566 Some(FusedForm::Rz { angle, phase, plan }) => {
567 self.changed = true;
568 target.accumulate_phase(Parameter::from(phase));
569 self.emit_via_plan(
570 StandardGate::RZ,
571 qubit,
572 smallvec![ParameterValue::Fixed(angle)],
573 plan,
574 target,
575 )?;
576 }
577 Some(FusedForm::U {
578 decomposition,
579 plan,
580 }) => {
581 self.changed = true;
582 target.accumulate_phase(Parameter::from(decomposition.global_phase));
583 self.emit_via_plan(
584 StandardGate::U,
585 qubit,
586 smallvec![
587 ParameterValue::Fixed(decomposition.theta),
588 ParameterValue::Fixed(decomposition.phi),
589 ParameterValue::Fixed(decomposition.lambda),
590 ],
591 plan,
592 target,
593 )?;
594 }
595 None => {
596 for operation in run {
597 self.push_direct(operation, target);
598 }
599 }
600 }
601 Ok(())
602 }
603
604 fn peephole_merge_run(
610 &mut self,
611 qubit: Qubit,
612 run: Vec<ValueOperation>,
613 target: &mut LoweringTarget<'_>,
614 ) -> Vec<ValueOperation> {
615 let mut merged: Vec<ValueOperation> = Vec::with_capacity(run.len());
616 for operation in run {
617 let Some(last) = merged.len().checked_sub(1) else {
618 merged.push(operation);
619 continue;
620 };
621 if let (Some(left), Some(right)) =
623 (fixed_rz_angle(&merged[last]), fixed_rz_angle(&operation))
624 {
625 let angle = left + right;
626 merged.pop();
627 if angle.abs() > PHASE_EPS {
628 merged.push(rz_operation(qubit, angle));
629 }
630 self.changed = true;
631 continue;
632 }
633 match (x2_variant(&merged[last]), x2_variant(&operation)) {
634 (Some(plus), Some(other)) if plus == other => {
635 let pair_cost = self.pair_leaf_cost(&merged[last], &operation);
640 let native_x_better = self
641 .plan_info(StandardGate::X, PhysicalQubit::from_qubit(qubit))
642 .is_some_and(|info| {
643 matches!(info.plan.choice, PlanChoice::Native)
644 && info.leaf_count == 1
645 && pair_cost
646 .is_some_and(|pair| info.cost.strictly_better_than(pair))
647 });
648 if !native_x_better {
649 merged.push(operation);
650 continue;
651 }
652 merged.pop();
653 let phase = if plus { -FRAC_PI_2 } else { FRAC_PI_2 };
654 target.accumulate_phase(Parameter::from(phase));
655 merged.push(x_operation(qubit));
656 self.changed = true;
657 }
658 (Some(_), Some(_)) => {
659 merged.pop();
661 self.changed = true;
662 }
663 _ => {
664 if is_plain_x(&merged[last]) && is_plain_x(&operation) {
665 merged.pop();
667 self.changed = true;
668 continue;
669 }
670 merged.push(operation);
671 }
672 }
673 }
674 merged
675 }
676
677 fn pair_leaf_cost(
679 &self,
680 first: &ValueOperation,
681 second: &ValueOperation,
682 ) -> Option<DevicePhysicalCost> {
683 let leaves = [first, second]
684 .into_iter()
685 .map(|operation| self.native_leaf(operation))
686 .collect::<Option<Vec<_>>>()?;
687 Some(self.plans.leaves_physical_cost(&leaves))
688 }
689
690 fn fused_form(
693 &self,
694 qubit: Qubit,
695 decomposition: crate::compile::transform::decompose::unitary::OneQubitUnitaryDecomposition,
696 run: &[ValueOperation],
697 ) -> Option<FusedForm> {
698 let physical = PhysicalQubit::from_qubit(qubit);
699 let angle = decomposition.phi + decomposition.lambda;
700 let wrapped = angle.rem_euclid(2.0 * PI);
704 if decomposition.theta.abs() <= PHASE_EPS
705 && (wrapped <= PHASE_EPS || (2.0 * PI - wrapped) <= PHASE_EPS)
706 {
707 return Some(FusedForm::Identity {
708 phase: decomposition.global_phase,
709 });
710 }
711 let run_cost = self.run_physical_cost(run)?;
716 let mut best: Option<(DevicePhysicalCost, FusedForm)> = None;
717 if decomposition.theta.abs() <= PHASE_EPS
718 && let Some(info) = self.plan_info(StandardGate::RZ, physical).filter(|info| {
719 fusion_candidate_is_admissible(info.leaf_count, info.cost, run.len(), run_cost)
720 })
721 {
722 best = Some((
725 info.cost,
726 FusedForm::Rz {
727 angle,
728 phase: decomposition.global_phase + angle / 2.0,
729 plan: info.plan,
730 },
731 ));
732 }
733 if let Some(info) = self.plan_info(StandardGate::U, physical).filter(|info| {
734 fusion_candidate_is_admissible(info.leaf_count, info.cost, run.len(), run_cost)
735 }) && best
736 .as_ref()
737 .is_none_or(|(best_cost, _)| info.cost.strictly_better_than(*best_cost))
738 {
739 best = Some((
740 info.cost,
741 FusedForm::U {
742 decomposition,
743 plan: info.plan,
744 },
745 ));
746 }
747 best.map(|(_, form)| form)
748 }
749
750 fn plan_info(&self, gate: StandardGate, physical: PhysicalQubit) -> Option<FusionPlanInfo> {
753 let state =
754 DeviceGateState::from_instruction(&Instruction::Standard(gate), smallvec![physical])?;
755 let plan = self.plans.selected_plan(&state)?;
756 let cost = plan.physical_cost;
757 let leaf_count = plan.summary.leaves.len();
758 Some(FusionPlanInfo {
759 plan,
760 cost,
761 leaf_count,
762 })
763 }
764
765 fn run_physical_cost(&self, run: &[ValueOperation]) -> Option<DevicePhysicalCost> {
767 let leaves = run
768 .iter()
769 .map(|operation| self.native_leaf(operation))
770 .collect::<Option<Vec<_>>>()?;
771 Some(self.plans.leaves_physical_cost(&leaves))
772 }
773
774 fn native_leaf(&self, operation: &ValueOperation) -> Option<NativePlanLeaf> {
777 let ValueInstruction::Instruction(instruction) = &operation.instruction else {
778 return None;
779 };
780 let ordered_qargs: SmallVec<[PhysicalQubit; 2]> = operation
781 .qubits
782 .iter()
783 .copied()
784 .map(PhysicalQubit::from_qubit)
785 .collect();
786 let calibration = self
787 .device
788 .native_instruction_calibration(instruction, &ordered_qargs)?;
789 Some(NativePlanLeaf {
790 instruction: instruction.clone(),
791 ordered_qargs,
792 error_rate: calibration.error_rate,
793 duration: calibration.duration,
794 })
795 }
796
797 fn emit_via_plan(
800 &mut self,
801 gate: StandardGate,
802 qubit: Qubit,
803 params: SmallVec<[ParameterValue; 3]>,
804 plan: Arc<SelectedNativePlan>,
805 target: &mut LoweringTarget<'_>,
806 ) -> Result<(), CompilerError> {
807 let instruction = Instruction::Standard(gate);
808 let state = DeviceGateState::from_instruction(
809 &instruction,
810 smallvec![PhysicalQubit::from_qubit(qubit)],
811 )
812 .ok_or_else(|| {
813 CompilerError::InvariantViolation(format!(
814 "missing device-lowering key for fused {gate:?} emission"
815 ))
816 })?;
817 self.emit_fused = true;
818 let result = self.lower_gate_like_with_plan(
819 LowerableOperation {
820 instruction,
821 qubits: smallvec![qubit],
822 params: SmallVec::from_vec(params.into_vec()),
823 label: None,
824 },
825 state,
826 plan,
827 target,
828 );
829 self.emit_fused = false;
830 result
831 }
832}
833
834impl OperationSequenceLowerer for DeviceCircuitLowerer<'_> {
835 fn lower_one_operation(
836 &mut self,
837 operation: &Operation,
838 classical_remap: &ClassicalRemap,
839 target: &mut LoweringTarget<'_>,
840 ) -> Result<(), CompilerError> {
841 self.lower_operation(operation, classical_remap, target)
842 }
843}
844
845enum FusedForm {
847 Identity { phase: f64 },
849 Rz {
851 angle: f64,
852 phase: f64,
853 plan: Arc<SelectedNativePlan>,
854 },
855 U {
857 decomposition: crate::compile::transform::decompose::unitary::OneQubitUnitaryDecomposition,
858 plan: Arc<SelectedNativePlan>,
859 },
860}
861
862struct FusionPlanInfo {
864 plan: Arc<SelectedNativePlan>,
865 cost: DevicePhysicalCost,
866 leaf_count: usize,
867}
868
869fn fusion_candidate_is_admissible(
872 candidate_leaf_count: usize,
873 candidate_cost: DevicePhysicalCost,
874 run_leaf_count: usize,
875 run_cost: DevicePhysicalCost,
876) -> bool {
877 candidate_leaf_count < run_leaf_count && candidate_cost.strictly_better_than(run_cost)
878}
879
880enum BoundaryQubits {
882 Touched(Vec<Qubit>),
884 All,
886}
887
888fn boundary_qubits(operation: &ValueOperation) -> BoundaryQubits {
896 match &operation.instruction {
897 ValueInstruction::ClassicalControl(_) => BoundaryQubits::All,
898 ValueInstruction::Instruction(_) if operation.qubits.is_empty() => BoundaryQubits::All,
899 ValueInstruction::Instruction(_) => {
900 BoundaryQubits::Touched(operation.qubits.iter().copied().collect())
901 }
902 }
903}
904
905fn bufferable_one_qubit_leaf(operation: &ValueOperation) -> Option<Qubit> {
908 let ValueInstruction::Instruction(Instruction::Standard(gate)) = &operation.instruction else {
909 return None;
910 };
911 let fixed_numeric = operation
912 .params
913 .iter()
914 .all(|param| matches!(param, ParameterValue::Fixed(value) if value.is_finite()));
915 bufferable_one_qubit(
916 *gate,
917 &operation.qubits,
918 operation.label.is_some(),
919 fixed_numeric,
920 )
921}
922
923fn bufferable_one_qubit(
925 gate: StandardGate,
926 qubits: &[Qubit],
927 has_label: bool,
928 fixed_numeric: bool,
929) -> Option<Qubit> {
930 (gate.num_qubits() == 1 && qubits.len() == 1 && !has_label && fixed_numeric)
931 .then_some(qubits[0])
932}
933
934fn fixed_rz_angle(operation: &ValueOperation) -> Option<f64> {
936 if !matches!(
937 &operation.instruction,
938 ValueInstruction::Instruction(Instruction::Standard(StandardGate::RZ))
939 ) {
940 return None;
941 }
942 match operation.params.first() {
943 Some(ParameterValue::Fixed(value)) if value.is_finite() => Some(*value),
944 _ => None,
945 }
946}
947
948fn rz_operation(qubit: Qubit, angle: f64) -> ValueOperation {
949 ValueOperation {
950 instruction: ValueInstruction::from_instruction(Instruction::Standard(StandardGate::RZ)),
951 qubits: smallvec![qubit],
952 params: smallvec![ParameterValue::Fixed(angle)],
953 label: None,
954 }
955}
956
957fn x_operation(qubit: Qubit) -> ValueOperation {
958 ValueOperation {
959 instruction: ValueInstruction::from_instruction(Instruction::Standard(StandardGate::X)),
960 qubits: smallvec![qubit],
961 params: SmallVec::new(),
962 label: None,
963 }
964}
965
966fn x2_variant(operation: &ValueOperation) -> Option<bool> {
968 match &operation.instruction {
969 ValueInstruction::Instruction(Instruction::Standard(StandardGate::X2P)) => Some(true),
970 ValueInstruction::Instruction(Instruction::Standard(StandardGate::X2M)) => Some(false),
971 _ => None,
972 }
973}
974
975fn is_plain_x(operation: &ValueOperation) -> bool {
976 matches!(
977 &operation.instruction,
978 ValueInstruction::Instruction(Instruction::Standard(StandardGate::X))
979 )
980}
981
982struct RootStateScan {
986 states: Vec<DeviceGateState>,
987 has_gphase: bool,
988 used_qubits: Vec<Qubit>,
989}
990
991fn collect_root_states(circuit: &Circuit) -> Result<RootStateScan, CompilerError> {
992 fn collect(
993 operations: &[Operation],
994 scan: &mut (HashSet<DeviceGateState>, bool, HashSet<Qubit>),
995 ) {
996 for operation in operations {
997 match &operation.instruction {
998 Instruction::Standard(StandardGate::GPhase) => scan.1 = true,
999 Instruction::Standard(_) | Instruction::McGate(_) => {
1000 scan.2.extend(operation.qubits.iter().copied());
1001 if let Some(instruction) =
1002 KnowledgeInstructionKey::from_instruction(&operation.instruction)
1003 {
1004 scan.0.insert(DeviceGateState {
1005 instruction,
1006 ordered_qargs: operation
1007 .qubits
1008 .iter()
1009 .copied()
1010 .map(PhysicalQubit::from_qubit)
1011 .collect(),
1012 });
1013 }
1014 }
1015 Instruction::ClassicalControl(control) => {
1016 for body in control.bodies() {
1017 collect(body.operations(), scan);
1018 }
1019 }
1020 _ => {}
1021 }
1022 }
1023 }
1024
1025 let mut scan = (HashSet::new(), false, HashSet::new());
1026 collect(circuit.operations(), &mut scan);
1027 Ok(RootStateScan {
1028 states: scan.0.into_iter().collect(),
1029 has_gphase: scan.1,
1030 used_qubits: scan.2.into_iter().collect(),
1031 })
1032}
1033
1034fn has_fusible_one_qubit_run(circuit: &Circuit) -> bool {
1041 fn bufferable_source_qubit(operation: &Operation) -> Option<Qubit> {
1042 let Instruction::Standard(gate) = &operation.instruction else {
1043 return None;
1044 };
1045 let fixed_numeric = operation
1046 .params
1047 .iter()
1048 .all(|param| matches!(param, crate::circuit::CircuitParam::Fixed(value) if value.is_finite()));
1049 bufferable_one_qubit(
1050 *gate,
1051 &operation.qubits,
1052 operation.label.is_some(),
1053 fixed_numeric,
1054 )
1055 }
1056
1057 fn flush_source_boundary(operation: &Operation, pending: &mut HashSet<Qubit>) {
1058 if operation.qubits.is_empty() {
1059 pending.clear();
1060 } else {
1061 for qubit in &operation.qubits {
1062 pending.remove(qubit);
1063 }
1064 }
1065 }
1066
1067 fn scan(operations: &[Operation]) -> bool {
1068 let mut pending = HashSet::<Qubit>::new();
1069 for operation in operations {
1070 match &operation.instruction {
1071 Instruction::Standard(_) | Instruction::McGate(_) => {
1072 if let Some(qubit) = bufferable_source_qubit(operation) {
1073 if !pending.insert(qubit) {
1074 return true;
1075 }
1076 } else {
1077 flush_source_boundary(operation, &mut pending);
1078 }
1079 }
1080 Instruction::ClassicalControl(control) => {
1081 pending.clear();
1082 let body_has_run = control.bodies().any(|body| scan(body.operations()));
1083 if body_has_run {
1084 return true;
1085 }
1086 }
1087 _ => flush_source_boundary(operation, &mut pending),
1088 }
1089 }
1090 false
1091 }
1092
1093 scan(circuit.operations())
1094}
1095
1096fn instantiate_rule(
1097 rule: &crate::compile::knowledge::rule::Rule,
1098 operation: &LowerableOperation,
1099) -> Result<Vec<LowerableOperation>, CompilerError> {
1100 let params = operation
1101 .params
1102 .iter()
1103 .map(Parameter::from)
1104 .collect::<Vec<_>>();
1105 let view = ConcreteOperationView::new(&operation.instruction, &operation.qubits, ¶ms);
1106 let bindings = rule_matches_operations(rule, &[view])
1107 .map_err(|error| CompilerError::InvariantViolation(error.to_string()))?
1108 .ok_or_else(|| {
1109 CompilerError::InvariantViolation(format!(
1110 "selected device-lowering rule {} does not match {}",
1111 rule.name, operation.instruction
1112 ))
1113 })?;
1114 let replacements = instantiate_target(&rule.target, &bindings)
1115 .map_err(|error| CompilerError::InvariantViolation(error.to_string()))?;
1116 Ok(replacements
1117 .into_iter()
1118 .map(|replacement| LowerableOperation {
1119 instruction: replacement.instruction,
1120 qubits: replacement.qubits,
1121 params: SmallVec::from_vec(replacement.params.into_vec()),
1122 label: None,
1123 })
1124 .collect())
1125}
1126
1127fn instantiate_direction_template(
1128 template: DirectionTemplate,
1129 source: &LowerableOperation,
1130) -> Vec<LowerableOperation> {
1131 debug_assert_eq!(source.qubits.len(), 2);
1132 let q0 = source.qubits[0];
1133 let q1 = source.qubits[1];
1134 let reversed = || LowerableOperation {
1135 instruction: source.instruction.clone(),
1136 qubits: smallvec![q1, q0],
1137 params: source.params.clone(),
1138 label: source.label.clone(),
1139 };
1140 match template {
1141 DirectionTemplate::Cx | DirectionTemplate::Rzx => {
1142 let h = |qubit| LowerableOperation {
1143 instruction: Instruction::Standard(StandardGate::H),
1144 qubits: smallvec![qubit],
1145 params: SmallVec::new(),
1146 label: None,
1147 };
1148 vec![h(q0), h(q1), reversed(), h(q0), h(q1)]
1149 }
1150 DirectionTemplate::Symmetric(_) => vec![reversed()],
1151 }
1152}
1153
1154#[cfg(test)]
1155#[path = "device_lowering_test.rs"]
1156mod device_lowering_test;