Skip to main content

cqlib_core/compile/transform/
device_lowering.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0.
6// You may obtain a copy of this license in the LICENSE.txt file in
7// the root directory of this source tree or at
8// http://www.apache.org/licenses/LICENSE-2.0.
9//
10// Any modifications or derivative works of this code must retain this
11// copyright notice, and modified files need to carry a notice indicating
12// that they have been altered from the originals.
13
14//! Exact ordered-qargs lowering to a concrete device instruction set.
15//!
16//! Routing deliberately uses an undirected connectivity projection. This
17//! transform runs after routing and closes the remaining ISA gap: every
18//! gate-like operation is either retained as an exact native capability or
19//! lowered through a finite, recursively verified plan whose leaves are exact
20//! device capabilities. A separate device verifier remains the terminal safety
21//! boundary for each finalized candidate. Enhanced workflow selection may run
22//! afterward, but it only chooses among candidates that crossed that boundary.
23//!
24//! Emission fuses buffered one-qubit runs on each qubit before they reach the
25//! output: exact peephole merges (`RZ` accumulation, `X2P`/`X2M` pairs,
26//! inverse-pair cancellation) run first, and a run is then replaced by a
27//! re-synthesized `RZ`/`U` plan only when the fused form has both strictly
28//! fewer native leaves and strictly better calibrated physical cost. Fusion
29//! therefore never grows the lowered circuit or trades fidelity for gate count.
30
31use 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/// Lowers a routed physical circuit to one device's exact native instruction
62/// capabilities, including ordered qargs and local capability overrides.
63#[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    /// Creates a lowerer borrowing one immutable device capability model.
71    pub const fn new(device: &'a Device) -> Self {
72        Self {
73            device,
74            planning_session: None,
75        }
76    }
77
78    /// Reuses a compile-scoped calibration snapshot and prepared root cache.
79    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    /// Returns the device whose exact capabilities define lowering leaves.
90    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        // Fused lowering can synthesize buffered one-qubit runs into `RZ` or
112        // `U` operations on any circuit qubit that actually carries gates, so
113        // the planner needs candidate plans for those states. They are kept
114        // separate from circuit roots: they must not count toward the
115        // already-native fast path (a missing `U` plan must not block it), and
116        // unused qubits are excluded so planning does not grow with device
117        // width on sparse circuits.
118        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        // Fast path: when no phase folding is pending, the planner selects the
142        // native leaf for every circuit root state, and no potentially
143        // fusible one-qubit run exists, lowering (and fused emission) is a
144        // no-op and the full circuit rebuild can be skipped. This check must
145        // go through the planner: a natively supported gate can still lower
146        // further when a cheaper direction or template realization exists.
147        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 native one-qubit leaves buffered per qubit for run fusion.
168    pending: BTreeMap<Qubit, Vec<ValueOperation>>,
169    /// Set while a synthesized fusion form is being emitted so its leaves
170    /// bypass the pending buffer (otherwise emission would fuse again).
171    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                // Control-flow bodies are hard fusion boundaries: flush the
235                // enclosing scope's pending runs before lowering any body so
236                // nothing is delayed across (or into) a structured body.
237                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    /// Emits one operation, buffering native one-qubit leaves for run fusion.
483    ///
484    /// Buffering delays emission past operations on disjoint qubits, which is
485    /// a commutation on disjoint supports. Anything touching a buffered qubit
486    /// (a multi-qubit leaf, a directive, a delay, or a measurement) flushes
487    /// exactly that qubit; global boundaries (control flow, empty-scope
488    /// barriers, classical stores) flush every qubit in deterministic order.
489    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    /// Emits one buffered one-qubit run, replacing it with a shorter fused
538    /// synthesis when the planner offers one on this qubit.
539    ///
540    /// Fusion is exact: the buffered run's 2x2 unitary is recomposed from the
541    /// gate matrices and re-synthesized. A candidate is emitted only when it
542    /// is strictly shorter than the buffered run, so fusion never grows the
543    /// lowered circuit; otherwise the run passes through unchanged.
544    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    /// Applies exact intra-run peephole merges before whole-run synthesis:
605    /// `RZ` angle accumulation, `X2P`/`X2M` pairs to `X` when the planner's
606    /// realization of `X` on the qubit is the native gate and strictly better
607    /// than the pair (their scalar factors fold into `phase_delta`), and
608    /// exact inverse-pair cancellation.
609    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            // RZ(a) · RZ(b) = RZ(a+b) exactly; a zero sum cancels the pair.
622            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                    // X2P² = e^{-iπ/2}·X and X2M² = e^{+iπ/2}·X. Merge only
636                    // when the planner realizes X on this qubit as the native
637                    // gate and that realization is strictly better than the
638                    // pair it replaces (a noisy native X must not win).
639                    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                    // X2P · X2M = X2M · X2P = I exactly.
660                    merged.pop();
661                    self.changed = true;
662                }
663                _ => {
664                    if is_plain_x(&merged[last]) && is_plain_x(&operation) {
665                        // X · X = I exactly.
666                        merged.pop();
667                        self.changed = true;
668                        continue;
669                    }
670                    merged.push(operation);
671                }
672            }
673        }
674        merged
675    }
676
677    /// Costs two adjacent buffered operations as native leaves.
678    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    /// Selects the shortest exact fused form for one buffered run, or `None`
691    /// when no candidate beats simply emitting the run.
692    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        // With θ≈0 the run is M = e^{i·gp}·diag(1, e^{iγ}) for γ = φ + λ.
701        // When γ ≡ 0 (mod 2π) the diagonal is exactly the identity, so the
702        // whole run is the scalar e^{i·gp}: emit no gate and keep the phase.
703        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        // Candidates must be both strictly shorter and strictly better under
712        // the exact device physical cost used by the planner and native loop.
713        // This keeps lowering leaf-monotone while rejecting shorter but noisier
714        // (or slower) realizations.
715        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            // RZ(γ) = diag(e^{-iγ/2}, e^{iγ/2}) while U(0,φ,λ) = diag(1, e^{i(φ+λ)}),
723            // so the run is RZ(φ+λ) up to the extra phase (φ+λ)/2.
724            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    /// Returns the selected plan, exact physical cost, and native leaf count
751    /// for a gate on one physical qubit, when the planner prepared that state.
752    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    /// Costs one buffered run exactly as the planner would cost its leaves.
766    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    /// Builds the native plan leaf for one buffered operation, including its
775    /// calibration data.
776    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    /// Emits one fused gate through its selected plan with buffering
798    /// suspended, so the plan's own leaves reach the output directly.
799    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
845/// One exact fused replacement for a buffered one-qubit run.
846enum FusedForm {
847    /// The run is a pure global phase; no operation is emitted.
848    Identity { phase: f64 },
849    /// The run is a single `RZ` up to `phase` (a pure z rotation).
850    Rz {
851        angle: f64,
852        phase: f64,
853        plan: Arc<SelectedNativePlan>,
854    },
855    /// The run is a general `U` up to its decomposition's global phase.
856    U {
857        decomposition: crate::compile::transform::decompose::unitary::OneQubitUnitaryDecomposition,
858        plan: Arc<SelectedNativePlan>,
859    },
860}
861
862/// Planner data needed to decide whether one fused realization is admissible.
863struct FusionPlanInfo {
864    plan: Arc<SelectedNativePlan>,
865    cost: DevicePhysicalCost,
866    leaf_count: usize,
867}
868
869/// Returns whether a non-identity fused realization satisfies lowering's
870/// production monotonicity contract.
871fn 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
880/// How an operation bounds the pending one-qubit buffers.
881enum BoundaryQubits {
882    /// Flush only the listed qubits.
883    Touched(Vec<Qubit>),
884    /// Flush every buffered qubit.
885    All,
886}
887
888/// Classifies the flush scope of a non-bufferable operation.
889///
890/// Gate-like operations flush exactly their own qubits (operations on disjoint
891/// qubits commute across them). Barriers flush their scope, with an empty
892/// scope meaning a global barrier. Structured control flow and qubit-less
893/// operations (including pure classical stores) are treated conservatively as
894/// full flushes.
895fn 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
905/// Returns the qubit of a native one-qubit leaf eligible for run buffering:
906/// a standard one-qubit gate, unlabeled, with all parameters fixed numbers.
907fn 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
923/// Shared structural eligibility for source scanning and native-leaf buffering.
924fn 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
934/// Returns the fixed angle of a one-parameter `RZ` operation.
935fn 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
966/// Returns whether the operation is `X2P` (true) or `X2M` (false).
967fn 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
982/// Root gate states discovered in one circuit plus whether any `GPhase`
983/// operation (which lowering always folds) was seen, plus the qubits that
984/// carry at least one gate-like operation.
985struct 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
1034/// Detects any source one-qubit run that fused emission may improve.
1035///
1036/// This mirrors the pending buffer's adjacency semantics: operations on other
1037/// qubits do not separate a run, operations touching the qubit do, and global
1038/// boundaries flush every run. Each structured-control body is scanned with
1039/// fresh state so mutually exclusive branches can never form a false run.
1040fn 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, &params);
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;