Skip to main content

cqlib_core/compile/transform/
native_optimization.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. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Exact physical optimization after device instruction lowering.
14//!
15//! [`NativeOptimizer`] closes a bounded loop over independent stage branches.
16//! Every round evaluates `baseline -> A`, `baseline -> B -> C`, and
17//! `A -> B -> C`, where A/C are local one-qubit optimization and B is
18//! exact-physical two-qubit resynthesis. Each legalized stage is a returnable
19//! quality checkpoint. A non-improving intermediate may feed its dependent
20//! stage inside one branch, but it cannot replace the round cursor or suppress
21//! a sibling branch. Every transform recursively visits structured
22//! control-flow bodies.
23//!
24//! Candidate circuits are accepted under a Native-only quality policy. The
25//! balanced policy first protects the immutable entry circuit's entangler
26//! count/depth, total depth, calibrated error, and makespan in every
27//! control-flow scope; candidates inside that envelope are then ranked against
28//! the best returnable circuit. This avoids assigning arbitrary execution
29//! counts to conditional or loop bodies. The optimizer restores the best whole
30//! circuit seen; independently optimal control-flow bodies are never spliced
31//! together.
32//!
33//! One immutable exact-physical synthesis context is shared by resynthesis,
34//! local optimization, and scope costing for the lifetime of a single run. The
35//! catalog is rebuilt transactionally only when scope costing finds an
36//! unprepared root; a prepared-but-unsupported root remains a real failure.
37
38use crate::circuit::{
39    Circuit, ClassicalControlOp, Directive, Instruction, Operation, Parameter, ParameterValue,
40    Qubit, StandardGate, ValueClassicalControlOp, ValueControlBody, ValueInstruction,
41    ValueOperation, ValueSwitchCase,
42};
43use crate::compile::CompilerError;
44use crate::compile::device_planning::DevicePlanningSession;
45use crate::compile::device_planning::cost::{RobustDurationKey, RobustErrorKey};
46use crate::compile::numeric_matrix::one_qubit_run_matrix;
47use crate::compile::sabre::MetricAvailability;
48use crate::compile::transform::decompose::unitary::{
49    DeviceContextCostFailure, DeviceSynthesisPlacement, DeviceTwoQubitSynthesisContext,
50    OneQubitUnitaryDecomposition, synthesize_numeric_1q_unitary,
51};
52use crate::compile::transform::native_quality::{
53    NativeQualityPolicy, NativeQualityVector, NativeQualityViolation,
54};
55use crate::compile::transform::rebuild::{CircuitRebuildContext, ClassicalRemap};
56use crate::compile::transform::resynthesis::{
57    NativeResynthesisPolicy, NativeResynthesisSession, NativeWorksetStats,
58    TwoQubitBlockResynthesisConfig, resynthesize_two_qubit_blocks_incremental,
59};
60use crate::compile::transform::target_basis::{TargetBasisCost, TargetBasisCostModel};
61use crate::compile::transform::{
62    Canonicalizer, CircuitAnalysis, DeviceLowerer, RewriteEdits, TransformOutcome, Transformer,
63};
64use crate::device::Device;
65use smallvec::{SmallVec, smallvec};
66use std::borrow::Cow;
67use std::cmp::Ordering;
68use std::collections::{BTreeMap, HashMap, HashSet};
69use std::f64::consts::{FRAC_PI_2, FRAC_PI_4};
70use std::sync::Arc;
71
72const PHASE_EPS: f64 = 1e-12;
73
74struct NativeStageCandidate {
75    circuit: Arc<Circuit>,
76    costs: Vec<NativeQualityVector>,
77    context: DeviceTwoQubitSynthesisContext,
78    improves_best: bool,
79}
80
81enum NativeStageOutcome {
82    Unchanged,
83    Unavailable,
84    Candidate(NativeStageCandidate),
85}
86
87struct NativeBestCheckpoint<'a> {
88    circuit: &'a mut Arc<Circuit>,
89    costs: &'a mut Vec<NativeQualityVector>,
90    context: &'a mut DeviceTwoQubitSynthesisContext,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq)]
94pub struct NativeOptimizationSummary {
95    /// Number of native two-qubit operations across all control-flow scopes.
96    pub native_two_qubit_ops: u64,
97    /// Sum of native two-qubit depth across all control-flow scopes.
98    pub native_two_qubit_depth: u64,
99    /// Sum of total native depth across all control-flow scopes.
100    pub total_native_depth: u64,
101    /// Number of native operations across all control-flow scopes.
102    pub native_total_ops: u64,
103    /// Summed predicted log error, or `None` when calibration is unavailable.
104    pub predicted_log_error: Option<f64>,
105    /// Number of operations whose error metric was unavailable.
106    pub unavailable_error_count: u64,
107    /// Number of operations whose error metric was imputed.
108    pub imputed_error_count: u64,
109}
110
111/// Exact final quality for every control-flow scope in deterministic traversal
112/// order. Aggregate summaries remain useful diagnostics, but only this
113/// checkpoint is strong enough to authorize workflow-level candidate
114/// replacement.
115#[derive(Debug, Clone, PartialEq)]
116pub(crate) struct NativeExactQualityCheckpoint {
117    scopes: Vec<NativeQualityVector>,
118}
119
120impl NativeExactQualityCheckpoint {
121    fn new(scopes: Vec<NativeQualityVector>) -> Self {
122        Self { scopes }
123    }
124
125    /// Returns whether this checkpoint strictly Pareto-dominates `incumbent`
126    /// in every corresponding control-flow scope.
127    pub(crate) fn strictly_dominates(&self, incumbent: &Self) -> bool {
128        if self.scopes.len() != incumbent.scopes.len() {
129            return false;
130        }
131        let mut strict = false;
132        for (candidate, incumbent) in self.scopes.iter().zip(&incumbent.scopes) {
133            let Some(scope_strict) = candidate.exact_pareto_dominance(*incumbent) else {
134                return false;
135            };
136            strict |= scope_strict;
137        }
138        strict
139    }
140
141    /// Requires the full exact Pareto contract and a strict entangler-count or
142    /// entangler-depth improvement in at least one corresponding scope.
143    pub(crate) fn strictly_dominates_with_two_qubit_gain(&self, incumbent: &Self) -> bool {
144        if !self.strictly_dominates(incumbent) {
145            return false;
146        }
147        self.scopes
148            .iter()
149            .zip(&incumbent.scopes)
150            .any(|(candidate, incumbent)| {
151                candidate.physical.native_two_qubit_ops < incumbent.physical.native_two_qubit_ops
152                    || candidate.physical.native_two_qubit_depth
153                        < incumbent.physical.native_two_qubit_depth
154            })
155    }
156
157    /// Deterministic final SABRE-beam ordering. Lower is better.
158    pub(crate) fn compare_for_sabre_beam(&self, other: &Self) -> Ordering {
159        let left = self.summary();
160        let right = other.summary();
161        left.native_two_qubit_ops
162            .cmp(&right.native_two_qubit_ops)
163            .then_with(|| {
164                left.native_two_qubit_depth
165                    .cmp(&right.native_two_qubit_depth)
166            })
167            .then_with(|| left.total_native_depth.cmp(&right.total_native_depth))
168            .then_with(|| left.native_total_ops.cmp(&right.native_total_ops))
169            .then_with(|| {
170                aggregate_error(&self.scopes)
171                    .compare_by(aggregate_error(&other.scopes), RobustErrorKey::compare)
172            })
173            .then_with(|| {
174                aggregate_duration(&self.scopes).compare_by(
175                    aggregate_duration(&other.scopes),
176                    RobustDurationKey::compare,
177                )
178            })
179            .then_with(|| {
180                aggregate_makespan(&self.scopes)
181                    .compare_by(aggregate_makespan(&other.scopes), |left, right| {
182                        left.total_cmp(&right)
183                    })
184            })
185    }
186
187    pub(crate) fn summary(&self) -> NativeOptimizationSummary {
188        summarize_scope_costs(&self.scopes)
189    }
190}
191
192fn aggregate_error(scopes: &[NativeQualityVector]) -> MetricAvailability<RobustErrorKey> {
193    aggregate_optional_metric(
194        scopes.iter().map(|quality| quality.physical.error),
195        |left, right| left.combine(right),
196    )
197}
198
199fn aggregate_duration(scopes: &[NativeQualityVector]) -> MetricAvailability<RobustDurationKey> {
200    aggregate_optional_metric(
201        scopes.iter().map(|quality| quality.physical.duration),
202        |left, right| left.combine(right),
203    )
204}
205
206fn aggregate_makespan(scopes: &[NativeQualityVector]) -> MetricAvailability<f64> {
207    aggregate_optional_metric(
208        scopes.iter().map(|quality| quality.physical.makespan),
209        |left, right| left + right,
210    )
211}
212
213fn aggregate_optional_metric<T: Copy>(
214    metrics: impl IntoIterator<Item = MetricAvailability<T>>,
215    combine: impl Fn(T, T) -> T,
216) -> MetricAvailability<T> {
217    let mut aggregate = None;
218    for metric in metrics {
219        aggregate = Some(match (aggregate, metric) {
220            (None, metric) => metric,
221            (Some(MetricAvailability::Disabled), MetricAvailability::Disabled) => {
222                MetricAvailability::Disabled
223            }
224            (Some(MetricAvailability::Available(left)), MetricAvailability::Available(right)) => {
225                MetricAvailability::Available(combine(left, right))
226            }
227            (Some(MetricAvailability::Inconsistent), _)
228            | (_, MetricAvailability::Inconsistent)
229            | (Some(MetricAvailability::Disabled), MetricAvailability::Available(_))
230            | (Some(MetricAvailability::Available(_)), MetricAvailability::Disabled) => {
231                MetricAvailability::Inconsistent
232            }
233        });
234    }
235    aggregate.unwrap_or(MetricAvailability::Disabled)
236}
237
238/// Result of one bounded exact-physical native optimization run.
239#[derive(Debug, Clone)]
240pub struct NativeOptimizationResult {
241    /// Best whole-circuit point accepted by the optimizer.
242    pub circuit: Circuit,
243    /// Whether the returned circuit differs from the supplied input.
244    pub changed: bool,
245    /// Number of optimization rounds entered, including a terminal stable round.
246    pub rounds: u8,
247    /// Whether the last explored state was discarded in favor of an earlier
248    /// best returnable point.
249    pub restored_best: bool,
250    /// Exact physical cost summary at optimizer entry.
251    pub before: NativeOptimizationSummary,
252    /// Exact physical cost summary for the returned circuit.
253    pub after: NativeOptimizationSummary,
254}
255
256/// Bounded native optimization loop with minimum-point restoration.
257///
258/// Inputs must already be routed and lowered to exact native instructions for
259/// `device`. Most callers should use the compiler workflow; this lower-level
260/// entry point is intended for diagnostics and custom physical pipelines.
261pub struct NativeOptimizer<'a> {
262    device: Cow<'a, Device>,
263    planning_session: Arc<DevicePlanningSession>,
264    resynthesis: TwoQubitBlockResynthesisConfig,
265    max_rounds: u8,
266    max_stale_rounds: u8,
267    quality_policy: NativeQualityPolicy,
268}
269
270impl<'a> NativeOptimizer<'a> {
271    /// Production native-loop budget used by normal compilation.
272    pub const NORMAL_MAX_ROUNDS: u8 = 2;
273    /// Production stale-round budget used by normal compilation.
274    pub const NORMAL_MAX_STALE_ROUNDS: u8 = 1;
275    /// Production native-loop budget used by enhanced compilation.
276    pub const ENHANCED_MAX_ROUNDS: u8 = 8;
277    /// Production stale-round budget used by enhanced compilation.
278    pub const ENHANCED_MAX_STALE_ROUNDS: u8 = 3;
279
280    /// Creates a reusable optimizer borrowing `device`.
281    pub fn new(
282        device: &'a Device,
283        resynthesis: TwoQubitBlockResynthesisConfig,
284        max_rounds: u8,
285        max_stale_rounds: u8,
286    ) -> Result<Self, CompilerError> {
287        validate_native_optimization_budgets(max_rounds, max_stale_rounds)?;
288        Ok(Self {
289            device: Cow::Borrowed(device),
290            planning_session: Arc::new(DevicePlanningSession::new(device)),
291            resynthesis,
292            max_rounds,
293            max_stale_rounds,
294            quality_policy: NativeQualityPolicy::EntanglerFirst,
295        })
296    }
297
298    /// Creates a reusable optimizer owning an immutable device snapshot.
299    ///
300    /// This is useful for language bindings and long-lived optimizer objects:
301    /// repeated runs reuse the same run-local planning cache without requiring
302    /// a self-referential wrapper.
303    pub fn new_owned(
304        device: Device,
305        resynthesis: TwoQubitBlockResynthesisConfig,
306        max_rounds: u8,
307        max_stale_rounds: u8,
308    ) -> Result<NativeOptimizer<'static>, CompilerError> {
309        validate_native_optimization_budgets(max_rounds, max_stale_rounds)?;
310        let planning_session = Arc::new(DevicePlanningSession::new(&device));
311        Ok(NativeOptimizer {
312            device: Cow::Owned(device),
313            planning_session,
314            resynthesis,
315            max_rounds,
316            max_stale_rounds,
317            quality_policy: NativeQualityPolicy::EntanglerFirst,
318        })
319    }
320
321    /// Creates an optimizer with the exact budget used by normal compilation.
322    pub fn normal(device: &'a Device) -> Self {
323        Self::new(
324            device,
325            TwoQubitBlockResynthesisConfig::normal(Default::default()),
326            Self::NORMAL_MAX_ROUNDS,
327            Self::NORMAL_MAX_STALE_ROUNDS,
328        )
329        .expect("production native optimization budgets must be valid")
330    }
331
332    /// Creates an optimizer with the exact budget used by enhanced compilation.
333    pub fn enhanced(device: &'a Device) -> Self {
334        Self::new(
335            device,
336            TwoQubitBlockResynthesisConfig::enhanced(Default::default()),
337            Self::ENHANCED_MAX_ROUNDS,
338            Self::ENHANCED_MAX_STALE_ROUNDS,
339        )
340        .expect("production native optimization budgets must be valid")
341    }
342
343    /// Device snapshot costed and validated by this optimizer.
344    pub fn device(&self) -> &Device {
345        self.device.as_ref()
346    }
347
348    /// Two-qubit resynthesis configuration used in every round.
349    pub fn resynthesis(&self) -> &TwoQubitBlockResynthesisConfig {
350        &self.resynthesis
351    }
352
353    /// Maximum number of rounds entered by one run.
354    pub const fn max_rounds(&self) -> u8 {
355        self.max_rounds
356    }
357
358    /// Number of consecutive non-improving rounds allowed before stopping.
359    pub const fn max_stale_rounds(&self) -> u8 {
360        self.max_stale_rounds
361    }
362
363    /// Candidate quality policy used by this optimizer.
364    pub const fn quality_policy(&self) -> NativeQualityPolicy {
365        self.quality_policy
366    }
367
368    /// Selects the Native-only candidate quality policy.
369    pub fn with_quality_policy(mut self, quality_policy: NativeQualityPolicy) -> Self {
370        self.quality_policy = quality_policy;
371        self
372    }
373
374    pub(crate) fn with_session(
375        device: &'a Device,
376        resynthesis: TwoQubitBlockResynthesisConfig,
377        max_rounds: u8,
378        max_stale_rounds: u8,
379        planning_session: Arc<DevicePlanningSession>,
380    ) -> Self {
381        debug_assert!(max_rounds > 0);
382        debug_assert!(max_stale_rounds > 0);
383        Self {
384            device: Cow::Borrowed(device),
385            planning_session,
386            resynthesis,
387            max_rounds,
388            max_stale_rounds,
389            quality_policy: NativeQualityPolicy::EntanglerFirst,
390        }
391    }
392
393    pub fn run(&self, circuit: &Circuit) -> Result<NativeOptimizationResult, CompilerError> {
394        self.run_with_policy(circuit, NativeResynthesisPolicy::Incremental)
395            .map(|(result, _)| result)
396    }
397
398    pub(crate) fn run_with_exact_quality_and_stats(
399        &self,
400        circuit: &Circuit,
401    ) -> Result<
402        (
403            NativeOptimizationResult,
404            NativeExactQualityCheckpoint,
405            NativeWorksetStats,
406        ),
407        CompilerError,
408    > {
409        let initial = Canonicalizer::production()
410            .transform(circuit, None)?
411            .into_circuit(circuit);
412        self.run_canonicalized(circuit, initial, NativeResynthesisPolicy::Incremental)
413    }
414
415    pub(crate) fn run_with_proven_canonical_input_exact_quality_and_stats(
416        &self,
417        circuit: &Circuit,
418    ) -> Result<
419        (
420            NativeOptimizationResult,
421            NativeExactQualityCheckpoint,
422            NativeWorksetStats,
423        ),
424        CompilerError,
425    > {
426        self.run_canonicalized(
427            circuit,
428            circuit.clone(),
429            NativeResynthesisPolicy::Incremental,
430        )
431    }
432
433    pub(crate) fn run_with_policy(
434        &self,
435        circuit: &Circuit,
436        policy: NativeResynthesisPolicy,
437    ) -> Result<(NativeOptimizationResult, NativeWorksetStats), CompilerError> {
438        let initial = Canonicalizer::production()
439            .transform(circuit, None)?
440            .into_circuit(circuit);
441        self.run_canonicalized(circuit, initial, policy)
442            .map(|(result, _, stats)| (result, stats))
443    }
444
445    /// Runs from an input whose production-canonical postcondition has already
446    /// been established by this optimizer or its workflow caller.
447    fn run_canonicalized(
448        &self,
449        source: &Circuit,
450        initial: Circuit,
451        policy: NativeResynthesisPolicy,
452    ) -> Result<
453        (
454            NativeOptimizationResult,
455            NativeExactQualityCheckpoint,
456            NativeWorksetStats,
457        ),
458        CompilerError,
459    > {
460        self.device().validate_circuit(&initial)?;
461        // Native rounds can carry very large circuits. Share immutable states
462        // between the exploration cursor, the best returnable checkpoint, and
463        // the exact round-boundary cycle detector.
464        let initial = Arc::new(initial);
465        let mut current = initial.clone();
466        let mut best = initial;
467        // This exact-physical context is immutable and run-scoped. All consumers
468        // on the exploration path share its Arc-backed catalog until a candidate
469        // exposes missing coverage.
470        let mut context = DeviceTwoQubitSynthesisContext::build_with_session(
471            self.device(),
472            &current,
473            DeviceSynthesisPlacement::ExactPhysical,
474            Arc::clone(&self.planning_session),
475        )?;
476        let mut best_context = context.clone();
477        let mut best_costs = scope_costs_with_context(&best, &context).map_err(scope_cost_error)?;
478        let entry_costs = best_costs.clone();
479        let before = summarize_scope_costs(&best_costs);
480        let mut rounds = 0;
481        let mut stale = 0_u8;
482        // Incremental operation identity is branch-local. Exact synthesis
483        // artifacts remain run-scoped and are moved between these sessions
484        // immediately around the A->B call.
485        let mut baseline_session = NativeResynthesisSession::new(policy);
486        let mut after_local_session = NativeResynthesisSession::new(policy);
487        let mut seen_states = vec![current.clone()];
488
489        'optimization: while rounds < self.max_rounds && stale < self.max_stale_rounds {
490            rounds += 1;
491            let round_start = current.clone();
492            let round_context = context.clone();
493            let mut improved_in_round = false;
494
495            // A is generated from the immutable round baseline. Its legal
496            // result remains available to the dependent A->B->C branch even
497            // when the standalone A checkpoint is rejected.
498            let phase_a_branch = match OptimizeNativeLocalGates::with_quality_policy(
499                round_context.clone(),
500                self.quality_policy,
501            )
502            .transform(&round_start, None)?
503            {
504                TransformOutcome::Unchanged => None,
505                TransformOutcome::Changed(candidate) => match self.evaluate_stage_candidate(
506                    &round_start,
507                    Arc::new(candidate),
508                    &best_costs,
509                    &entry_costs,
510                    &round_context,
511                    &mut after_local_session,
512                )? {
513                    NativeStageOutcome::Candidate(candidate) => {
514                        let branch = (candidate.circuit.clone(), candidate.context.clone());
515                        improved_in_round |= install_best_checkpoint(
516                            &candidate,
517                            &mut best,
518                            &mut best_costs,
519                            &mut best_context,
520                        );
521                        Some(branch)
522                    }
523                    NativeStageOutcome::Unchanged | NativeStageOutcome::Unavailable => None,
524                },
525            };
526
527            // B/C from the immutable baseline is a sibling search path. It is
528            // deliberately evaluated even when A produced a legal candidate.
529            improved_in_round |= self.explore_resynthesis_branch(
530                &round_start,
531                &round_context,
532                NativeBestCheckpoint {
533                    circuit: &mut best,
534                    costs: &mut best_costs,
535                    context: &mut best_context,
536                },
537                &entry_costs,
538                &mut baseline_session,
539            )?;
540
541            if let Some((phase_a, phase_a_context)) = phase_a_branch {
542                // Only exact synthesis artifacts cross the branch boundary;
543                // incremental operation IDs, diffs, and worksets stay local.
544                baseline_session.swap_synthesis_cache(&mut after_local_session);
545                let branch_result = self.explore_resynthesis_branch(
546                    &phase_a,
547                    &phase_a_context,
548                    NativeBestCheckpoint {
549                        circuit: &mut best,
550                        costs: &mut best_costs,
551                        context: &mut best_context,
552                    },
553                    &entry_costs,
554                    &mut after_local_session,
555                );
556                baseline_session.swap_synthesis_cache(&mut after_local_session);
557                improved_in_round |= branch_result?;
558            }
559
560            // Only a quality-accepted whole-circuit checkpoint may seed the
561            // next round. Rejected A/B/C states have already served their
562            // bounded dependent edge and cannot contaminate later rounds.
563            current = best.clone();
564            context = best_context.clone();
565
566            if current.as_ref() == round_start.as_ref() {
567                break;
568            }
569
570            // Only round-boundary exploration states participate in cycle
571            // detection. The same circuit at different A/B/C phase positions
572            // does not imply the same remaining deterministic transition.
573            if let Some(seen) = seen_states
574                .iter()
575                .find(|seen| seen.as_ref() == current.as_ref())
576            {
577                current = seen.clone();
578                baseline_session.record_cycle_early_exit();
579                break 'optimization;
580            }
581            seen_states.push(current.clone());
582
583            if improved_in_round {
584                stale = 0;
585            } else {
586                stale = stale.saturating_add(1);
587            }
588        }
589
590        let restored_best = current.as_ref() != best.as_ref();
591        let after = summarize_scope_costs(&best_costs);
592        let exact_quality = NativeExactQualityCheckpoint::new(best_costs);
593        drop(current);
594        drop(seen_states);
595        let best = Arc::try_unwrap(best).unwrap_or_else(|shared| shared.as_ref().clone());
596        let result = NativeOptimizationResult {
597            changed: best != *source,
598            circuit: best,
599            rounds,
600            restored_best,
601            before,
602            after,
603        };
604        let mut stats = baseline_session.stats();
605        stats.merge_workset_from(after_local_session.stats());
606        Ok((result, exact_quality, stats))
607    }
608
609    /// Evaluates one detached `B -> C` branch. B and C are separate returnable
610    /// checkpoints, while raw B remains available to C even when B itself is
611    /// not lowerable or does not improve the whole-circuit quality point.
612    fn explore_resynthesis_branch(
613        &self,
614        branch_start: &Arc<Circuit>,
615        branch_context: &DeviceTwoQubitSynthesisContext,
616        best: NativeBestCheckpoint<'_>,
617        entry_costs: &[NativeQualityVector],
618        session: &mut NativeResynthesisSession,
619    ) -> Result<bool, CompilerError> {
620        let resynthesis_outcome = resynthesize_two_qubit_blocks_incremental(
621            branch_start,
622            self.resynthesis.clone(),
623            branch_context.clone(),
624            session,
625            self.quality_policy,
626        )?;
627        let TransformOutcome::Changed(raw_phase_b) = resynthesis_outcome else {
628            return Ok(false);
629        };
630        let raw_phase_b = Arc::new(raw_phase_b);
631        let phase_b_outcome = self.evaluate_stage_candidate(
632            branch_start,
633            raw_phase_b.clone(),
634            best.costs,
635            entry_costs,
636            branch_context,
637            session,
638        )?;
639        let cleanup_context = match phase_b_outcome {
640            NativeStageOutcome::Candidate(candidate) => {
641                let context = candidate.context.clone();
642                let improved =
643                    install_best_checkpoint(&candidate, best.circuit, best.costs, best.context);
644                (context, improved)
645            }
646            NativeStageOutcome::Unchanged | NativeStageOutcome::Unavailable => {
647                (branch_context.clone(), false)
648            }
649        };
650        let (cleanup_context, mut improved) = cleanup_context;
651
652        // C follows generated B, not accepted B. This preserves the useful
653        // coupled edge without binding B's acceptance to C's acceptance.
654        if let TransformOutcome::Changed(cleaned) =
655            OptimizeNativeLocalGates::with_quality_policy(cleanup_context, self.quality_policy)
656                .transform(&raw_phase_b, None)?
657            && let NativeStageOutcome::Candidate(candidate) = self.evaluate_stage_candidate(
658                branch_start,
659                Arc::new(cleaned),
660                best.costs,
661                entry_costs,
662                branch_context,
663                session,
664            )?
665        {
666            improved |= install_best_checkpoint(&candidate, best.circuit, best.costs, best.context);
667        }
668        Ok(improved)
669    }
670
671    /// Legalizes, canonicalizes, validates, and scores one detached stage
672    /// candidate. A quality failure prevents only installation as `best`; the
673    /// legal candidate and its costing context remain available for bounded
674    /// exploration by a dependent stage.
675    fn evaluate_stage_candidate(
676        &self,
677        baseline: &Arc<Circuit>,
678        generated: Arc<Circuit>,
679        best_costs: &[NativeQualityVector],
680        entry_costs: &[NativeQualityVector],
681        context: &DeviceTwoQubitSynthesisContext,
682        resynthesis_session: &mut NativeResynthesisSession,
683    ) -> Result<NativeStageOutcome, CompilerError> {
684        let legalized = match DeviceLowerer::with_session(self.device(), &self.planning_session)
685            .transform(&generated, None)
686        {
687            Ok(TransformOutcome::Unchanged) => generated,
688            Ok(TransformOutcome::Changed(legalized)) => Arc::new(legalized),
689            // A standalone checkpoint may be unavailable even though a later
690            // cleanup of the raw generated circuit can still be lowerable.
691            Err(CompilerError::DeviceLoweringFailed(_)) => {
692                return Ok(NativeStageOutcome::Unavailable);
693            }
694            Err(error) => return Err(error),
695        };
696        let candidate = match Canonicalizer::production().transform(&legalized, None)? {
697            TransformOutcome::Unchanged => legalized,
698            TransformOutcome::Changed(candidate) => Arc::new(candidate),
699        };
700        if candidate.as_ref() == baseline.as_ref() {
701            return Ok(NativeStageOutcome::Unchanged);
702        }
703
704        // A non-improving candidate may become the next exploration state, so
705        // validation is mandatory before returning it in every build profile.
706        self.device().validate_circuit(&candidate)?;
707        let mut candidate_context = context.clone();
708        let candidate_costs =
709            self.candidate_costs_with_reuse(&candidate, &mut candidate_context)?;
710        let improves_best = match scope_quality_decision(
711            &candidate_costs,
712            best_costs,
713            entry_costs,
714            self.quality_policy,
715        ) {
716            Ok(()) => true,
717            Err(violation) => {
718                resynthesis_session.record_quality_rejection(violation);
719                false
720            }
721        };
722        Ok(NativeStageOutcome::Candidate(NativeStageCandidate {
723            circuit: candidate,
724            costs: candidate_costs,
725            context: candidate_context,
726            improves_best,
727        }))
728    }
729
730    /// Costs a candidate with the run-scoped context, rebuilding transactionally
731    /// at most once when (and only when) the catalog did not prepare a required root.
732    fn candidate_costs_with_reuse(
733        &self,
734        candidate: &Circuit,
735        context: &mut DeviceTwoQubitSynthesisContext,
736    ) -> Result<Vec<NativeQualityVector>, CompilerError> {
737        match scope_costs_with_context(candidate, context) {
738            Ok(costs) => Ok(costs),
739            Err(ScopeCostError::Context(DeviceContextCostFailure::Unprepared(_))) => {
740                let rebuilt = DeviceTwoQubitSynthesisContext::build_with_session(
741                    self.device(),
742                    candidate,
743                    DeviceSynthesisPlacement::ExactPhysical,
744                    Arc::clone(&self.planning_session),
745                )?;
746                let costs = match scope_costs_with_context(candidate, &rebuilt) {
747                    Ok(costs) => costs,
748                    Err(ScopeCostError::Context(DeviceContextCostFailure::Unprepared(state))) => {
749                        return Err(CompilerError::InvariantViolation(format!(
750                            "rebuilt native optimizer context was not prepared for {state:?}"
751                        )));
752                    }
753                    Err(error) => return Err(scope_cost_error(error)),
754                };
755                *context = rebuilt;
756                Ok(costs)
757            }
758            Err(error) => Err(scope_cost_error(error)),
759        }
760    }
761}
762
763fn install_best_checkpoint(
764    candidate: &NativeStageCandidate,
765    best: &mut Arc<Circuit>,
766    best_costs: &mut Vec<NativeQualityVector>,
767    best_context: &mut DeviceTwoQubitSynthesisContext,
768) -> bool {
769    if !candidate.improves_best {
770        return false;
771    }
772    *best = candidate.circuit.clone();
773    best_costs.clone_from(&candidate.costs);
774    *best_context = candidate.context.clone();
775    true
776}
777
778fn validate_native_optimization_budgets(
779    max_rounds: u8,
780    max_stale_rounds: u8,
781) -> Result<(), CompilerError> {
782    if max_rounds == 0 {
783        return Err(CompilerError::InvalidInput(
784            "native optimizer max_rounds must be greater than zero".to_string(),
785        ));
786    }
787    if max_stale_rounds == 0 {
788        return Err(CompilerError::InvalidInput(
789            "native optimizer max_stale_rounds must be greater than zero".to_string(),
790        ));
791    }
792    Ok(())
793}
794
795#[derive(Debug)]
796enum ScopeCostError {
797    Compiler(CompilerError),
798    Context(DeviceContextCostFailure),
799}
800
801impl From<CompilerError> for ScopeCostError {
802    fn from(error: CompilerError) -> Self {
803        Self::Compiler(error)
804    }
805}
806
807fn scope_cost_error(error: ScopeCostError) -> CompilerError {
808    match error {
809        ScopeCostError::Compiler(error) => error,
810        ScopeCostError::Context(_) => CompilerError::InvariantViolation(
811            "native optimizer could not cost a legalized control-flow scope".to_string(),
812        ),
813    }
814}
815
816fn scope_costs_with_context(
817    circuit: &Circuit,
818    context: &DeviceTwoQubitSynthesisContext,
819) -> Result<Vec<NativeQualityVector>, ScopeCostError> {
820    let mut costs = Vec::new();
821    collect_scope_costs(circuit.operations(), context, &mut costs)?;
822    Ok(costs)
823}
824
825fn collect_scope_costs(
826    operations: &[Operation],
827    context: &DeviceTwoQubitSynthesisContext,
828    output: &mut Vec<NativeQualityVector>,
829) -> Result<(), ScopeCostError> {
830    let mut accumulator = context
831        .exact_sequence_cost_accumulator()
832        .map_err(ScopeCostError::Context)?;
833    for operation in operations {
834        match &operation.instruction {
835            Instruction::Standard(_) | Instruction::McGate(_) => accumulator
836                .add_gate(&operation.instruction, &operation.qubits)
837                .map_err(ScopeCostError::Context)?,
838            Instruction::ClassicalControl(control) => {
839                for body in control.bodies() {
840                    collect_scope_costs(body.operations(), context, output)?;
841                }
842            }
843            Instruction::UnitaryGate(_)
844            | Instruction::CircuitGate(_)
845            | Instruction::ClassicalData(_)
846            | Instruction::Directive(_)
847            | Instruction::Delay => {}
848        }
849    }
850    output.push(NativeQualityVector::for_operations(
851        accumulator.finish(),
852        operations,
853    ));
854    Ok(())
855}
856
857fn scope_quality_decision(
858    candidate: &[NativeQualityVector],
859    current: &[NativeQualityVector],
860    entry: &[NativeQualityVector],
861    policy: NativeQualityPolicy,
862) -> Result<(), NativeQualityViolation> {
863    if candidate.len() != current.len() || candidate.len() != entry.len() {
864        return Err(NativeQualityViolation::ScopeShape);
865    }
866    let mut improved = false;
867    for ((candidate, current), entry) in candidate.iter().zip(current).zip(entry) {
868        if let Some(violation) = candidate.admissibility_violation_against(*entry, policy) {
869            return Err(violation);
870        }
871        match candidate.compare(*current, policy) {
872            std::cmp::Ordering::Less => improved = true,
873            std::cmp::Ordering::Equal => {}
874            std::cmp::Ordering::Greater => return Err(NativeQualityViolation::Rank),
875        }
876    }
877    if improved {
878        Ok(())
879    } else {
880        Err(NativeQualityViolation::Rank)
881    }
882}
883
884fn summarize_scope_costs(costs: &[NativeQualityVector]) -> NativeOptimizationSummary {
885    let mut predicted_log_error = Some(0.0);
886    let mut unavailable_error_count = 0;
887    let mut imputed_error_count = 0;
888    for quality in costs {
889        let cost = quality.physical;
890        match cost.error {
891            MetricAvailability::Available(error) => {
892                if let Some(total) = &mut predicted_log_error {
893                    *total += error.log_error;
894                }
895                unavailable_error_count += u64::from(error.unavailable_count);
896                imputed_error_count += u64::from(error.imputed_count);
897            }
898            MetricAvailability::Disabled | MetricAvailability::Inconsistent => {
899                predicted_log_error = None;
900            }
901        }
902    }
903    NativeOptimizationSummary {
904        native_two_qubit_ops: costs
905            .iter()
906            .map(|quality| u64::from(quality.physical.native_two_qubit_ops))
907            .sum(),
908        native_two_qubit_depth: costs
909            .iter()
910            .map(|quality| u64::from(quality.physical.native_two_qubit_depth))
911            .sum(),
912        total_native_depth: costs
913            .iter()
914            .map(|quality| u64::from(quality.physical.total_native_depth))
915            .sum(),
916        native_total_ops: costs
917            .iter()
918            .map(|quality| u64::from(quality.physical.native_total_ops))
919            .sum(),
920        predicted_log_error,
921        unavailable_error_count,
922        imputed_error_count,
923    }
924}
925
926/// Performs target-costed one-qubit fusion and exact frame propagation.
927#[derive(Debug, Clone)]
928pub(crate) struct OptimizeNativeLocalGates {
929    device_context: DeviceTwoQubitSynthesisContext,
930    quality_policy: NativeQualityPolicy,
931}
932
933impl OptimizeNativeLocalGates {
934    pub(crate) fn with_quality_policy(
935        device_context: DeviceTwoQubitSynthesisContext,
936        quality_policy: NativeQualityPolicy,
937    ) -> Self {
938        Self {
939            device_context,
940            quality_policy,
941        }
942    }
943}
944
945impl Transformer for OptimizeNativeLocalGates {
946    fn name(&self) -> &'static str {
947        "optimize.native_local_gates"
948    }
949
950    fn transform(
951        &self,
952        circuit: &Circuit,
953        _analysis: Option<&CircuitAnalysis>,
954    ) -> Result<TransformOutcome, CompilerError> {
955        let policy = LocalOptimizationPolicy::Device {
956            context: self.device_context.clone(),
957            quality_policy: self.quality_policy,
958        };
959        LocalOneQPass::run(circuit, &policy)
960    }
961}
962
963/// Cost policy used by the shared one-qubit/frame optimization engine.
964#[derive(Debug, Clone)]
965pub(crate) enum LocalOptimizationPolicy {
966    Logical,
967    Basis(Arc<TargetBasisCostModel>),
968    Device {
969        context: DeviceTwoQubitSynthesisContext,
970        quality_policy: NativeQualityPolicy,
971    },
972}
973
974/// Runs the shared one-qubit/frame optimizer with an explicit cost policy.
975pub(crate) fn optimize_one_qubit_runs_with_policy(
976    circuit: &Circuit,
977    policy: &LocalOptimizationPolicy,
978) -> Result<TransformOutcome, CompilerError> {
979    LocalOneQPass::run(circuit, policy)
980}
981
982pub(crate) fn optimize_one_qubit_runs_with_policy_and_edits(
983    circuit: &Circuit,
984    policy: &LocalOptimizationPolicy,
985) -> Result<(TransformOutcome, RewriteEdits), CompilerError> {
986    LocalOneQPass::run_with_rewrite_edits(circuit, policy)
987}
988
989struct LocalOneQPass<'source, 'policy> {
990    source: &'source Circuit,
991    policy: &'policy LocalOptimizationPolicy,
992    rebuild: CircuitRebuildContext,
993}
994
995struct SequenceRewrite {
996    operations: Vec<ValueOperation>,
997    /// Output-to-input correspondence for the current sequence. Rebuilt or
998    /// generated operations are `None`; unchanged operations retain their
999    /// source order.
1000    provenance: Option<Vec<Option<usize>>>,
1001    phase_delta: f64,
1002    changed: bool,
1003}
1004
1005impl<'source, 'policy> LocalOneQPass<'source, 'policy> {
1006    fn run(
1007        source: &'source Circuit,
1008        policy: &'policy LocalOptimizationPolicy,
1009    ) -> Result<TransformOutcome, CompilerError> {
1010        Self::run_internal(source, policy, false).map(|(outcome, _)| outcome)
1011    }
1012
1013    fn run_with_rewrite_edits(
1014        source: &'source Circuit,
1015        policy: &'policy LocalOptimizationPolicy,
1016    ) -> Result<(TransformOutcome, RewriteEdits), CompilerError> {
1017        let (outcome, edits) = Self::run_internal(source, policy, true)?;
1018        Ok((
1019            outcome,
1020            edits.expect("rewrite edits were requested for the one-qubit pass"),
1021        ))
1022    }
1023
1024    fn run_internal(
1025        source: &'source Circuit,
1026        policy: &'policy LocalOptimizationPolicy,
1027        track_rewrite_edits: bool,
1028    ) -> Result<(TransformOutcome, Option<RewriteEdits>), CompilerError> {
1029        let rebuild = CircuitRebuildContext::new(source);
1030        let root_classical = rebuild.root_classical().clone();
1031        let mut pass = Self {
1032            source,
1033            policy,
1034            rebuild,
1035        };
1036        let rewrite =
1037            pass.process_sequence(source.operations(), &root_classical, track_rewrite_edits)?;
1038        if !rewrite.changed {
1039            return Ok((
1040                TransformOutcome::Unchanged,
1041                track_rewrite_edits.then(|| {
1042                    RewriteEdits::linear(
1043                        source.operations().len(),
1044                        source.operations().len(),
1045                        Vec::new(),
1046                    )
1047                }),
1048            ));
1049        }
1050        debug_assert!(
1051            rewrite
1052                .provenance
1053                .as_ref()
1054                .is_none_or(|provenance| rewrite.operations.len() == provenance.len())
1055        );
1056        let edits = rewrite.provenance.as_ref().map(|provenance| {
1057            RewriteEdits::from_operation_provenance(source.operations().len(), provenance)
1058        });
1059        let mut global_phase = source.global_phase();
1060        if rewrite.phase_delta.abs() > PHASE_EPS {
1061            global_phase = global_phase + Parameter::from(rewrite.phase_delta);
1062        }
1063        let circuit = pass
1064            .rebuild
1065            .finish(source.qubits(), rewrite.operations, global_phase)?;
1066        Ok((TransformOutcome::Changed(circuit), edits))
1067    }
1068
1069    fn process_sequence(
1070        &mut self,
1071        operations: &[Operation],
1072        classical_remap: &ClassicalRemap,
1073        track_provenance: bool,
1074    ) -> Result<SequenceRewrite, CompilerError> {
1075        let mut values = Vec::with_capacity(operations.len());
1076        let mut provenance = track_provenance.then(|| Vec::with_capacity(operations.len()));
1077        let mut nested_changed = false;
1078        for (order, operation) in operations.iter().enumerate() {
1079            if let Instruction::ClassicalControl(control) = &operation.instruction {
1080                let (instruction, changed) = self.rebuild_control_flow(control, classical_remap)?;
1081                values.push(ValueOperation {
1082                    qubits: instruction.used_qubits().into_iter().collect(),
1083                    instruction: ValueInstruction::ClassicalControl(instruction),
1084                    params: CircuitRebuildContext::resolve_source_params(
1085                        self.source,
1086                        &operation.params,
1087                    )?,
1088                    label: operation.label.clone(),
1089                });
1090                if let Some(provenance) = &mut provenance {
1091                    provenance.push((!changed).then_some(order));
1092                }
1093                nested_changed |= changed;
1094            } else {
1095                values.push(self.rebuild.remap_preserved_operation(
1096                    self.source,
1097                    operation,
1098                    classical_remap,
1099                )?);
1100                if let Some(provenance) = &mut provenance {
1101                    provenance.push(Some(order));
1102                }
1103            }
1104        }
1105
1106        let optimized = match self.policy {
1107            LocalOptimizationPolicy::Device { .. } => {
1108                // Preserve the existing native behavior: frame movement is
1109                // speculative within a native round, while the outer minimum
1110                // point controller decides whether the whole round survives.
1111                let framed = propagate_frames(values, provenance)?;
1112                let fused = fuse_one_qubit_runs(framed.operations, framed.provenance, self.policy)?;
1113                ValueRewrite {
1114                    operations: fused.operations,
1115                    provenance: fused.provenance,
1116                    phase_delta: framed.phase_delta + fused.phase_delta,
1117                    changed: framed.changed || fused.changed,
1118                }
1119            }
1120            LocalOptimizationPolicy::Logical | LocalOptimizationPolicy::Basis(_) => {
1121                optimize_transactional(values, provenance, self.policy)?
1122            }
1123        };
1124        Ok(SequenceRewrite {
1125            operations: optimized.operations,
1126            provenance: optimized.provenance,
1127            phase_delta: optimized.phase_delta,
1128            changed: nested_changed || optimized.changed,
1129        })
1130    }
1131
1132    fn rebuild_body(
1133        &mut self,
1134        operations: &[Operation],
1135        classical_remap: &ClassicalRemap,
1136    ) -> Result<(ValueControlBody, bool), CompilerError> {
1137        let mut rewrite = self.process_sequence(operations, classical_remap, false)?;
1138        if rewrite.phase_delta.abs() > PHASE_EPS {
1139            rewrite.operations.insert(
1140                0,
1141                ValueOperation {
1142                    instruction: ValueInstruction::from_instruction(Instruction::Standard(
1143                        StandardGate::GPhase,
1144                    )),
1145                    qubits: SmallVec::new(),
1146                    params: smallvec![ParameterValue::Fixed(rewrite.phase_delta)],
1147                    label: None,
1148                },
1149            );
1150            rewrite.changed = true;
1151        }
1152        Ok((ValueControlBody::new(rewrite.operations), rewrite.changed))
1153    }
1154
1155    fn rebuild_control_flow(
1156        &mut self,
1157        control: &ClassicalControlOp,
1158        classical_remap: &ClassicalRemap,
1159    ) -> Result<(ValueClassicalControlOp, bool), CompilerError> {
1160        Ok(match control {
1161            ClassicalControlOp::If(op) => {
1162                let (then_body, then_changed) =
1163                    self.rebuild_body(op.then_body().operations(), classical_remap)?;
1164                let else_rewrite = op
1165                    .else_body()
1166                    .map(|body| self.rebuild_body(body.operations(), classical_remap))
1167                    .transpose()?;
1168                let else_changed = else_rewrite.as_ref().is_some_and(|(_, changed)| *changed);
1169                (
1170                    ValueClassicalControlOp::If {
1171                        condition: classical_remap.remap_expr(op.condition())?,
1172                        then_body,
1173                        else_body: else_rewrite.map(|(body, _)| body),
1174                    },
1175                    then_changed || else_changed,
1176                )
1177            }
1178            ClassicalControlOp::While(op) => {
1179                let (body, changed) = self.rebuild_body(op.body().operations(), classical_remap)?;
1180                (
1181                    ValueClassicalControlOp::While {
1182                        condition: classical_remap.remap_expr(op.condition())?,
1183                        body,
1184                    },
1185                    changed,
1186                )
1187            }
1188            ClassicalControlOp::For(op) => {
1189                let (body, changed) = self.rebuild_body(op.body().operations(), classical_remap)?;
1190                (
1191                    ValueClassicalControlOp::For {
1192                        var: classical_remap.remap_var(op.var())?,
1193                        start: classical_remap.remap_expr(op.start())?,
1194                        stop: classical_remap.remap_expr(op.stop())?,
1195                        step: classical_remap.remap_expr(op.step())?,
1196                        body,
1197                    },
1198                    changed,
1199                )
1200            }
1201            ClassicalControlOp::Switch(op) => {
1202                let mut changed = false;
1203                let cases = op
1204                    .cases()
1205                    .iter()
1206                    .map(|case| {
1207                        let (body, body_changed) =
1208                            self.rebuild_body(case.body().operations(), classical_remap)?;
1209                        changed |= body_changed;
1210                        Ok(ValueSwitchCase::new(case.value(), body))
1211                    })
1212                    .collect::<Result<Vec<_>, CompilerError>>()?;
1213                let default_rewrite = op
1214                    .default()
1215                    .map(|body| self.rebuild_body(body.operations(), classical_remap))
1216                    .transpose()?;
1217                changed |= default_rewrite
1218                    .as_ref()
1219                    .is_some_and(|(_, body_changed)| *body_changed);
1220                (
1221                    ValueClassicalControlOp::Switch {
1222                        target: classical_remap.remap_expr(op.target())?,
1223                        cases,
1224                        default: default_rewrite.map(|(body, _)| body),
1225                    },
1226                    changed,
1227                )
1228            }
1229            ClassicalControlOp::Break => (ValueClassicalControlOp::Break, false),
1230            ClassicalControlOp::Continue => (ValueClassicalControlOp::Continue, false),
1231        })
1232    }
1233}
1234
1235struct ValueRewrite {
1236    operations: Vec<ValueOperation>,
1237    provenance: Option<Vec<Option<usize>>>,
1238    phase_delta: f64,
1239    changed: bool,
1240}
1241
1242#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
1243struct LogicalOneQCost {
1244    one_qubit_ops: usize,
1245    affected_region_depth: usize,
1246    total_gate_ops: usize,
1247}
1248
1249impl LocalOptimizationPolicy {
1250    fn strictly_better(
1251        &self,
1252        candidate: &[ValueOperation],
1253        source: &[ValueOperation],
1254    ) -> Result<bool, CompilerError> {
1255        Ok(match self {
1256            Self::Logical => logical_one_qubit_cost(candidate) < logical_one_qubit_cost(source),
1257            Self::Basis(model) => {
1258                let Some(before) = basis_cost(model, source)? else {
1259                    return Ok(false);
1260                };
1261                let Some(after) = basis_cost(model, candidate)? else {
1262                    return Ok(false);
1263                };
1264                after.two_qubit_ops <= before.two_qubit_ops
1265                    && compare_basis_cost(after, before).is_lt()
1266            }
1267            Self::Device {
1268                context,
1269                quality_policy,
1270            } => {
1271                let Some(before) = exact_local_sequence_cost(context, source, "source")? else {
1272                    return Ok(false);
1273                };
1274                let Some(after) = exact_local_sequence_cost(context, candidate, "candidate")?
1275                else {
1276                    return Ok(false);
1277                };
1278                let before = NativeQualityVector::for_value_operations(before, source);
1279                let after = NativeQualityVector::for_value_operations(after, candidate);
1280                after.admissible_against(before, *quality_policy)
1281                    && after.compare(before, *quality_policy).is_lt()
1282            }
1283        })
1284    }
1285}
1286
1287fn exact_local_sequence_cost(
1288    context: &DeviceTwoQubitSynthesisContext,
1289    operations: &[ValueOperation],
1290    role: &str,
1291) -> Result<Option<crate::compile::transform::decompose::unitary::DevicePhysicalCost>, CompilerError>
1292{
1293    match context.exact_sequence_cost_diagnostic(operations) {
1294        Ok(cost) => Ok(Some(cost)),
1295        Err(DeviceContextCostFailure::Unsupported(failure)) => {
1296            let _ = failure;
1297            Ok(None)
1298        }
1299        Err(DeviceContextCostFailure::Unprepared(state)) => {
1300            Err(CompilerError::InvariantViolation(format!(
1301                "native local optimization context was not prepared for {role} state {state:?}"
1302            )))
1303        }
1304        Err(DeviceContextCostFailure::WrongPlacement) => Err(CompilerError::InvariantViolation(
1305            "native local optimization requires an exact-physical context".to_string(),
1306        )),
1307        Err(DeviceContextCostFailure::InvalidOperation(reason)) => {
1308            Err(CompilerError::InvariantViolation(format!(
1309                "invalid native local optimization {role}: {reason}"
1310            )))
1311        }
1312    }
1313}
1314
1315fn optimize_transactional(
1316    operations: Vec<ValueOperation>,
1317    provenance: Option<Vec<Option<usize>>>,
1318    policy: &LocalOptimizationPolicy,
1319) -> Result<ValueRewrite, CompilerError> {
1320    debug_assert!(
1321        provenance
1322            .as_ref()
1323            .is_none_or(|provenance| operations.len() == provenance.len())
1324    );
1325    let framed = propagate_frames(operations.clone(), provenance.clone())?;
1326    let fused_after_frames = fuse_one_qubit_runs(framed.operations, framed.provenance, policy)?;
1327    let combined_phase = framed.phase_delta + fused_after_frames.phase_delta;
1328    let combined_changed = framed.changed || fused_after_frames.changed;
1329    if combined_changed && policy.strictly_better(&fused_after_frames.operations, &operations)? {
1330        return Ok(ValueRewrite {
1331            operations: fused_after_frames.operations,
1332            provenance: fused_after_frames.provenance,
1333            phase_delta: combined_phase,
1334            changed: true,
1335        });
1336    }
1337
1338    // A neutral or harmful frame movement must not hide an independently
1339    // useful one-qubit fusion on the original sequence.
1340    fuse_one_qubit_runs(operations, provenance, policy)
1341}
1342
1343fn logical_one_qubit_cost(operations: &[ValueOperation]) -> LogicalOneQCost {
1344    let mut depths = HashMap::<Qubit, usize>::new();
1345    let mut cost = LogicalOneQCost::default();
1346    for operation in operations {
1347        let ValueInstruction::Instruction(Instruction::Standard(gate)) = operation.instruction
1348        else {
1349            continue;
1350        };
1351        if gate == StandardGate::GPhase {
1352            continue;
1353        }
1354        cost.total_gate_ops += 1;
1355        if gate.num_qubits() == 1 && operation.qubits.len() == 1 {
1356            cost.one_qubit_ops += 1;
1357        }
1358        if operation.qubits.is_empty() {
1359            continue;
1360        }
1361        let next = operation
1362            .qubits
1363            .iter()
1364            .filter_map(|qubit| depths.get(qubit))
1365            .max()
1366            .copied()
1367            .unwrap_or(0)
1368            + 1;
1369        for &qubit in &operation.qubits {
1370            depths.insert(qubit, next);
1371        }
1372        cost.affected_region_depth = cost.affected_region_depth.max(next);
1373    }
1374    cost
1375}
1376
1377fn basis_cost(
1378    model: &TargetBasisCostModel,
1379    operations: &[ValueOperation],
1380) -> Result<Option<TargetBasisCost>, CompilerError> {
1381    let operations = operations
1382        .iter()
1383        .filter(|operation| {
1384            matches!(
1385                operation.instruction,
1386                ValueInstruction::Instruction(Instruction::Standard(_))
1387            ) && operation.params.iter().all(
1388                |parameter| matches!(parameter, ParameterValue::Fixed(value) if value.is_finite()),
1389            )
1390        })
1391        .cloned()
1392        .collect::<Vec<_>>();
1393    if operations.is_empty() {
1394        return Ok(Some(TargetBasisCost::default()));
1395    }
1396    let mut qubits = operations
1397        .iter()
1398        .flat_map(|operation| operation.qubits.iter().copied())
1399        .collect::<Vec<_>>();
1400    qubits.sort_by_key(|qubit| qubit.index());
1401    qubits.dedup();
1402    match model.cost_of_fixed_operations(qubits, operations) {
1403        Ok(cost) => Ok(Some(cost)),
1404        Err(CompilerError::InvalidInput(_)) | Err(CompilerError::TransformFailed { .. }) => {
1405            Ok(None)
1406        }
1407        Err(error) => Err(error),
1408    }
1409}
1410
1411fn compare_basis_cost(left: TargetBasisCost, right: TargetBasisCost) -> std::cmp::Ordering {
1412    left.two_qubit_ops
1413        .cmp(&right.two_qubit_ops)
1414        .then_with(|| left.depth.cmp(&right.depth))
1415        .then_with(|| left.total_ops.cmp(&right.total_ops))
1416        .then_with(|| left.parameterized_ops.cmp(&right.parameterized_ops))
1417}
1418
1419/// Replaces fixed numeric 1Q runs only when the synthesized unitary has a
1420/// strictly better exact-physical device cost than the original run.
1421fn fuse_one_qubit_runs(
1422    operations: Vec<ValueOperation>,
1423    provenance: Option<Vec<Option<usize>>>,
1424    policy: &LocalOptimizationPolicy,
1425) -> Result<ValueRewrite, CompilerError> {
1426    debug_assert!(
1427        provenance
1428            .as_ref()
1429            .is_none_or(|provenance| operations.len() == provenance.len())
1430    );
1431    let runs = collect_one_qubit_runs(&operations);
1432    let mut replacements = HashMap::<usize, (Vec<usize>, Vec<ValueOperation>)>::new();
1433    let mut phase_delta = 0.0;
1434
1435    for run in runs.into_iter().filter(|run| run.len() >= 2) {
1436        let source_ops = run
1437            .iter()
1438            .map(|order| operations[*order].clone())
1439            .collect::<Vec<_>>();
1440        let Some(matrix) = one_qubit_run_matrix(&source_ops) else {
1441            continue;
1442        };
1443        let Ok(decomposition) = synthesize_numeric_1q_unitary(&matrix) else {
1444            continue;
1445        };
1446        let qubit = source_ops[0].qubits[0];
1447        let mut candidate = Vec::new();
1448        if decomposition.theta.abs() > PHASE_EPS
1449            || decomposition.phi.abs() > PHASE_EPS
1450            || decomposition.lambda.abs() > PHASE_EPS
1451        {
1452            candidate.push(u_operation(qubit, decomposition));
1453        }
1454        if !policy.strictly_better(&candidate, &source_ops)? {
1455            continue;
1456        }
1457        phase_delta += decomposition.global_phase;
1458        replacements.insert(run[0], (run, candidate));
1459    }
1460
1461    if replacements.is_empty() {
1462        return Ok(ValueRewrite {
1463            operations,
1464            provenance,
1465            phase_delta: 0.0,
1466            changed: false,
1467        });
1468    }
1469
1470    let mut skipped = HashSet::new();
1471    for (first, (orders, _)) in &replacements {
1472        skipped.extend(orders.iter().copied().filter(|order| order != first));
1473    }
1474    let mut output = Vec::with_capacity(operations.len());
1475    let mut output_provenance = provenance
1476        .as_ref()
1477        .map(|provenance| Vec::with_capacity(provenance.len()));
1478    let mut source_orders = provenance.unwrap_or_default().into_iter();
1479    for (order, operation) in operations.into_iter().enumerate() {
1480        let source_order = source_orders.next().flatten();
1481        if let Some((_, replacement)) = replacements.remove(&order) {
1482            if let Some(output_provenance) = &mut output_provenance {
1483                output_provenance.extend(std::iter::repeat_n(None, replacement.len()));
1484            }
1485            output.extend(replacement);
1486        } else if !skipped.contains(&order) {
1487            output.push(operation);
1488            if let Some(output_provenance) = &mut output_provenance {
1489                output_provenance.push(source_order);
1490            }
1491        }
1492    }
1493    Ok(ValueRewrite {
1494        operations: output,
1495        provenance: output_provenance,
1496        phase_delta,
1497        changed: true,
1498    })
1499}
1500
1501fn collect_one_qubit_runs(operations: &[ValueOperation]) -> Vec<Vec<usize>> {
1502    let mut active = BTreeMap::<Qubit, Vec<usize>>::new();
1503    let mut runs = Vec::new();
1504    for (order, operation) in operations.iter().enumerate() {
1505        if is_fixed_numeric_one_qubit_gate(operation) {
1506            active.entry(operation.qubits[0]).or_default().push(order);
1507            continue;
1508        }
1509
1510        let global_boundary = operation.qubits.is_empty()
1511            || matches!(operation.instruction, ValueInstruction::ClassicalControl(_))
1512            || matches!(
1513                operation.instruction,
1514                ValueInstruction::Instruction(Instruction::Directive(Directive::Barrier))
1515            );
1516        if global_boundary {
1517            runs.extend(std::mem::take(&mut active).into_values());
1518        } else {
1519            for qubit in &operation.qubits {
1520                if let Some(run) = active.remove(qubit) {
1521                    runs.push(run);
1522                }
1523            }
1524        }
1525    }
1526    runs.extend(active.into_values());
1527    runs
1528}
1529
1530fn is_fixed_numeric_one_qubit_gate(operation: &ValueOperation) -> bool {
1531    matches!(
1532        &operation.instruction,
1533        ValueInstruction::Instruction(Instruction::Standard(gate))
1534            if gate.num_qubits() == 1
1535                && operation.qubits.len() == 1
1536                && operation.label.is_none()
1537                && operation.params.iter().all(|param| {
1538                    matches!(param, ParameterValue::Fixed(value) if value.is_finite())
1539                })
1540                && gate.matrix(&fixed_params(operation).unwrap_or_default()).is_ok()
1541    )
1542}
1543
1544fn fixed_params(operation: &ValueOperation) -> Option<Vec<f64>> {
1545    operation
1546        .params
1547        .iter()
1548        .map(|param| match param {
1549            ParameterValue::Fixed(value) if value.is_finite() => Some(*value),
1550            ParameterValue::Fixed(_) | ParameterValue::Param(_) => None,
1551        })
1552        .collect()
1553}
1554
1555fn u_operation(qubit: Qubit, decomposition: OneQubitUnitaryDecomposition) -> ValueOperation {
1556    ValueOperation {
1557        instruction: ValueInstruction::from_instruction(Instruction::Standard(StandardGate::U)),
1558        qubits: smallvec![qubit],
1559        params: smallvec![
1560            ParameterValue::Fixed(decomposition.theta),
1561            ParameterValue::Fixed(decomposition.phi),
1562            ParameterValue::Fixed(decomposition.lambda),
1563        ],
1564        label: None,
1565    }
1566}
1567
1568#[derive(Debug, Clone, Copy, Default)]
1569struct QubitFrame {
1570    z_angle: f64,
1571    pauli_x: bool,
1572    pauli_z: bool,
1573}
1574
1575impl QubitFrame {
1576    fn is_empty(self) -> bool {
1577        self.z_angle.abs() <= PHASE_EPS && !self.pauli_x && !self.pauli_z
1578    }
1579
1580    fn multiply_pauli(&mut self, x: bool, z: bool) -> u8 {
1581        // The new gate is later in circuit order, so the accumulated matrix is
1582        // P_new * P_pending. In canonical X^x Z^z order this contributes -1 when
1583        // the new Z anticommutes with a pending X.
1584        let phase = if z && self.pauli_x { 2 } else { 0 };
1585        self.pauli_x ^= x;
1586        self.pauli_z ^= z;
1587        phase
1588    }
1589}
1590
1591/// Ejects pending Z/Pauli frames forward through a closed table of proven gate
1592/// identities and materializes a frame at the first unsupported boundary.
1593///
1594/// A pending frame occurs earlier in circuit time than the operation currently
1595/// being visited. Moving it forward therefore conjugates it by that operation.
1596/// Frames never cross structured control flow, labels, barriers, or resets.
1597fn propagate_frames(
1598    operations: Vec<ValueOperation>,
1599    provenance: Option<Vec<Option<usize>>>,
1600) -> Result<ValueRewrite, CompilerError> {
1601    debug_assert!(
1602        provenance
1603            .as_ref()
1604            .is_none_or(|provenance| operations.len() == provenance.len())
1605    );
1606    let mut frames = BTreeMap::<Qubit, QubitFrame>::new();
1607    let mut output = Vec::with_capacity(operations.len());
1608    let mut output_provenance = provenance
1609        .as_ref()
1610        .map(|provenance| Vec::with_capacity(provenance.len()));
1611    let mut source_orders = provenance.unwrap_or_default().into_iter();
1612    let mut phase_delta = 0.0;
1613    let mut changed = false;
1614
1615    for mut operation in operations {
1616        let source_order = source_orders.next().flatten();
1617        if operation.label.is_none()
1618            && let Some((qubit, z_angle, phase)) = z_carrier(&operation)
1619        {
1620            flush_pauli(
1621                qubit,
1622                &mut frames,
1623                &mut output,
1624                &mut output_provenance,
1625                &mut phase_delta,
1626            );
1627            frames.entry(qubit).or_default().z_angle += z_angle;
1628            phase_delta += phase;
1629            changed = true;
1630            continue;
1631        }
1632        if operation.label.is_none()
1633            && let Some((qubit, x, z, phase)) = pauli_carrier(&operation)
1634        {
1635            flush_z(qubit, &mut frames, &mut output, &mut output_provenance);
1636            let frame = frames.entry(qubit).or_default();
1637            let extra_phase = frame.multiply_pauli(x, z);
1638            phase_delta += phase + f64::from(extra_phase) * FRAC_PI_2;
1639            changed = true;
1640            continue;
1641        }
1642
1643        let instruction = match &operation.instruction {
1644            ValueInstruction::Instruction(instruction) => Some(instruction.clone()),
1645            ValueInstruction::ClassicalControl(_) => None,
1646        };
1647
1648        if operation.label.is_none()
1649            && matches!(
1650                instruction.as_ref(),
1651                Some(Instruction::Standard(StandardGate::SWAP))
1652            )
1653            && operation.qubits.len() == 2
1654        {
1655            let left = frames.remove(&operation.qubits[0]).unwrap_or_default();
1656            let right = frames.remove(&operation.qubits[1]).unwrap_or_default();
1657            frames.insert(operation.qubits[0], right);
1658            frames.insert(operation.qubits[1], left);
1659            output.push(operation);
1660            if let Some(output_provenance) = &mut output_provenance {
1661                output_provenance.push(source_order);
1662            }
1663            changed |= !left.is_empty() || !right.is_empty();
1664            continue;
1665        }
1666
1667        if operation.label.is_none()
1668            && let Some(gate @ (StandardGate::CX | StandardGate::CZ)) =
1669                instruction.as_ref().and_then(|i| {
1670                    let Instruction::Standard(gate @ (StandardGate::CX | StandardGate::CZ)) = i
1671                    else {
1672                        return None;
1673                    };
1674                    Some(*gate)
1675                })
1676            && operation.qubits.len() == 2
1677        {
1678            let pair = [operation.qubits[0], operation.qubits[1]];
1679            if gate == StandardGate::CX {
1680                flush_z(pair[1], &mut frames, &mut output, &mut output_provenance);
1681            }
1682            if pair.iter().any(|qubit| {
1683                frames
1684                    .get(qubit)
1685                    .is_some_and(|frame| frame.pauli_x || frame.pauli_z)
1686            }) {
1687                for qubit in pair {
1688                    flush_z(qubit, &mut frames, &mut output, &mut output_provenance);
1689                }
1690                phase_delta += propagate_clifford_paulis(gate, pair, &mut frames);
1691                changed = true;
1692            }
1693            output.push(operation);
1694            if let Some(output_provenance) = &mut output_provenance {
1695                output_provenance.push(source_order);
1696            }
1697            continue;
1698        }
1699
1700        if operation.label.is_none()
1701            && let Some(Instruction::Standard(gate)) = instruction.as_ref()
1702            && is_z_diagonal(*gate)
1703        {
1704            let blocked = operation
1705                .qubits
1706                .iter()
1707                .any(|qubit| frames.get(qubit).is_some_and(|frame| frame.pauli_x));
1708            if !blocked {
1709                output.push(operation);
1710                if let Some(output_provenance) = &mut output_provenance {
1711                    output_provenance.push(source_order);
1712                }
1713                continue;
1714            }
1715        }
1716
1717        if operation.label.is_none() && absorb_z_into_xy_axis(&mut operation, &frames) {
1718            output.push(operation);
1719            if let Some(output_provenance) = &mut output_provenance {
1720                output_provenance.push(None);
1721            }
1722            changed = true;
1723            continue;
1724        }
1725
1726        if instruction
1727            .as_ref()
1728            .is_some_and(|instruction| instruction.has_measurement())
1729        {
1730            for qubit in &operation.qubits {
1731                if frames.get(qubit).is_some_and(|frame| frame.pauli_x) {
1732                    flush_frame(
1733                        *qubit,
1734                        &mut frames,
1735                        &mut output,
1736                        &mut output_provenance,
1737                        &mut phase_delta,
1738                    );
1739                } else {
1740                    changed |= frames.remove(qubit).is_some_and(|frame| !frame.is_empty());
1741                }
1742            }
1743            output.push(operation);
1744            if let Some(output_provenance) = &mut output_provenance {
1745                output_provenance.push(source_order);
1746            }
1747            continue;
1748        }
1749
1750        let global_boundary = operation.qubits.is_empty()
1751            || operation.label.is_some()
1752            || matches!(operation.instruction, ValueInstruction::ClassicalControl(_))
1753            || matches!(
1754                instruction.as_ref(),
1755                Some(Instruction::Directive(
1756                    Directive::Barrier | Directive::Reset
1757                ))
1758            );
1759        if global_boundary {
1760            flush_all(
1761                &mut frames,
1762                &mut output,
1763                &mut output_provenance,
1764                &mut phase_delta,
1765            );
1766        } else {
1767            for qubit in &operation.qubits {
1768                flush_frame(
1769                    *qubit,
1770                    &mut frames,
1771                    &mut output,
1772                    &mut output_provenance,
1773                    &mut phase_delta,
1774                );
1775            }
1776        }
1777        output.push(operation);
1778        if let Some(output_provenance) = &mut output_provenance {
1779            output_provenance.push(source_order);
1780        }
1781    }
1782    flush_all(
1783        &mut frames,
1784        &mut output,
1785        &mut output_provenance,
1786        &mut phase_delta,
1787    );
1788
1789    Ok(ValueRewrite {
1790        operations: output,
1791        provenance: output_provenance,
1792        phase_delta,
1793        changed,
1794    })
1795}
1796
1797fn z_carrier(operation: &ValueOperation) -> Option<(Qubit, f64, f64)> {
1798    let ValueInstruction::Instruction(Instruction::Standard(gate)) = &operation.instruction else {
1799        return None;
1800    };
1801    let qubit = *operation.qubits.first()?;
1802    Some(match gate {
1803        StandardGate::RZ => (qubit, *fixed_params(operation)?.first()?, 0.0),
1804        StandardGate::Phase => {
1805            let angle = *fixed_params(operation)?.first()?;
1806            (qubit, angle, angle / 2.0)
1807        }
1808        StandardGate::S => (qubit, FRAC_PI_2, FRAC_PI_4),
1809        StandardGate::SDG => (qubit, -FRAC_PI_2, -FRAC_PI_4),
1810        StandardGate::T => (qubit, FRAC_PI_4, FRAC_PI_4 / 2.0),
1811        StandardGate::TDG => (qubit, -FRAC_PI_4, -FRAC_PI_4 / 2.0),
1812        _ => return None,
1813    })
1814}
1815
1816fn pauli_carrier(operation: &ValueOperation) -> Option<(Qubit, bool, bool, f64)> {
1817    let ValueInstruction::Instruction(Instruction::Standard(gate)) = &operation.instruction else {
1818        return None;
1819    };
1820    let qubit = *operation.qubits.first()?;
1821    Some(match gate {
1822        StandardGate::X => (qubit, true, false, 0.0),
1823        StandardGate::Y => (qubit, true, true, FRAC_PI_2),
1824        StandardGate::Z => (qubit, false, true, 0.0),
1825        _ => return None,
1826    })
1827}
1828
1829fn absorb_z_into_xy_axis(
1830    operation: &mut ValueOperation,
1831    frames: &BTreeMap<Qubit, QubitFrame>,
1832) -> bool {
1833    let Some(&qubit) = operation.qubits.first() else {
1834        return false;
1835    };
1836    if operation.qubits.len() != 1 {
1837        return false;
1838    }
1839    let Some(frame) = frames.get(&qubit) else {
1840        return false;
1841    };
1842    if frame.z_angle.abs() <= PHASE_EPS || frame.pauli_x || frame.pauli_z {
1843        return false;
1844    }
1845    let ValueInstruction::Instruction(Instruction::Standard(gate)) = &operation.instruction else {
1846        return false;
1847    };
1848    let axis_index = match gate {
1849        StandardGate::RXY => 1,
1850        StandardGate::XY | StandardGate::XY2P | StandardGate::XY2M => 0,
1851        _ => return false,
1852    };
1853    let Some(ParameterValue::Fixed(axis)) = operation.params.get_mut(axis_index) else {
1854        return false;
1855    };
1856    // G(phi) RZ(a) = RZ(a) G(phi-a), so the pending frame can stay virtual.
1857    *axis -= frame.z_angle;
1858    true
1859}
1860
1861fn is_z_diagonal(gate: StandardGate) -> bool {
1862    matches!(
1863        gate,
1864        StandardGate::CZ | StandardGate::CRZ | StandardGate::RZZ
1865    )
1866}
1867
1868#[derive(Clone, Copy, Default)]
1869struct TwoQubitPauli {
1870    phase: u8,
1871    x: [bool; 2],
1872    z: [bool; 2],
1873}
1874
1875impl TwoQubitPauli {
1876    fn multiply(self, right: Self) -> Self {
1877        let anti = self
1878            .z
1879            .iter()
1880            .zip(right.x)
1881            .filter(|(z, x)| **z && *x)
1882            .count() as u8;
1883        Self {
1884            phase: (self.phase + right.phase + 2 * anti) % 4,
1885            x: [self.x[0] ^ right.x[0], self.x[1] ^ right.x[1]],
1886            z: [self.z[0] ^ right.z[0], self.z[1] ^ right.z[1]],
1887        }
1888    }
1889}
1890
1891/// Conjugates a two-qubit Pauli frame through CX or CZ using their stabilizer
1892/// generator images in canonical `X0 X1 Z0 Z1` multiplication order.
1893fn propagate_clifford_paulis(
1894    gate: StandardGate,
1895    qubits: [Qubit; 2],
1896    frames: &mut BTreeMap<Qubit, QubitFrame>,
1897) -> f64 {
1898    let input = TwoQubitPauli {
1899        phase: 0,
1900        x: qubits.map(|qubit| frames.get(&qubit).is_some_and(|frame| frame.pauli_x)),
1901        z: qubits.map(|qubit| frames.get(&qubit).is_some_and(|frame| frame.pauli_z)),
1902    };
1903    let generators = match gate {
1904        StandardGate::CX => [
1905            TwoQubitPauli {
1906                x: [true, true],
1907                ..Default::default()
1908            },
1909            TwoQubitPauli {
1910                x: [false, true],
1911                ..Default::default()
1912            },
1913            TwoQubitPauli {
1914                z: [true, false],
1915                ..Default::default()
1916            },
1917            TwoQubitPauli {
1918                z: [true, true],
1919                ..Default::default()
1920            },
1921        ],
1922        StandardGate::CZ => [
1923            TwoQubitPauli {
1924                x: [true, false],
1925                z: [false, true],
1926                ..Default::default()
1927            },
1928            TwoQubitPauli {
1929                x: [false, true],
1930                z: [true, false],
1931                ..Default::default()
1932            },
1933            TwoQubitPauli {
1934                z: [true, false],
1935                ..Default::default()
1936            },
1937            TwoQubitPauli {
1938                z: [false, true],
1939                ..Default::default()
1940            },
1941        ],
1942        _ => unreachable!("only Clifford propagation gates are passed here"),
1943    };
1944    let enabled = [input.x[0], input.x[1], input.z[0], input.z[1]];
1945    let result = generators
1946        .into_iter()
1947        .zip(enabled)
1948        .filter(|(_, enabled)| *enabled)
1949        .fold(TwoQubitPauli::default(), |acc, (generator, _)| {
1950            acc.multiply(generator)
1951        });
1952    for (index, qubit) in qubits.into_iter().enumerate() {
1953        let frame = frames.entry(qubit).or_default();
1954        frame.pauli_x = result.x[index];
1955        frame.pauli_z = result.z[index];
1956    }
1957    f64::from(result.phase) * FRAC_PI_2
1958}
1959
1960fn flush_all(
1961    frames: &mut BTreeMap<Qubit, QubitFrame>,
1962    output: &mut Vec<ValueOperation>,
1963    provenance: &mut Option<Vec<Option<usize>>>,
1964    phase_delta: &mut f64,
1965) {
1966    let qubits = frames.keys().copied().collect::<Vec<_>>();
1967    for qubit in qubits {
1968        flush_frame(qubit, frames, output, provenance, phase_delta);
1969    }
1970}
1971
1972fn flush_frame(
1973    qubit: Qubit,
1974    frames: &mut BTreeMap<Qubit, QubitFrame>,
1975    output: &mut Vec<ValueOperation>,
1976    provenance: &mut Option<Vec<Option<usize>>>,
1977    phase_delta: &mut f64,
1978) {
1979    flush_pauli(qubit, frames, output, provenance, phase_delta);
1980    flush_z(qubit, frames, output, provenance);
1981    if frames.get(&qubit).is_some_and(|frame| frame.is_empty()) {
1982        frames.remove(&qubit);
1983    }
1984}
1985
1986fn flush_pauli(
1987    qubit: Qubit,
1988    frames: &mut BTreeMap<Qubit, QubitFrame>,
1989    output: &mut Vec<ValueOperation>,
1990    provenance: &mut Option<Vec<Option<usize>>>,
1991    phase_delta: &mut f64,
1992) {
1993    let Some(frame) = frames.get_mut(&qubit) else {
1994        return;
1995    };
1996    let gate = match (frame.pauli_x, frame.pauli_z) {
1997        (false, false) => None,
1998        (true, false) => Some(StandardGate::X),
1999        (false, true) => Some(StandardGate::Z),
2000        (true, true) => {
2001            *phase_delta -= FRAC_PI_2;
2002            Some(StandardGate::Y)
2003        }
2004    };
2005    if let Some(gate) = gate {
2006        output.push(ValueOperation {
2007            instruction: ValueInstruction::from_instruction(Instruction::Standard(gate)),
2008            qubits: smallvec![qubit],
2009            params: SmallVec::new(),
2010            label: None,
2011        });
2012        if let Some(provenance) = provenance {
2013            provenance.push(None);
2014        }
2015    }
2016    frame.pauli_x = false;
2017    frame.pauli_z = false;
2018}
2019
2020fn flush_z(
2021    qubit: Qubit,
2022    frames: &mut BTreeMap<Qubit, QubitFrame>,
2023    output: &mut Vec<ValueOperation>,
2024    provenance: &mut Option<Vec<Option<usize>>>,
2025) {
2026    let Some(frame) = frames.get_mut(&qubit) else {
2027        return;
2028    };
2029    if frame.z_angle.abs() > PHASE_EPS {
2030        output.push(ValueOperation {
2031            instruction: ValueInstruction::from_instruction(Instruction::Standard(
2032                StandardGate::RZ,
2033            )),
2034            qubits: smallvec![qubit],
2035            params: smallvec![ParameterValue::Fixed(frame.z_angle)],
2036            label: None,
2037        });
2038        if let Some(provenance) = provenance {
2039            provenance.push(None);
2040        }
2041    }
2042    frame.z_angle = 0.0;
2043}
2044
2045#[cfg(test)]
2046#[path = "native_optimization_test.rs"]
2047mod native_optimization_test;