Skip to main content

cqlib_core/compile/
workflow.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2025-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//! Workflow-level orchestration for the new compiler optimization pipeline.
15//!
16//! The workflow is a staged composition layer, not an optimization algorithm.
17//! It resolves target constraints, runs completed compiler transforms in a
18//! deterministic order, and records only the postconditions it can actually
19//! verify with the compiler capabilities currently implemented.
20//!
21//! The shared workflow follows the stable pass order:
22//! canonicalize input, expand circuit-backed definitions, apply production
23//! knowledge rewrite, decompose unitary and multi-controlled gates,
24//! canonicalize again, optimize the decomposed circuit, optionally lower to a
25//! routing-compatible basis, optionally route on a device, optionally translate
26//! to the resolved target basis, and canonicalize the output representation. A
27//! strict-device candidate suffix then lowers every gate to exact ordered native
28//! capabilities, closes a bounded native-optimization loop, and validates the
29//! completed physical circuit.
30//!
31//! The enhanced workflow uses the same correctness contract but raises rewrite,
32//! resynthesis, native-optimization, and SABRE search budgets, performs
33//! post-routing cleanup, and adds target-aware cleanup when an explicit target
34//! basis is present. For a strict [`CompileTarget::Device`], it also snapshots
35//! the immutable pre-routing prefix. Candidate zero first completes the entire
36//! routing-to-validation suffix; a bounded two-tier SABRE Pareto beam then
37//! explores alternative routes from the same prefix, with every candidate
38//! independently traversing that same suffix through device validation. An
39//! exploratory candidate replaces candidate zero only when it preserves the
40//! exact native-quality contract in every control-flow scope and strictly
41//! improves native two-qubit count or depth. Otherwise the validated candidate
42//! zero remains the result.
43//!
44//! Stages are deliberately ordered around compiler invariants. Early
45//! canonicalization gives later passes a stable representation, definition and
46//! high-level gate decomposition remove operations that routing cannot accept,
47//! routing runs before final target-basis cleanup because it may insert SWAPs,
48//! and output canonicalization removes representation noise before exact device
49//! lowering. Native optimization is re-legalized and costed on exact physical
50//! qargs. Validation is terminal within each finalized candidate suffix. In an
51//! enhanced strict-device workflow, `select.sabre_pareto_beam` is the final
52//! orchestration and reporting step; it applies no transform after validation
53//! and can select only an already validated candidate.
54
55use crate::circuit::{Circuit, Instruction, Operation, StandardGate};
56use crate::compile::CompilerError;
57use crate::compile::device_planning::DevicePlanningSession;
58use crate::compile::resource::ResourceLimits;
59use crate::compile::sabre::{RoutingTarget, SabreConfig};
60use crate::compile::transform::analysis::WorkflowCircuitAnalysis;
61use crate::compile::transform::decompose::unitary::{
62    DeviceSynthesisPlacement, DeviceTwoQubitSynthesisContext, TwoQubitSynthesisTarget,
63};
64use crate::compile::transform::decompose::{
65    DecomposeDefinitions, DecomposeMcGates, DecomposeUnitaries, McGateDecomposeConfig,
66    UnitaryDecomposeConfig,
67};
68use crate::compile::transform::layout::PhysicalLayoutGraph;
69use crate::compile::transform::native_optimization::{
70    NativeExactQualityCheckpoint, NativeOptimizationSummary, NativeOptimizer,
71};
72use crate::compile::transform::resynthesis::{
73    WorkflowResynthesisSession, resynthesize_two_qubit_blocks_workflow,
74};
75use crate::compile::transform::transformer::{PassApplicability, WorkflowPass};
76use crate::compile::transform::{
77    CanonicalizeConfig, Canonicalizer, CircuitAnalysis, CommutativeCancellation, DeviceLowerer,
78    KnowledgeRewriteDiagnostics, KnowledgeRewriteSession, KnowledgeRewriter, LayoutObjective,
79    LowerToRoutingBasis, NativeQualityPolicy, OptimizeOneQubitRuns, ResynthesizeTwoQubitBlocks,
80    RewriteConfig, RewriteEdits, RewriteExecutionRecord, SabreParetoRouteCandidate,
81    TargetBasisCostModel, TargetBasisLowerer, TransformOutcome, Transformer,
82    TwoQubitBlockResynthesisConfig, VirtualPermutation, VirtualPermutationElisionStatus,
83    elide_virtual_permutations, prepare_sabre_pareto_routes_with_session_on_physical,
84    route_sabre_tracked_on_topology, route_sabre_tracked_with_session_on_physical,
85    route_with_layout_tracked_on_topology, route_with_layout_tracked_with_session_on_physical,
86};
87use std::sync::{Arc, Mutex, OnceLock};
88
89use super::{
90    CompileConfig, CompileMode, CompileResult, CompileTarget, DeviceCompilationMetadata,
91    DeviceCompileTarget,
92};
93
94/// Per-step execution record produced by a workflow run.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct WorkflowStepReport {
97    /// Coarse workflow stage, following the staged-pass-manager model.
98    pub stage: &'static str,
99    /// Workflow-local step name.
100    pub name: &'static str,
101    /// Whether this step changed the circuit representation.
102    pub changed: bool,
103    /// Whether the step was intentionally skipped.
104    pub skipped: bool,
105    /// Optional skip or configuration note.
106    pub reason: Option<String>,
107}
108
109/// Evidence that one exact circuit revision satisfies one canonicalization
110/// configuration. Any circuit mutation invalidates the evidence implicitly by
111/// advancing the revision.
112#[derive(Debug, Clone)]
113struct CanonicalizationProof {
114    circuit_revision: u64,
115    config: CanonicalizeConfig,
116}
117
118struct WorkflowState {
119    current: Circuit,
120    analysis: Option<WorkflowCircuitAnalysis>,
121    circuit_revision: u64,
122    canonical_proof: Option<CanonicalizationProof>,
123    rewrite_session: KnowledgeRewriteSession,
124    rewrite_diagnostics: KnowledgeRewriteDiagnostics,
125    collect_rewrite_diagnostics: bool,
126    changed: bool,
127    steps: Vec<WorkflowStepReport>,
128    prepared_target_basis: Option<Arc<PreparedTargetBasis>>,
129    two_qubit_target: TwoQubitSynthesisTarget,
130    virtual_permutation: Option<VirtualPermutation>,
131    device_metadata: Option<DeviceCompilationMetadata>,
132    one_qubit_optimizer: Option<OptimizeOneQubitRuns>,
133    pending_one_qubit_resynthesis: bool,
134    resynthesis_session: WorkflowResynthesisSession,
135    routing_physical: Option<Arc<PhysicalLayoutGraph>>,
136    topology_routing_target: Option<Arc<RoutingTarget>>,
137    planning_session: Option<Arc<DevicePlanningSession>>,
138    native_quality_checkpoint: Option<NativeExactQualityCheckpoint>,
139}
140
141struct PreparedTargetBasis {
142    instructions: Arc<[Instruction]>,
143    lowerer: Arc<TargetBasisLowerer>,
144    cost_model: Arc<TargetBasisCostModel>,
145}
146
147impl PreparedTargetBasis {
148    fn new(target_basis: Vec<Instruction>) -> Result<Self, CompilerError> {
149        let instructions: Arc<[Instruction]> = target_basis.into();
150        let lowerer = Arc::new(TargetBasisLowerer::from_shared_basis(Arc::clone(
151            &instructions,
152        ))?);
153        let cost_model = Arc::new(TargetBasisCostModel::from_lowerer(Arc::clone(&lowerer))?);
154        Ok(Self {
155            instructions,
156            lowerer,
157            cost_model,
158        })
159    }
160}
161
162/// Target-derived data shared by every run of one compiler workflow.
163///
164/// Circuit analysis, SABRE DAGs, interaction requirements, and route metadata
165/// deliberately remain in [`WorkflowState`] or the routing call that owns them.
166struct PreparedCompileTarget {
167    target_basis: Option<Arc<PreparedTargetBasis>>,
168    one_qubit_optimizer: Option<OptimizeOneQubitRuns>,
169    two_qubit_target: TwoQubitSynthesisTarget,
170    routing_physical: Option<Arc<PhysicalLayoutGraph>>,
171    topology_routing_target: Option<Arc<RoutingTarget>>,
172    planning_session: Option<Arc<DevicePlanningSession>>,
173}
174
175impl PreparedCompileTarget {
176    fn new(config: &CompileConfig) -> Result<Self, CompilerError> {
177        let target_basis = match &config.target {
178            CompileTarget::Basis(target_basis)
179            | CompileTarget::TopologyBasis {
180                basis: target_basis,
181                ..
182            } => {
183                validate_workflow_target_basis_config(target_basis)?;
184                Some(Arc::new(PreparedTargetBasis::new(target_basis.to_vec())?))
185            }
186            CompileTarget::Logical | CompileTarget::Device(_) => None,
187        };
188        let one_qubit_optimizer = match &config.target {
189            CompileTarget::Logical => Some(OptimizeOneQubitRuns::logical()),
190            CompileTarget::Basis(_) | CompileTarget::TopologyBasis { .. } => {
191                let prepared = target_basis.as_ref().ok_or_else(|| {
192                    CompilerError::InvariantViolation(
193                        "explicit target basis was not prepared".to_string(),
194                    )
195                })?;
196                Some(OptimizeOneQubitRuns::basis_with_cost_model(Arc::clone(
197                    &prepared.cost_model,
198                )))
199            }
200            CompileTarget::Device(_) => None,
201        };
202        let two_qubit_target = target_basis
203            .as_ref()
204            .map_or_else(TwoQubitSynthesisTarget::unconstrained, |prepared| {
205                TwoQubitSynthesisTarget::from_cost_model(Arc::clone(&prepared.cost_model))
206            });
207        let (routing_physical, topology_routing_target, planning_session) = match &config.target {
208            CompileTarget::TopologyBasis { device_target, .. } => {
209                let physical = Arc::new(PhysicalLayoutGraph::from_device(&device_target.device)?);
210                let routing = Arc::new(RoutingTarget::from_physical(&physical)?);
211                (Some(physical), Some(routing), None)
212            }
213            CompileTarget::Device(target) => {
214                let physical = Arc::new(PhysicalLayoutGraph::from_device(&target.device)?);
215                let session = Arc::new(DevicePlanningSession::new(&target.device));
216                (Some(physical), None, Some(session))
217            }
218            CompileTarget::Logical | CompileTarget::Basis(_) => (None, None, None),
219        };
220        Ok(Self {
221            target_basis,
222            one_qubit_optimizer,
223            two_qubit_target,
224            routing_physical,
225            topology_routing_target,
226            planning_session,
227        })
228    }
229}
230
231impl WorkflowState {
232    /// Creates an isolated branch at the current workflow boundary. Immutable
233    /// target preparation is shared, while mutation-sensitive rewrite and
234    /// resynthesis sessions start fresh so one exploratory route cannot affect
235    /// candidate zero or a sibling candidate.
236    fn detached_clone(&self) -> Self {
237        Self {
238            current: self.current.clone(),
239            analysis: None,
240            circuit_revision: self.circuit_revision,
241            canonical_proof: self.canonical_proof.clone(),
242            rewrite_session: KnowledgeRewriteSession::default(),
243            rewrite_diagnostics: self.rewrite_diagnostics,
244            collect_rewrite_diagnostics: self.collect_rewrite_diagnostics,
245            changed: self.changed,
246            steps: self.steps.clone(),
247            prepared_target_basis: self.prepared_target_basis.clone(),
248            two_qubit_target: self.two_qubit_target.clone(),
249            virtual_permutation: self.virtual_permutation.clone(),
250            device_metadata: self.device_metadata.clone(),
251            one_qubit_optimizer: self.one_qubit_optimizer.clone(),
252            pending_one_qubit_resynthesis: self.pending_one_qubit_resynthesis,
253            resynthesis_session: WorkflowResynthesisSession::default(),
254            routing_physical: self.routing_physical.clone(),
255            topology_routing_target: self.topology_routing_target.clone(),
256            planning_session: self.planning_session.clone(),
257            native_quality_checkpoint: self.native_quality_checkpoint.clone(),
258        }
259    }
260
261    fn analysis(&mut self) -> &WorkflowCircuitAnalysis {
262        if self.analysis.is_none() {
263            self.analysis = Some(WorkflowCircuitAnalysis::analyze(&self.current));
264        }
265        self.analysis
266            .as_ref()
267            .expect("workflow analysis was initialized above")
268    }
269
270    fn skip_if_proven_noop(
271        &mut self,
272        stage: &'static str,
273        name: &'static str,
274        pass: &impl WorkflowPass,
275    ) -> bool {
276        let applicability = pass.applicability(self.analysis());
277        let PassApplicability::ProvenNoOp(reason) = applicability else {
278            return false;
279        };
280        self.record_skipped(stage, name, reason);
281        true
282    }
283
284    fn advance_circuit_revision(&mut self) {
285        self.circuit_revision = self.circuit_revision.wrapping_add(1);
286        if self.circuit_revision == 0 {
287            // Prevent a wrapped revision from aliasing an ancient proof.
288            self.rewrite_session.invalidate();
289            self.canonical_proof = None;
290        }
291    }
292
293    fn adopt_changed_circuit(&mut self, circuit: Circuit, edits: &RewriteEdits) {
294        let old_revision = self.circuit_revision;
295        let new_revision = old_revision.wrapping_add(1);
296        if new_revision == 0 {
297            self.rewrite_session.invalidate();
298            self.canonical_proof = None;
299        } else {
300            self.rewrite_session.apply_rewrite_edits(
301                old_revision,
302                new_revision,
303                &self.current,
304                &circuit,
305                edits,
306            );
307        }
308        self.analysis = None;
309        self.current = circuit;
310        self.circuit_revision = new_revision;
311        self.changed = true;
312    }
313
314    fn apply_transform(
315        &mut self,
316        stage: &'static str,
317        name: &'static str,
318        transform: impl FnOnce(&Circuit, &CircuitAnalysis) -> Result<TransformOutcome, CompilerError>,
319    ) -> Result<bool, CompilerError> {
320        let tracks_rewrite_edits = self.rewrite_session.tracks_rewrite_edits();
321        self.apply_transform_with_edits(stage, name, |circuit, analysis| {
322            let outcome = transform(circuit, analysis)?;
323            let edits = match &outcome {
324                TransformOutcome::Unchanged => RewriteEdits::linear(
325                    circuit.operations().len(),
326                    circuit.operations().len(),
327                    Vec::new(),
328                ),
329                TransformOutcome::Changed(after) if tracks_rewrite_edits => {
330                    RewriteEdits::between_linear_circuits(circuit, after)
331                }
332                TransformOutcome::Changed(_) => RewriteEdits::Unknown,
333            };
334            Ok((outcome, edits))
335        })
336    }
337
338    fn apply_transform_without_analysis_with_edits(
339        &mut self,
340        stage: &'static str,
341        name: &'static str,
342        transform: impl FnOnce(&Circuit) -> Result<(TransformOutcome, RewriteEdits), CompilerError>,
343    ) -> Result<bool, CompilerError> {
344        let changed = match transform(&self.current)? {
345            (TransformOutcome::Unchanged, _) => false,
346            (TransformOutcome::Changed(circuit), edits) => {
347                self.adopt_changed_circuit(circuit, &edits);
348                true
349            }
350        };
351        self.steps.push(WorkflowStepReport {
352            stage,
353            name,
354            changed,
355            skipped: false,
356            reason: None,
357        });
358        Ok(changed)
359    }
360
361    fn apply_transform_with_edits(
362        &mut self,
363        stage: &'static str,
364        name: &'static str,
365        transform: impl FnOnce(
366            &Circuit,
367            &CircuitAnalysis,
368        ) -> Result<(TransformOutcome, RewriteEdits), CompilerError>,
369    ) -> Result<bool, CompilerError> {
370        if self.analysis.is_none() {
371            self.analysis = Some(WorkflowCircuitAnalysis::analyze(&self.current));
372        }
373        let analysis = self
374            .analysis
375            .as_ref()
376            .expect("workflow analysis was initialized above")
377            .public();
378        let changed = match transform(&self.current, analysis)? {
379            (TransformOutcome::Unchanged, _) => false,
380            (TransformOutcome::Changed(circuit), edits) => {
381                self.adopt_changed_circuit(circuit, &edits);
382                true
383            }
384        };
385        self.steps.push(WorkflowStepReport {
386            stage,
387            name,
388            changed,
389            skipped: false,
390            reason: None,
391        });
392        Ok(changed)
393    }
394
395    fn apply_canonicalize(
396        &mut self,
397        stage: &'static str,
398        name: &'static str,
399        canonicalizer: &Canonicalizer,
400    ) -> Result<bool, CompilerError> {
401        if self.has_canonical_proof(canonicalizer.config()) {
402            // Proof reuse is an executed no-op, rather than a skipped workflow
403            // stage: the same postcondition is established without rerunning
404            // the canonicalizer.
405            self.steps.push(WorkflowStepReport {
406                stage,
407                name,
408                changed: false,
409                skipped: false,
410                reason: None,
411            });
412            return Ok(false);
413        }
414        let changed = self.apply_transform_without_analysis_with_edits(stage, name, |circuit| {
415            canonicalizer.transform_with_rewrite_edits(circuit)
416        })?;
417        self.canonical_proof = Some(CanonicalizationProof {
418            circuit_revision: self.circuit_revision,
419            config: canonicalizer.config().clone(),
420        });
421        Ok(changed)
422    }
423
424    fn has_canonical_proof(&self, config: &CanonicalizeConfig) -> bool {
425        self.canonical_proof.as_ref().is_some_and(|proof| {
426            proof.circuit_revision == self.circuit_revision && &proof.config == config
427        })
428    }
429
430    fn apply_knowledge_rewrite(
431        &mut self,
432        stage: &'static str,
433        name: &'static str,
434        config: RewriteConfig,
435    ) -> Result<bool, CompilerError> {
436        if self
437            .rewrite_session
438            .reusable_proof(self.circuit_revision, &config)
439            .is_some()
440        {
441            if self.collect_rewrite_diagnostics {
442                self.rewrite_diagnostics.merge(KnowledgeRewriteDiagnostics {
443                    direct_reuses: 1,
444                    ..KnowledgeRewriteDiagnostics::default()
445                });
446            }
447            self.steps.push(WorkflowStepReport {
448                stage,
449                name,
450                changed: false,
451                skipped: false,
452                reason: None,
453            });
454            return Ok(false);
455        }
456
457        let rewriter = KnowledgeRewriter::new(config.clone());
458        let proof_reach = rewriter.proof_reach()?;
459        let qubit_bijection_invariant = rewriter.proof_is_qubit_bijection_invariant()?;
460        let reconciliation = self
461            .rewrite_session
462            .take_reconciled_linear_state(self.circuit_revision, &config);
463        let (result, workspace) = rewriter.run_for_session(
464            &self.current,
465            reconciliation.state,
466            self.collect_rewrite_diagnostics,
467        )?;
468        if self.collect_rewrite_diagnostics && reconciliation.full_scan_fallback {
469            self.rewrite_diagnostics.full_scan_fallbacks = self
470                .rewrite_diagnostics
471                .full_scan_fallbacks
472                .saturating_add(1);
473        }
474        if self.collect_rewrite_diagnostics {
475            self.rewrite_diagnostics.merge(result.diagnostics);
476        }
477        let changed = result.changed;
478        if changed {
479            let circuit = result.circuit.ok_or_else(|| {
480                CompilerError::InvariantViolation(
481                    "knowledge rewrite reported a change without an output circuit".to_string(),
482                )
483            })?;
484            self.analysis = None;
485            self.current = circuit;
486            self.advance_circuit_revision();
487            self.changed = true;
488        }
489        self.rewrite_session.record_execution(
490            &self.current,
491            self.circuit_revision,
492            RewriteExecutionRecord {
493                config,
494                proof_reach,
495                qubit_bijection_invariant,
496                reached_fixpoint: result.stats.reached_fixpoint,
497                workspace,
498            },
499        );
500        self.steps.push(WorkflowStepReport {
501            stage,
502            name,
503            changed,
504            skipped: false,
505            reason: None,
506        });
507        Ok(changed)
508    }
509
510    fn record_skipped(
511        &mut self,
512        stage: &'static str,
513        name: &'static str,
514        reason: impl Into<String>,
515    ) {
516        self.steps.push(WorkflowStepReport {
517            stage,
518            name,
519            changed: false,
520            skipped: true,
521            reason: Some(reason.into()),
522        });
523    }
524}
525
526#[derive(Debug, Clone, Copy, PartialEq, Eq)]
527enum RewritePhase {
528    PreDecomposition,
529    PostDecomposition,
530    PostRouting,
531    TargetCleanup,
532}
533
534/// Compiler optimization workflow built from completed compiler transforms.
535pub struct CompilerWorkflow {
536    config: CompileConfig,
537    prepared_target: OnceLock<Arc<PreparedCompileTarget>>,
538    prepare_lock: Mutex<()>,
539}
540
541impl CompilerWorkflow {
542    /// Creates a compiler workflow from a complete configuration.
543    ///
544    /// Target preparation is deferred until the first run. Use
545    /// [`Self::try_new`] when construction should validate and prepare the
546    /// complete target eagerly.
547    pub const fn new(config: CompileConfig) -> Self {
548        Self {
549            config,
550            prepared_target: OnceLock::new(),
551            prepare_lock: Mutex::new(()),
552        }
553    }
554
555    /// Creates a workflow and eagerly prepares all target-derived invariants.
556    pub fn try_new(config: CompileConfig) -> Result<Self, CompilerError> {
557        let workflow = Self::new(config);
558        workflow.prepared_target()?;
559        Ok(workflow)
560    }
561
562    /// Returns the workflow configuration.
563    pub const fn config(&self) -> &CompileConfig {
564        &self.config
565    }
566
567    /// Runs the workflow over `circuit` and returns the rebuilt circuit plus
568    /// execution metadata.
569    pub fn run(&self, circuit: &Circuit) -> Result<CompileResult, CompilerError> {
570        self.run_owned(circuit.clone())
571    }
572
573    /// Runs the workflow by consuming `circuit`.
574    ///
575    /// This avoids the workflow's defensive input clone when the caller
576    /// already owns an isolated circuit value. The borrowed [`Self::run`]
577    /// entry point remains the compatibility path for callers that need to
578    /// retain their input.
579    pub fn run_owned(&self, circuit: Circuit) -> Result<CompileResult, CompilerError> {
580        self.run_internal(circuit, false).map(|(result, _)| result)
581    }
582
583    /// Runs the workflow and also returns aggregate rewrite diagnostics.
584    ///
585    /// This explicit entry point keeps benchmark-only attribution separate
586    /// from [`CompileResult`]'s stable semantic equality and normal API.
587    pub fn run_with_rewrite_diagnostics(
588        &self,
589        circuit: &Circuit,
590    ) -> Result<(CompileResult, KnowledgeRewriteDiagnostics), CompilerError> {
591        self.run_owned_with_rewrite_diagnostics(circuit.clone())
592    }
593
594    /// Runs the workflow by consuming `circuit` and collects rewrite
595    /// diagnostics for benchmark attribution.
596    ///
597    /// This is the owned-input counterpart of
598    /// [`Self::run_with_rewrite_diagnostics`] and avoids its defensive clone.
599    pub fn run_owned_with_rewrite_diagnostics(
600        &self,
601        circuit: Circuit,
602    ) -> Result<(CompileResult, KnowledgeRewriteDiagnostics), CompilerError> {
603        self.run_internal(circuit, true)
604    }
605
606    fn run_internal(
607        &self,
608        circuit: Circuit,
609        collect_rewrite_diagnostics: bool,
610    ) -> Result<(CompileResult, KnowledgeRewriteDiagnostics), CompilerError> {
611        let prepared_target = self.prepared_target()?;
612        let analysis = WorkflowCircuitAnalysis::analyze(&circuit);
613        let mut state = WorkflowState {
614            current: circuit,
615            analysis: Some(analysis),
616            circuit_revision: 0,
617            canonical_proof: None,
618            rewrite_session: KnowledgeRewriteSession::default(),
619            rewrite_diagnostics: KnowledgeRewriteDiagnostics::default(),
620            collect_rewrite_diagnostics,
621            changed: false,
622            steps: Vec::new(),
623            prepared_target_basis: prepared_target.target_basis.clone(),
624            two_qubit_target: prepared_target.two_qubit_target.clone(),
625            virtual_permutation: None,
626            device_metadata: None,
627            one_qubit_optimizer: prepared_target.one_qubit_optimizer.clone(),
628            pending_one_qubit_resynthesis: false,
629            resynthesis_session: WorkflowResynthesisSession::default(),
630            routing_physical: prepared_target.routing_physical.clone(),
631            topology_routing_target: prepared_target.topology_routing_target.clone(),
632            planning_session: prepared_target.planning_session.clone(),
633            native_quality_checkpoint: None,
634        };
635
636        self.record_pre_init(&mut state);
637        self.validate_resources(&mut state)?;
638        self.lower_init(&mut state)?;
639        self.lower_decompose(&mut state)?;
640        self.lower_optimize(&mut state)?;
641        self.lower_routing_basis(&mut state)?;
642        let pareto_prefix = (self.config.mode == CompileMode::Enhanced
643            && self.strict_device_target().is_some())
644        .then(|| state.detached_clone());
645        self.lower_physical(&mut state)?;
646        self.finish_post_routing(&mut state)?;
647        if let Some(prefix) = pareto_prefix {
648            self.apply_sabre_pareto_beam(&mut state, prefix)?;
649        }
650
651        Ok((
652            CompileResult {
653                circuit: state.current,
654                changed: state.changed,
655                mode: self.config.mode,
656                steps: state.steps,
657                device_metadata: state.device_metadata,
658            },
659            state.rewrite_diagnostics,
660        ))
661    }
662
663    /// Establishes a stable high-level IR before gate-specific lowering.
664    ///
665    /// Definition expansion precedes the first rewrite pass so knowledge rules
666    /// see the operations contained by user-defined gates.
667    fn lower_init(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
668        state.apply_canonicalize("init", "canonicalize.input", &Canonicalizer::production())?;
669        self.apply_definition_decomposition(state)?;
670        let rewrite_config = self.rewrite_config(RewritePhase::PreDecomposition)?;
671        state.apply_knowledge_rewrite(
672            "optimization",
673            "optimize.pre_decomposition",
674            rewrite_config,
675        )?;
676        Ok(())
677    }
678
679    /// Lowers opaque unitary and multi-controlled operations.
680    ///
681    /// Routing and target-basis translation only operate on concrete operation
682    /// families, so this stage runs before physical and target lowering.
683    fn lower_decompose(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
684        self.apply_unitary_decomposition(state)?;
685        self.apply_mc_gate_decomposition(state)?;
686        state.apply_canonicalize(
687            "optimization",
688            "canonicalize.after_decomposition",
689            &Canonicalizer::production(),
690        )?;
691        state.apply_transform_without_analysis_with_edits(
692            "optimization",
693            "optimize.commutative_cancellation",
694            |circuit| CommutativeCancellation::new().transform_with_rewrite_edits(circuit),
695        )?;
696        self.apply_virtual_permutation_elision(state)?;
697        self.apply_two_qubit_resynthesis(
698            state,
699            "optimization",
700            "resynthesize.two_qubit_blocks",
701            DeviceSynthesisPlacement::PreLayoutEnvelope,
702        )?;
703        state.pending_one_qubit_resynthesis =
704            self.apply_one_qubit_optimization(state, "optimize.one_qubit.post_decomposition")?;
705        Ok(())
706    }
707
708    fn lower_optimize(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
709        let rewrite_config = self.rewrite_config(RewritePhase::PostDecomposition)?;
710        state.apply_knowledge_rewrite(
711            "optimization",
712            "optimize.post_decomposition",
713            rewrite_config,
714        )?;
715        self.close_one_qubit_resynthesis(state)
716    }
717
718    /// Applies optional routing-basis lowering before physical routing.
719    ///
720    /// This stage is intentionally separate from final target-basis
721    /// translation. It only guarantees that standard gates unsupported by
722    /// SABRE's arity model, such as `CCX`, are lowered to operations with at
723    /// most two qubits before layout and routing run.
724    fn lower_routing_basis(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
725        self.apply_routing_basis_decomposition(state)
726    }
727
728    /// Applies optional physical lowering from logical to physical qubits.
729    fn lower_physical(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
730        self.apply_layout_and_routing(state)
731    }
732
733    /// Completes the shared lowering, optimization, and validation suffix after
734    /// the routing stage has either produced a routed circuit or been skipped.
735    fn finish_post_routing(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
736        if self.config.mode == CompileMode::Enhanced {
737            self.apply_post_routing_resynthesis(state)?;
738            self.apply_post_routing_cleanup(state)?;
739        }
740        self.lower_target(state)?;
741        self.lower_output(state)?;
742        self.lower_device_instructions(state)?;
743        self.canonicalize_native_input(state)?;
744        self.optimize_native_instructions(state)?;
745        self.validate_device(state)?;
746        self.validate_topology_target(state)?;
747        Ok(())
748    }
749
750    /// Applies optional target-basis translation and target-aware cleanup.
751    ///
752    /// This stage runs after routing because routing may insert SWAPs and expose
753    /// new target-aware rewrite opportunities.
754    fn lower_target(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
755        self.apply_target_translation(state)?;
756        if self.config.mode == CompileMode::Enhanced {
757            if let Some(cleanup_config) = self.target_cleanup_config(state)? {
758                state.apply_knowledge_rewrite(
759                    "optimization",
760                    "optimize.target_cleanup",
761                    cleanup_config,
762                )?;
763            } else {
764                state.record_skipped(
765                    "optimization",
766                    "optimize.target_cleanup",
767                    "no explicit target basis configured",
768                );
769            }
770        }
771        if matches!(
772            self.config.target,
773            CompileTarget::Basis(_) | CompileTarget::TopologyBasis { .. }
774        ) {
775            let changed =
776                self.apply_one_qubit_optimization(state, "optimize.one_qubit.post_translation")?;
777            if changed {
778                self.apply_target_translation_named(
779                    state,
780                    "translate.target_basis.after_one_qubit",
781                )?;
782            } else {
783                state.record_skipped(
784                    "translation",
785                    "translate.target_basis.after_one_qubit",
786                    "post-translation one-qubit optimization was stable",
787                );
788            }
789            self.validate_explicit_target_basis(state)?;
790        } else {
791            state.record_skipped(
792                "optimization",
793                "optimize.one_qubit.post_translation",
794                "no explicit basis target configured",
795            );
796            state.record_skipped(
797                "translation",
798                "translate.target_basis.after_one_qubit",
799                "no explicit basis target configured",
800            );
801        }
802        Ok(())
803    }
804
805    fn lower_output(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
806        state.apply_canonicalize(
807            "output",
808            "canonicalize.output",
809            &Canonicalizer::production(),
810        )?;
811        Ok(())
812    }
813
814    fn lower_device_instructions(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
815        let Some(target) = self.strict_device_target() else {
816            state.record_skipped(
817                "translation",
818                "lower.device_instructions",
819                self.strict_device_skip_reason(),
820            );
821            return Ok(());
822        };
823        let planning_session = state.planning_session.clone();
824        let lowerer = if let Some(session) = planning_session.as_deref() {
825            DeviceLowerer::with_session(&target.device, session)
826        } else {
827            DeviceLowerer::new(&target.device)
828        };
829        state.apply_transform_without_analysis_with_edits(
830            "translation",
831            "lower.device_instructions",
832            |circuit| Ok((lowerer.transform(circuit, None)?, RewriteEdits::Unknown)),
833        )?;
834        Ok(())
835    }
836
837    fn canonicalize_native_input(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
838        if self.strict_device_target().is_none() {
839            state.record_skipped(
840                "optimization",
841                "canonicalize.native_input",
842                self.strict_device_skip_reason(),
843            );
844            return Ok(());
845        }
846        // The native optimizer owns this entry postcondition: it reuses an
847        // exact workflow proof for the current revision, or canonicalizes the
848        // input itself when device lowering invalidated that proof. A separate
849        // workflow pass here would duplicate one of those paths.
850        state.record_skipped(
851            "optimization",
852            "canonicalize.native_input",
853            "native optimizer establishes canonical input on entry",
854        );
855        Ok(())
856    }
857
858    fn optimize_native_instructions(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
859        let Some(target) = self.strict_device_target() else {
860            state.record_skipped(
861                "optimization",
862                "optimize.native_fixed_point",
863                self.strict_device_skip_reason(),
864            );
865            return Ok(());
866        };
867        let (max_rounds, max_stale_rounds) = match self.config.mode {
868            CompileMode::Normal => (
869                NativeOptimizer::NORMAL_MAX_ROUNDS,
870                NativeOptimizer::NORMAL_MAX_STALE_ROUNDS,
871            ),
872            CompileMode::Enhanced => (
873                NativeOptimizer::ENHANCED_MAX_ROUNDS,
874                NativeOptimizer::ENHANCED_MAX_STALE_ROUNDS,
875            ),
876        };
877        let planning_session = state.planning_session.clone().ok_or_else(|| {
878            CompilerError::InvariantViolation(
879                "strict device workflow has no planning session".to_string(),
880            )
881        })?;
882        let optimizer = NativeOptimizer::with_session(
883            &target.device,
884            self.two_qubit_resynthesis_config_for_state(state),
885            max_rounds,
886            max_stale_rounds,
887            Arc::clone(&planning_session),
888        )
889        .with_quality_policy(NativeQualityPolicy::BalancedDepth);
890        let input_is_canonical = state.has_canonical_proof(Canonicalizer::production().config());
891        let (result, exact_quality, native_stats) = if input_is_canonical {
892            optimizer.run_with_proven_canonical_input_exact_quality_and_stats(&state.current)?
893        } else {
894            optimizer.run_with_exact_quality_and_stats(&state.current)?
895        };
896        let changed = result.changed;
897        let reason = format!(
898            "quality_policy=balanced_depth; rounds={}; restored_best={}; native_2q_ops={}->{}; native_2q_depth={}->{}; native_depth={}->{}; native_ops={}->{}; predicted_log_error={:?}->{:?}; unavailable_error_count={}->{}; imputed_error_count={}->{}; quality_rejections=scope_shape:{},2q_ops:{},2q_depth:{},total_depth:{},error:{},makespan:{},rank:{}",
899            result.rounds,
900            result.restored_best,
901            result.before.native_two_qubit_ops,
902            result.after.native_two_qubit_ops,
903            result.before.native_two_qubit_depth,
904            result.after.native_two_qubit_depth,
905            result.before.total_native_depth,
906            result.after.total_native_depth,
907            result.before.native_total_ops,
908            result.after.native_total_ops,
909            result.before.predicted_log_error,
910            result.after.predicted_log_error,
911            result.before.unavailable_error_count,
912            result.after.unavailable_error_count,
913            result.before.imputed_error_count,
914            result.after.imputed_error_count,
915            native_stats.quality_scope_shape_rejections,
916            native_stats.quality_two_qubit_ops_rejections,
917            native_stats.quality_two_qubit_depth_rejections,
918            native_stats.quality_total_depth_rejections,
919            native_stats.quality_error_rejections,
920            native_stats.quality_makespan_rejections,
921            native_stats.quality_rank_rejections,
922        );
923        if changed {
924            state.analysis = None;
925            state.advance_circuit_revision();
926        }
927        state.current = result.circuit;
928        state.changed |= changed;
929        state.steps.push(WorkflowStepReport {
930            stage: "optimization",
931            name: "optimize.native_fixed_point",
932            changed,
933            skipped: false,
934            reason: Some(reason),
935        });
936        state.native_quality_checkpoint = Some(exact_quality);
937        Ok(())
938    }
939
940    fn validate_device(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
941        let Some(target) = self.strict_device_target() else {
942            state.record_skipped(
943                "validation",
944                "validate.device",
945                self.strict_device_skip_reason(),
946            );
947            return Ok(());
948        };
949        target.device.validate_circuit(&state.current)?;
950        state.steps.push(WorkflowStepReport {
951            stage: "validation",
952            name: "validate.device",
953            changed: false,
954            skipped: false,
955            reason: None,
956        });
957        Ok(())
958    }
959
960    /// Validates the final circuit against a topology-basis target.
961    ///
962    /// This validator is deliberately independent from SABRE target
963    /// preparation. It checks usable physical qubits, undirected connectivity,
964    /// and the explicit output basis without constructing a planning session or
965    /// native-plan catalog.
966    fn validate_topology_target(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
967        let CompileTarget::TopologyBasis {
968            device_target,
969            basis,
970        } = &self.config.target
971        else {
972            return Ok(());
973        };
974        let allowed = basis
975            .iter()
976            .filter_map(|instruction| match instruction {
977                Instruction::Standard(gate) => Some(*gate),
978                _ => None,
979            })
980            .collect::<std::collections::HashSet<_>>();
981        validate_operations_in_target_basis(state.current.operations(), &allowed)?;
982        validate_operations_on_topology(state.current.operations(), &device_target.device)?;
983        state.steps.push(WorkflowStepReport {
984            stage: "validation",
985            name: "validate.topology",
986            changed: false,
987            skipped: false,
988            reason: None,
989        });
990        Ok(())
991    }
992
993    /// Builds the rewrite configuration for a workflow phase.
994    ///
995    /// Rewrite phases use the production optimizer. Enhanced mode only
996    /// increases bounded search budgets rather than changing correctness
997    /// requirements.
998    ///
999    /// Compiler invariant: once a circuit has been physically routed, every
1000    /// two-qubit operation acts on a device coupling edge, so any rewrite
1001    /// phase that runs after routing must preserve undirected two-qubit
1002    /// connectivity. New physical phases added to the workflow must opt into
1003    /// this policy; pre-routing phases and basis-only cleanup (which never
1004    /// routes) stay unrestricted.
1005    fn rewrite_config(&self, phase: RewritePhase) -> Result<RewriteConfig, CompilerError> {
1006        let preserve_connectivity = match phase {
1007            RewritePhase::PreDecomposition | RewritePhase::PostDecomposition => false,
1008            RewritePhase::PostRouting => true,
1009            RewritePhase::TargetCleanup => self.routing_device_target().is_some(),
1010        };
1011        let mut config =
1012            RewriteConfig::production().with_preserve_two_qubit_connectivity(preserve_connectivity);
1013
1014        if self.config.mode == CompileMode::Enhanced {
1015            config = config
1016                .with_max_rounds(16)
1017                .with_max_window_ops(32)
1018                .with_max_pattern_len(12);
1019        }
1020
1021        Ok(config)
1022    }
1023
1024    fn target_cleanup_config(
1025        &self,
1026        state: &WorkflowState,
1027    ) -> Result<Option<RewriteConfig>, CompilerError> {
1028        let Some(prepared) = state.prepared_target_basis.as_ref() else {
1029            return Ok(None);
1030        };
1031
1032        self.rewrite_config(RewritePhase::TargetCleanup)?
1033            .with_target_instructions(prepared.instructions.to_vec())
1034            .map(Some)
1035    }
1036
1037    fn apply_definition_decomposition(
1038        &self,
1039        state: &mut WorkflowState,
1040    ) -> Result<(), CompilerError> {
1041        let decomposer = DecomposeDefinitions;
1042        if state.skip_if_proven_noop("init", "decompose.definitions", &decomposer) {
1043            return Ok(());
1044        }
1045        state.apply_transform("init", "decompose.definitions", |circuit, analysis| {
1046            decomposer.transform(circuit, Some(analysis))
1047        })?;
1048        Ok(())
1049    }
1050
1051    fn apply_unitary_decomposition(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
1052        let config = self.unitary_decompose_config_for_state(state);
1053        let scheduling_decomposer = DecomposeUnitaries::new(config.clone());
1054        if state.skip_if_proven_noop("translation", "decompose.unitary", &scheduling_decomposer) {
1055            return Ok(());
1056        }
1057        let decomposer = if let Some(target) = self.strict_device_target() {
1058            let planning_session = state.planning_session.clone().ok_or_else(|| {
1059                CompilerError::InvariantViolation(
1060                    "strict device workflow has no planning session".to_string(),
1061                )
1062            })?;
1063            let context = DeviceTwoQubitSynthesisContext::build_with_session(
1064                &target.device,
1065                &state.current,
1066                DeviceSynthesisPlacement::PreLayoutEnvelope,
1067                planning_session,
1068            )?;
1069            DecomposeUnitaries::new_device_aware(config, context)
1070        } else {
1071            DecomposeUnitaries::new(config)
1072        };
1073        state.apply_transform("translation", "decompose.unitary", |circuit, analysis| {
1074            decomposer.transform(circuit, Some(analysis))
1075        })?;
1076        Ok(())
1077    }
1078
1079    fn unitary_decompose_config_for_state(&self, state: &WorkflowState) -> UnitaryDecomposeConfig {
1080        UnitaryDecomposeConfig {
1081            two_qubit_target: state.two_qubit_target.clone(),
1082            ..Default::default()
1083        }
1084    }
1085
1086    fn apply_mc_gate_decomposition(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
1087        let config = self.mc_gate_decompose_config();
1088        let decomposer = DecomposeMcGates::new(config);
1089        if state.skip_if_proven_noop("translation", "decompose.mc_gates", &decomposer) {
1090            return Ok(());
1091        }
1092        state.apply_transform("translation", "decompose.mc_gates", |circuit, analysis| {
1093            decomposer.transform(circuit, Some(analysis))
1094        })?;
1095        Ok(())
1096    }
1097
1098    fn apply_two_qubit_resynthesis(
1099        &self,
1100        state: &mut WorkflowState,
1101        stage: &'static str,
1102        name: &'static str,
1103        placement: DeviceSynthesisPlacement,
1104    ) -> Result<bool, CompilerError> {
1105        let config = self.two_qubit_resynthesis_config_for_state(state);
1106        let device_context = if let Some(target) = self
1107            .strict_device_target()
1108            .filter(|_| ResynthesizeTwoQubitBlocks::is_applicable(&state.current))
1109        {
1110            let planning_session = state.planning_session.clone().ok_or_else(|| {
1111                CompilerError::InvariantViolation(
1112                    "strict device workflow has no planning session".to_string(),
1113                )
1114            })?;
1115            let context = DeviceTwoQubitSynthesisContext::build_with_session(
1116                &target.device,
1117                &state.current,
1118                placement,
1119                planning_session,
1120            )?;
1121            Some(context)
1122        } else {
1123            None
1124        };
1125        let mut session = std::mem::take(&mut state.resynthesis_session);
1126        let result = state.apply_transform_without_analysis_with_edits(stage, name, |circuit| {
1127            resynthesize_two_qubit_blocks_workflow(circuit, config, device_context, &mut session)
1128        });
1129        state.resynthesis_session = session;
1130        result
1131    }
1132
1133    fn apply_post_routing_resynthesis(
1134        &self,
1135        state: &mut WorkflowState,
1136    ) -> Result<(), CompilerError> {
1137        if self.routing_device_target().is_none() {
1138            state.record_skipped(
1139                "optimization",
1140                "resynthesize.two_qubit_blocks.post_routing",
1141                "routing was skipped",
1142            );
1143            return Ok(());
1144        }
1145        self.apply_two_qubit_resynthesis(
1146            state,
1147            "optimization",
1148            "resynthesize.two_qubit_blocks.post_routing",
1149            DeviceSynthesisPlacement::ExactPhysical,
1150        )?;
1151        Ok(())
1152    }
1153
1154    fn two_qubit_resynthesis_config_for_state(
1155        &self,
1156        state: &WorkflowState,
1157    ) -> TwoQubitBlockResynthesisConfig {
1158        match self.config.mode {
1159            CompileMode::Normal => {
1160                TwoQubitBlockResynthesisConfig::normal(state.two_qubit_target.clone())
1161            }
1162            CompileMode::Enhanced => {
1163                TwoQubitBlockResynthesisConfig::enhanced(state.two_qubit_target.clone())
1164            }
1165        }
1166    }
1167
1168    fn apply_routing_basis_decomposition(
1169        &self,
1170        state: &mut WorkflowState,
1171    ) -> Result<(), CompilerError> {
1172        if self.routing_device_target().is_none() {
1173            state.record_skipped(
1174                "translation",
1175                "decompose.routing_basis",
1176                "no target device configured",
1177            );
1178            return Ok(());
1179        }
1180
1181        if state.skip_if_proven_noop(
1182            "translation",
1183            "decompose.routing_basis",
1184            &LowerToRoutingBasis::default(),
1185        ) {
1186            return Ok(());
1187        }
1188
1189        let preferred_basis = state
1190            .prepared_target_basis
1191            .as_ref()
1192            .map(|prepared| prepared.instructions.to_vec());
1193        state.apply_transform(
1194            "translation",
1195            "decompose.routing_basis",
1196            |circuit, analysis| {
1197                LowerToRoutingBasis::new(preferred_basis).transform(circuit, Some(analysis))
1198            },
1199        )?;
1200        Ok(())
1201    }
1202
1203    fn mc_gate_decompose_config(&self) -> McGateDecomposeConfig {
1204        McGateDecomposeConfig {
1205            resource_policy: self.config.resource_policy,
1206            resource_limits: self.resource_limits(),
1207        }
1208    }
1209
1210    fn resource_limits(&self) -> ResourceLimits {
1211        ResourceLimits {
1212            max_total_qubits: self
1213                .routing_device_target()
1214                .map(|target| target.device.num_usable_qubits()),
1215        }
1216    }
1217
1218    /// Performs capacity-style resource preflight before lowering starts.
1219    ///
1220    /// Detailed ancillary leasing is still enforced by the decomposition
1221    /// resource manager when a specific synthesis candidate is selected.
1222    fn validate_resources(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
1223        let resource_limits = self.resource_limits();
1224        if let Some(max_total_qubits) = resource_limits.max_total_qubits
1225            && state.current.num_qubits() > max_total_qubits
1226        {
1227            return Err(CompilerError::InvalidInput(format!(
1228                "source circuit uses {} logical qubits but target capacity is {max_total_qubits}",
1229                state.current.num_qubits()
1230            )));
1231        }
1232        state.steps.push(WorkflowStepReport {
1233            stage: "pre_init",
1234            name: "validate.resources",
1235            changed: false,
1236            skipped: false,
1237            reason: resource_limits
1238                .max_total_qubits
1239                .map(|capacity| format!("target capacity permits {capacity} total logical qubits")),
1240        });
1241        Ok(())
1242    }
1243
1244    /// Runs layout selection and SABRE routing, or records a skipped routing step.
1245    ///
1246    /// A caller-supplied initial layout bypasses layout search but still uses
1247    /// the same SABRE router and trial settings. Without a supplied layout, the
1248    /// workflow uses the topology/direction-only compiler SABRE objective.
1249    fn apply_layout_and_routing(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
1250        let Some(target) = self.routing_device_target() else {
1251            state.record_skipped("routing", "route.sabre", "no target device configured");
1252            return Ok(());
1253        };
1254
1255        let virtual_permutation = state.virtual_permutation.clone().ok_or_else(|| {
1256            CompilerError::InvariantViolation(
1257                "routing started before virtual-permutation preparation".to_string(),
1258            )
1259        })?;
1260
1261        let device = &target.device;
1262        let config = sabre_config_for_mode(self.config.mode, target.seed);
1263        let (
1264            routed_circuit,
1265            route_edits,
1266            route_changed,
1267            swap_count,
1268            trials_evaluated,
1269            supplied_layout,
1270        ) = if let Some(initial_layout) = target.initial_layout.as_ref() {
1271            let routed = if let Some(session) = state.planning_session.as_deref() {
1272                let physical = state.routing_physical.as_deref().ok_or_else(|| {
1273                    CompilerError::InvariantViolation(
1274                        "strict-device routing has no prepared physical graph".to_string(),
1275                    )
1276                })?;
1277                route_with_layout_tracked_with_session_on_physical(
1278                    &state.current,
1279                    device,
1280                    physical,
1281                    initial_layout,
1282                    &config,
1283                    session,
1284                )?
1285            } else {
1286                let routing = state.topology_routing_target.as_deref().ok_or_else(|| {
1287                    CompilerError::InvariantViolation(
1288                        "topology routing has no prepared routing target".to_string(),
1289                    )
1290                })?;
1291                route_with_layout_tracked_on_topology(
1292                    &state.current,
1293                    routing,
1294                    initial_layout,
1295                    &config,
1296                )?
1297            };
1298            let route_changed = routed.routed().changed(&state.current);
1299            let swap_count = routed.routed().swap_count();
1300            let trials_evaluated = routed.routed().diagnostics().trials_evaluated;
1301            let final_layout =
1302                virtual_permutation.compose_final_layout(routed.routed().final_layout())?;
1303            state.device_metadata = Some(DeviceCompilationMetadata {
1304                initial_layout: routed.routed().initial_layout().clone(),
1305                final_layout,
1306                virtual_permutation: virtual_permutation.clone(),
1307            });
1308            let edits = routed.rewrite_edits(&state.current);
1309            (
1310                routed.into_routed().into_circuit(),
1311                edits,
1312                route_changed,
1313                swap_count,
1314                trials_evaluated,
1315                true,
1316            )
1317        } else {
1318            let objective = LayoutObjective::topology_only();
1319            let routed = if let Some(session) = state.planning_session.as_deref() {
1320                let physical = state.routing_physical.clone().ok_or_else(|| {
1321                    CompilerError::InvariantViolation(
1322                        "strict-device routing has no prepared physical graph".to_string(),
1323                    )
1324                })?;
1325                route_sabre_tracked_with_session_on_physical(
1326                    &state.current,
1327                    device,
1328                    &objective,
1329                    &config,
1330                    physical,
1331                    session,
1332                )?
1333            } else {
1334                let physical = state.routing_physical.clone().ok_or_else(|| {
1335                    CompilerError::InvariantViolation(
1336                        "topology routing has no prepared physical graph".to_string(),
1337                    )
1338                })?;
1339                let routing = state.topology_routing_target.clone().ok_or_else(|| {
1340                    CompilerError::InvariantViolation(
1341                        "topology routing has no prepared routing target".to_string(),
1342                    )
1343                })?;
1344                route_sabre_tracked_on_topology(
1345                    &state.current,
1346                    &objective,
1347                    &config,
1348                    physical,
1349                    routing,
1350                )?
1351            };
1352            let route_changed = routed.routed().changed(&state.current);
1353            let swap_count = routed.routed().swap_count();
1354            let trials_evaluated = routed.routed().diagnostics().trials_evaluated;
1355            let final_layout =
1356                virtual_permutation.compose_final_layout(routed.routed().final_layout())?;
1357            state.device_metadata = Some(DeviceCompilationMetadata {
1358                initial_layout: routed.routed().initial_layout().clone(),
1359                final_layout,
1360                virtual_permutation: virtual_permutation.clone(),
1361            });
1362            let edits = routed.rewrite_edits(&state.current);
1363            (
1364                routed.into_routed().into_circuit(),
1365                edits,
1366                route_changed,
1367                swap_count,
1368                trials_evaluated,
1369                false,
1370            )
1371        };
1372        if route_changed {
1373            state.adopt_changed_circuit(routed_circuit, &route_edits);
1374        } else {
1375            state.current = routed_circuit;
1376        }
1377
1378        let reason = if supplied_layout {
1379            format!(
1380                "inserted {} swap operations using {} routing trials from supplied initial layout",
1381                swap_count, trials_evaluated
1382            )
1383        } else {
1384            format!(
1385                "inserted {} swap operations using {} routing trials",
1386                swap_count, trials_evaluated
1387            )
1388        };
1389        state.steps.push(WorkflowStepReport {
1390            stage: "routing",
1391            name: "route.sabre",
1392            changed: route_changed,
1393            skipped: false,
1394            reason: Some(reason),
1395        });
1396        Ok(())
1397    }
1398
1399    /// Runs the bounded two-tier route beam after candidate zero has completed
1400    /// the entire workflow. Every exploratory route starts from the immutable
1401    /// pre-routing prefix and traverses the same post-routing, lowering,
1402    /// native-optimization, and validation suffix. Candidate zero remains the
1403    /// fallback unless a finalized branch strictly improves two-qubit count or
1404    /// depth while preserving its exact native quality in every control-flow
1405    /// scope.
1406    fn apply_sabre_pareto_beam(
1407        &self,
1408        state: &mut WorkflowState,
1409        prefix: WorkflowState,
1410    ) -> Result<(), CompilerError> {
1411        let target = self.strict_device_target().ok_or_else(|| {
1412            CompilerError::InvariantViolation(
1413                "SABRE Pareto beam requires a strict device target".to_string(),
1414            )
1415        })?;
1416        let baseline_quality = state.native_quality_checkpoint.clone().ok_or_else(|| {
1417            CompilerError::InvariantViolation(
1418                "candidate zero completed without an exact native quality checkpoint".to_string(),
1419            )
1420        })?;
1421        let baseline_summary = baseline_quality.summary();
1422        let physical = prefix.routing_physical.clone().ok_or_else(|| {
1423            CompilerError::InvariantViolation(
1424                "SABRE Pareto beam has no prepared physical graph".to_string(),
1425            )
1426        })?;
1427        let planning_session = prefix.planning_session.as_deref().ok_or_else(|| {
1428            CompilerError::InvariantViolation(
1429                "SABRE Pareto beam has no device planning session".to_string(),
1430            )
1431        })?;
1432        let config = sabre_pareto_config(target.seed);
1433        let mut search = match prepare_sabre_pareto_routes_with_session_on_physical(
1434            &prefix.current,
1435            &target.device,
1436            &config,
1437            physical,
1438            planning_session,
1439            target.initial_layout.as_ref(),
1440        ) {
1441            Ok(search) => search,
1442            Err(error) if pareto_exploration_error_is_recoverable(&error) => {
1443                state.steps.push(WorkflowStepReport {
1444                    stage: "selection",
1445                    name: "select.sabre_pareto_beam",
1446                    changed: false,
1447                    skipped: false,
1448                    reason: Some(format!(
1449                        "accepted=false; contract=exact_scope_pareto_with_2q_gain; candidate0_retained=true; finalized_candidates=1; {}; exploratory search unavailable: {error}",
1450                        sabre_pareto_quality_log(baseline_summary, None),
1451                    )),
1452                });
1453                return Ok(());
1454            }
1455            Err(error) => return Err(error),
1456        };
1457
1458        let mut finalized_candidates = 1usize;
1459        let mut attempted_candidates = 0usize;
1460        let mut discarded_candidates = 0usize;
1461        let mut first_discarded_error = None::<String>;
1462        let mut route_candidates = Vec::new();
1463        let mut tiers_evaluated = 0usize;
1464        for (tier, route_limit) in [(1usize, 8usize), (2, 20)] {
1465            tiers_evaluated = tier;
1466            match search.extend_to(route_limit, 3) {
1467                Ok(candidates) => route_candidates
1468                    .extend(candidates.into_iter().map(|candidate| (tier, candidate))),
1469                Err(error) if pareto_exploration_error_is_recoverable(&error) => {
1470                    discarded_candidates = discarded_candidates.saturating_add(1);
1471                    first_discarded_error.get_or_insert_with(|| error.to_string());
1472                    break;
1473                }
1474                Err(error) => return Err(error),
1475            }
1476        }
1477
1478        let mut selected = None::<(
1479            WorkflowState,
1480            NativeExactQualityCheckpoint,
1481            usize,
1482            usize,
1483            usize,
1484        )>;
1485        for (tier, candidate) in route_candidates {
1486            let id = candidate.id;
1487            attempted_candidates = attempted_candidates.saturating_add(1);
1488            let branch = match self.finalize_sabre_pareto_candidate(&prefix, candidate) {
1489                Ok(branch) => branch,
1490                Err(error) if pareto_exploration_error_is_recoverable(&error) => {
1491                    discarded_candidates = discarded_candidates.saturating_add(1);
1492                    first_discarded_error.get_or_insert_with(|| error.to_string());
1493                    continue;
1494                }
1495                Err(error) => return Err(error),
1496            };
1497            finalized_candidates = finalized_candidates.saturating_add(1);
1498            let quality = branch.native_quality_checkpoint.clone().ok_or_else(|| {
1499                CompilerError::InvariantViolation(
1500                    "finalized SABRE Pareto candidate has no exact quality checkpoint".to_string(),
1501                )
1502            })?;
1503            if !quality.strictly_dominates_with_two_qubit_gain(&baseline_quality) {
1504                continue;
1505            }
1506            let replace = selected.as_ref().is_none_or(
1507                |(_, incumbent, _, incumbent_layout, incumbent_route)| {
1508                    let ordering = quality.compare_for_sabre_beam(incumbent);
1509                    ordering.is_lt()
1510                        || ordering.is_eq()
1511                            && (id.layout_index, id.route_index)
1512                                < (*incumbent_layout, *incumbent_route)
1513                },
1514            );
1515            if replace {
1516                selected = Some((branch, quality, tier, id.layout_index, id.route_index));
1517            }
1518        }
1519
1520        let winner_summary = selected
1521            .as_ref()
1522            .map(|(_, quality, _, _, _)| quality.summary());
1523        let (changed, accepted, selected_tier, selected_layout, selected_route) =
1524            if let Some((branch, _, tier, layout, route)) = selected {
1525                let changed = branch.current != state.current
1526                    || branch.device_metadata != state.device_metadata;
1527                *state = branch;
1528                (changed, true, Some(tier), Some(layout), Some(route))
1529            } else {
1530                (false, false, None, None, None)
1531            };
1532        state.steps.push(WorkflowStepReport {
1533            stage: "selection",
1534            name: "select.sabre_pareto_beam",
1535            changed,
1536            skipped: false,
1537            reason: Some(format!(
1538                "accepted={accepted}; contract=exact_scope_pareto_with_2q_gain; candidate0_retained={}; tiers_evaluated={tiers_evaluated}; finalized_candidates={finalized_candidates}; attempted_candidates={attempted_candidates}; discarded_candidates={discarded_candidates}; selected_tier={selected_tier:?}; selected_layout={selected_layout:?}; selected_route={selected_route:?}; {}; first_discarded_error={first_discarded_error:?}",
1539                !accepted,
1540                sabre_pareto_quality_log(baseline_summary, winner_summary),
1541            )),
1542        });
1543        Ok(())
1544    }
1545
1546    fn finalize_sabre_pareto_candidate(
1547        &self,
1548        prefix: &WorkflowState,
1549        candidate: SabreParetoRouteCandidate,
1550    ) -> Result<WorkflowState, CompilerError> {
1551        let target = self.strict_device_target().ok_or_else(|| {
1552            CompilerError::InvariantViolation(
1553                "SABRE Pareto candidate requires a strict device target".to_string(),
1554            )
1555        })?;
1556        let mut branch = prefix.detached_clone();
1557        let virtual_permutation = branch.virtual_permutation.clone().ok_or_else(|| {
1558            CompilerError::InvariantViolation(
1559                "SABRE Pareto candidate has no virtual permutation".to_string(),
1560            )
1561        })?;
1562        let routed = candidate.routed;
1563        let route_changed = routed.routed().changed(&prefix.current);
1564        let swap_count = routed.routed().swap_count();
1565        let trials_evaluated = routed.routed().diagnostics().trials_evaluated;
1566        let final_layout =
1567            virtual_permutation.compose_final_layout(routed.routed().final_layout())?;
1568        branch.device_metadata = Some(DeviceCompilationMetadata {
1569            initial_layout: routed.routed().initial_layout().clone(),
1570            final_layout,
1571            virtual_permutation,
1572        });
1573        let edits = routed.rewrite_edits(&prefix.current);
1574        let routed_circuit = routed.into_routed().into_circuit();
1575        if route_changed {
1576            branch.adopt_changed_circuit(routed_circuit, &edits);
1577        } else {
1578            branch.current = routed_circuit;
1579        }
1580        branch.steps.push(WorkflowStepReport {
1581            stage: "routing",
1582            name: "route.sabre",
1583            changed: route_changed,
1584            skipped: false,
1585            reason: Some(format!(
1586                "exploratory Pareto route layout={} trial={}; inserted {swap_count} swap operations using {trials_evaluated} routing trials{}",
1587                candidate.id.layout_index,
1588                candidate.id.route_index,
1589                if target.initial_layout.is_some() {
1590                    " from supplied initial layout"
1591                } else {
1592                    ""
1593                },
1594            )),
1595        });
1596
1597        self.finish_post_routing(&mut branch)?;
1598        Ok(branch)
1599    }
1600
1601    /// Removes static logical SWAPs before numeric two-qubit optimization can
1602    /// absorb them into a strictly equivalent unitary realization.
1603    ///
1604    /// The resulting output permutation remains valid through later logical
1605    /// rewrites because those stages preserve logical wire identity.
1606    fn apply_virtual_permutation_elision(
1607        &self,
1608        state: &mut WorkflowState,
1609    ) -> Result<(), CompilerError> {
1610        if self.routing_device_target().is_none() {
1611            state.record_skipped(
1612                "optimization",
1613                "optimize.virtual_permutation",
1614                "no target device configured",
1615            );
1616            return Ok(());
1617        }
1618
1619        let elision = elide_virtual_permutations(&state.current)?;
1620        let elision_status = elision.status();
1621        let elided_swap_count = elision.elided_swap_count();
1622        state.virtual_permutation = Some(elision.virtual_permutation().clone());
1623        match elision_status {
1624            VirtualPermutationElisionStatus::Changed => {
1625                let circuit = elision.into_changed_circuit().ok_or_else(|| {
1626                    CompilerError::InvariantViolation(
1627                        "changed virtual-permutation result did not contain a circuit".to_string(),
1628                    )
1629                })?;
1630                let edits = RewriteEdits::between_linear_circuits(&state.current, &circuit);
1631                state.adopt_changed_circuit(circuit, &edits);
1632                state.steps.push(WorkflowStepReport {
1633                    stage: "optimization",
1634                    name: "optimize.virtual_permutation",
1635                    changed: true,
1636                    skipped: false,
1637                    reason: Some(format!(
1638                        "represented {elided_swap_count} logical swap operations as an output permutation"
1639                    )),
1640                });
1641            }
1642            VirtualPermutationElisionStatus::Unchanged => {
1643                state.record_skipped(
1644                    "optimization",
1645                    "optimize.virtual_permutation",
1646                    "no eligible logical swap operations",
1647                );
1648            }
1649            VirtualPermutationElisionStatus::SkippedControlFlow => {
1650                state.record_skipped(
1651                    "optimization",
1652                    "optimize.virtual_permutation",
1653                    "structured control flow may have path-dependent output permutations",
1654                );
1655            }
1656        }
1657        Ok(())
1658    }
1659
1660    fn apply_post_routing_cleanup(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
1661        if self.routing_device_target().is_none() {
1662            state.record_skipped(
1663                "optimization",
1664                "optimize.post_routing",
1665                "routing was skipped",
1666            );
1667            return Ok(());
1668        }
1669
1670        let rewrite_config = self.rewrite_config(RewritePhase::PostRouting)?;
1671        state.apply_knowledge_rewrite("optimization", "optimize.post_routing", rewrite_config)?;
1672        Ok(())
1673    }
1674
1675    fn apply_commutative_cancellation(
1676        &self,
1677        state: &mut WorkflowState,
1678        name: &'static str,
1679    ) -> Result<bool, CompilerError> {
1680        state.apply_transform_without_analysis_with_edits("optimization", name, |circuit| {
1681            CommutativeCancellation::new().transform_with_rewrite_edits(circuit)
1682        })
1683    }
1684
1685    fn apply_one_qubit_optimization(
1686        &self,
1687        state: &mut WorkflowState,
1688        name: &'static str,
1689    ) -> Result<bool, CompilerError> {
1690        let Some(optimizer) = state.one_qubit_optimizer.take() else {
1691            state.record_skipped(
1692                "optimization",
1693                name,
1694                "device target uses exact-physical native one-qubit optimization",
1695            );
1696            return Ok(false);
1697        };
1698        let result =
1699            state.apply_transform_without_analysis_with_edits("optimization", name, |circuit| {
1700                optimizer.transform_with_rewrite_edits(circuit)
1701            });
1702        state.one_qubit_optimizer = Some(optimizer);
1703        result
1704    }
1705
1706    fn close_one_qubit_resynthesis(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
1707        if matches!(self.config.target, CompileTarget::Device(_)) {
1708            state.record_skipped(
1709                "optimization",
1710                "optimize.one_qubit_fixed_point",
1711                "device target closes local optimization after exact native lowering",
1712            );
1713            state.pending_one_qubit_resynthesis = false;
1714            return Ok(());
1715        }
1716
1717        let max_rounds = match self.config.mode {
1718            CompileMode::Normal => 2,
1719            CompileMode::Enhanced => 4,
1720        };
1721        let before_revision = state.circuit_revision;
1722        let mut rounds = 0u8;
1723        let mut pending_resynthesis = state.pending_one_qubit_resynthesis;
1724        while rounds < max_rounds {
1725            rounds += 1;
1726            let changed_cc = self.apply_commutative_cancellation(
1727                state,
1728                "optimize.commutative_cancellation.after_rewrite",
1729            )?;
1730            let changed_1q =
1731                self.apply_one_qubit_optimization(state, "optimize.one_qubit.after_rewrite")?;
1732            // The pre-loop pending flag is consumed exactly once: after this
1733            // point the flag is only ever reassigned from `cleanup_changed`.
1734            let should_resynthesize = pending_resynthesis || changed_cc || changed_1q;
1735            if !should_resynthesize {
1736                break;
1737            }
1738
1739            let resynthesis_changed = self.apply_two_qubit_resynthesis(
1740                state,
1741                "optimization",
1742                "resynthesize.two_qubit_blocks.after_one_qubit",
1743                DeviceSynthesisPlacement::PreLayoutEnvelope,
1744            )?;
1745            let cleanup_changed = if resynthesis_changed {
1746                self.apply_one_qubit_optimization(state, "optimize.one_qubit.after_resynthesis")?
1747            } else {
1748                state.record_skipped(
1749                    "optimization",
1750                    "optimize.one_qubit.after_resynthesis",
1751                    "two-qubit resynthesis was stable",
1752                );
1753                false
1754            };
1755            let round_changed = changed_cc || changed_1q || resynthesis_changed || cleanup_changed;
1756            pending_resynthesis = cleanup_changed;
1757            if !round_changed {
1758                break;
1759            }
1760        }
1761        state.pending_one_qubit_resynthesis = false;
1762        state.steps.push(WorkflowStepReport {
1763            stage: "optimization",
1764            name: "optimize.one_qubit_fixed_point",
1765            changed: state.circuit_revision != before_revision,
1766            skipped: false,
1767            reason: Some(format!("rounds={rounds}; max_rounds={max_rounds}")),
1768        });
1769        Ok(())
1770    }
1771
1772    fn apply_target_translation(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
1773        self.apply_target_translation_named(state, "translate.target_basis")
1774    }
1775
1776    fn apply_target_translation_named(
1777        &self,
1778        state: &mut WorkflowState,
1779        name: &'static str,
1780    ) -> Result<(), CompilerError> {
1781        let Some(prepared) = state.prepared_target_basis.as_ref() else {
1782            state.record_skipped("translation", name, "no target basis configured");
1783            return Ok(());
1784        };
1785        let lowerer = Arc::clone(&prepared.lowerer);
1786
1787        if state.skip_if_proven_noop("translation", name, lowerer.as_ref()) {
1788            return Ok(());
1789        }
1790
1791        state.apply_transform("translation", name, |circuit, analysis| {
1792            lowerer.transform(circuit, Some(analysis))
1793        })?;
1794        Ok(())
1795    }
1796
1797    fn validate_explicit_target_basis(
1798        &self,
1799        state: &mut WorkflowState,
1800    ) -> Result<(), CompilerError> {
1801        let Some(prepared) = state.prepared_target_basis.as_ref() else {
1802            return Ok(());
1803        };
1804        let allowed = prepared
1805            .instructions
1806            .iter()
1807            .filter_map(|instruction| match instruction {
1808                Instruction::Standard(gate) => Some(*gate),
1809                _ => None,
1810            })
1811            .collect::<std::collections::HashSet<_>>();
1812        validate_operations_in_target_basis(state.current.operations(), &allowed)
1813    }
1814
1815    fn prepared_target(&self) -> Result<&PreparedCompileTarget, CompilerError> {
1816        if let Some(prepared) = self.prepared_target.get() {
1817            return Ok(prepared);
1818        }
1819        let _guard = self
1820            .prepare_lock
1821            .lock()
1822            .unwrap_or_else(|poisoned| poisoned.into_inner());
1823        if let Some(prepared) = self.prepared_target.get() {
1824            return Ok(prepared);
1825        }
1826        let prepared = Arc::new(PreparedCompileTarget::new(&self.config)?);
1827        self.prepared_target.set(prepared).map_err(|_| {
1828            CompilerError::InvariantViolation(
1829                "compiler target was initialized concurrently while holding its preparation lock"
1830                    .to_string(),
1831            )
1832        })?;
1833        self.prepared_target.get().map(Arc::as_ref).ok_or_else(|| {
1834            CompilerError::InvariantViolation(
1835                "compiler target preparation completed without publishing its result".to_string(),
1836            )
1837        })
1838    }
1839
1840    fn record_pre_init(&self, state: &mut WorkflowState) {
1841        let reason = match (&self.config.target, &state.prepared_target_basis) {
1842            (CompileTarget::Basis(_), Some(prepared)) => Some(format!(
1843                "resolved explicit target basis with {} instructions",
1844                prepared.instructions.len()
1845            )),
1846            (CompileTarget::Device(_), _) => {
1847                Some("resolved device target with ordered native capabilities".to_string())
1848            }
1849            (CompileTarget::TopologyBasis { .. }, Some(prepared)) => Some(format!(
1850                "resolved device topology with explicit target basis containing {} instructions",
1851                prepared.instructions.len()
1852            )),
1853            (CompileTarget::Logical, _) => Some("no target constraints configured".to_string()),
1854            (CompileTarget::Basis(_) | CompileTarget::TopologyBasis { .. }, None) => None,
1855        };
1856
1857        state.steps.push(WorkflowStepReport {
1858            stage: "pre_init",
1859            name: "resolve.target",
1860            changed: false,
1861            skipped: false,
1862            reason,
1863        });
1864    }
1865
1866    fn routing_device_target(&self) -> Option<&DeviceCompileTarget> {
1867        match &self.config.target {
1868            CompileTarget::Device(target) => Some(target),
1869            CompileTarget::TopologyBasis { device_target, .. } => Some(device_target),
1870            CompileTarget::Logical | CompileTarget::Basis(_) => None,
1871        }
1872    }
1873
1874    fn strict_device_target(&self) -> Option<&DeviceCompileTarget> {
1875        match &self.config.target {
1876            CompileTarget::Device(target) => Some(target),
1877            CompileTarget::Logical
1878            | CompileTarget::Basis(_)
1879            | CompileTarget::TopologyBasis { .. } => None,
1880        }
1881    }
1882
1883    fn strict_device_skip_reason(&self) -> &'static str {
1884        if matches!(self.config.target, CompileTarget::TopologyBasis { .. }) {
1885            "topology target does not require exact device-native output"
1886        } else {
1887            "no target device configured"
1888        }
1889    }
1890}
1891
1892fn sabre_config_for_mode(mode: CompileMode, seed: Option<u32>) -> SabreConfig {
1893    let mut config = SabreConfig {
1894        seed: seed.map(u64::from),
1895        ..SabreConfig::default()
1896    };
1897
1898    if mode == CompileMode::Enhanced {
1899        config.layout_trials = 24;
1900        config.layout_assignment_budget = 5_000_000;
1901        if let Some(vf2) = &mut config.vf2_prepass {
1902            vf2.call_limit = 5_000_000;
1903        }
1904        config.refinement_iterations = 2;
1905        config.routing_trials = 2;
1906    }
1907
1908    config
1909}
1910
1911fn sabre_pareto_config(seed: Option<u32>) -> SabreConfig {
1912    let mut config = sabre_config_for_mode(CompileMode::Enhanced, seed);
1913    config.refinement_iterations = 4;
1914    config.routing_trials = 20;
1915    config
1916}
1917
1918fn pareto_exploration_error_is_recoverable(error: &CompilerError) -> bool {
1919    matches!(
1920        error,
1921        CompilerError::Circuit(_)
1922            | CompilerError::InvalidInput(_)
1923            | CompilerError::TransformFailed { .. }
1924            | CompilerError::SabreRoutingFailed(_)
1925            | CompilerError::DeviceLoweringFailed(_)
1926            | CompilerError::DeviceValidationFailed(_)
1927    )
1928}
1929
1930fn sabre_pareto_quality_log(
1931    candidate0: NativeOptimizationSummary,
1932    winner: Option<NativeOptimizationSummary>,
1933) -> String {
1934    let Some(winner) = winner else {
1935        return format!(
1936            "candidate0_native_2q_ops={}; candidate0_native_2q_depth={}; candidate0_native_depth={}; candidate0_native_ops={}; winner_quality=None",
1937            candidate0.native_two_qubit_ops,
1938            candidate0.native_two_qubit_depth,
1939            candidate0.total_native_depth,
1940            candidate0.native_total_ops,
1941        );
1942    };
1943    format!(
1944        "native_2q_ops={}->{}; native_2q_depth={}->{}; native_depth={}->{}; native_ops={}->{}; predicted_log_error={:?}->{:?}",
1945        candidate0.native_two_qubit_ops,
1946        winner.native_two_qubit_ops,
1947        candidate0.native_two_qubit_depth,
1948        winner.native_two_qubit_depth,
1949        candidate0.total_native_depth,
1950        winner.total_native_depth,
1951        candidate0.native_total_ops,
1952        winner.native_total_ops,
1953        candidate0.predicted_log_error,
1954        winner.predicted_log_error,
1955    )
1956}
1957
1958fn validate_workflow_target_basis_config(
1959    target_basis: &[Instruction],
1960) -> Result<(), CompilerError> {
1961    if target_basis.is_empty() {
1962        return Err(CompilerError::InvalidInput(
1963            "workflow target basis must not be empty".to_string(),
1964        ));
1965    }
1966
1967    // Rewrite lowering can represent `McGate` as a target instruction, but the
1968    // current workflow decomposes all multi-controlled gates before target-basis
1969    // translation. Native multi-controlled target support therefore needs an
1970    // explicit workflow policy before it can be accepted here.
1971    for instruction in target_basis {
1972        if !matches!(instruction, Instruction::Standard(_)) {
1973            return Err(CompilerError::InvalidInput(format!(
1974                "unsupported workflow target instruction {instruction:?}"
1975            )));
1976        }
1977    }
1978    Ok(())
1979}
1980
1981fn validate_operations_in_target_basis(
1982    operations: &[Operation],
1983    allowed: &std::collections::HashSet<StandardGate>,
1984) -> Result<(), CompilerError> {
1985    for operation in operations {
1986        match &operation.instruction {
1987            Instruction::Standard(gate)
1988                if *gate != StandardGate::GPhase && !allowed.contains(gate) =>
1989            {
1990                return Err(CompilerError::InvariantViolation(format!(
1991                    "target-basis one-qubit cleanup left gate {gate:?} outside the configured basis"
1992                )));
1993            }
1994            Instruction::ClassicalControl(control) => {
1995                for body in control.bodies() {
1996                    validate_operations_in_target_basis(body.operations(), allowed)?;
1997                }
1998            }
1999            Instruction::McGate(_) | Instruction::UnitaryGate(_) | Instruction::CircuitGate(_) => {
2000                return Err(CompilerError::InvariantViolation(format!(
2001                    "target-basis one-qubit cleanup left gate-like instruction {} outside the configured basis",
2002                    operation.instruction
2003                )));
2004            }
2005            _ => {}
2006        }
2007    }
2008    Ok(())
2009}
2010
2011fn validate_operations_on_topology(
2012    operations: &[Operation],
2013    device: &crate::device::Device,
2014) -> Result<(), CompilerError> {
2015    use crate::device::PhysicalQubit;
2016
2017    for operation in operations {
2018        for qubit in operation.qubits.iter().copied() {
2019            let physical = PhysicalQubit::from_qubit(qubit);
2020            if !device.is_usable_qubit(physical) {
2021                return Err(CompilerError::InvariantViolation(format!(
2022                    "topology-basis output references unusable physical qubit {physical}"
2023                )));
2024            }
2025        }
2026        match &operation.instruction {
2027            Instruction::Standard(gate) if gate.num_qubits() == 2 => {
2028                let [left, right] = operation.qubits.as_slice() else {
2029                    return Err(CompilerError::InvariantViolation(format!(
2030                        "two-qubit gate {gate:?} has {} physical arguments",
2031                        operation.qubits.len()
2032                    )));
2033                };
2034                let left = PhysicalQubit::from_qubit(*left);
2035                let right = PhysicalQubit::from_qubit(*right);
2036                if !device
2037                    .topology()
2038                    .supports_coupling_either_direction(left, right)
2039                {
2040                    return Err(CompilerError::InvariantViolation(format!(
2041                        "topology-basis output gate {gate:?} uses non-adjacent physical qubits {left} and {right}"
2042                    )));
2043                }
2044            }
2045            Instruction::Standard(gate) if gate.num_qubits() > 2 => {
2046                return Err(CompilerError::InvariantViolation(format!(
2047                    "topology-basis output contains undecomposed gate {gate:?}"
2048                )));
2049            }
2050            Instruction::ClassicalControl(control) => {
2051                for body in control.bodies() {
2052                    validate_operations_on_topology(body.operations(), device)?;
2053                }
2054            }
2055            Instruction::McGate(_) | Instruction::UnitaryGate(_) | Instruction::CircuitGate(_) => {
2056                return Err(CompilerError::InvariantViolation(format!(
2057                    "topology-basis output contains undecomposed instruction {}",
2058                    operation.instruction
2059                )));
2060            }
2061            Instruction::Standard(_)
2062            | Instruction::Directive(_)
2063            | Instruction::ClassicalData(_)
2064            | Instruction::Delay => {}
2065        }
2066    }
2067    Ok(())
2068}
2069
2070#[cfg(test)]
2071#[path = "./workflow_test.rs"]
2072mod workflow_test;