1use 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#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct WorkflowStepReport {
97 pub stage: &'static str,
99 pub name: &'static str,
101 pub changed: bool,
103 pub skipped: bool,
105 pub reason: Option<String>,
107}
108
109#[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
162struct 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 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 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 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
534pub struct CompilerWorkflow {
536 config: CompileConfig,
537 prepared_target: OnceLock<Arc<PreparedCompileTarget>>,
538 prepare_lock: Mutex<()>,
539}
540
541impl CompilerWorkflow {
542 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 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 pub const fn config(&self) -> &CompileConfig {
564 &self.config
565 }
566
567 pub fn run(&self, circuit: &Circuit) -> Result<CompileResult, CompilerError> {
570 self.run_owned(circuit.clone())
571 }
572
573 pub fn run_owned(&self, circuit: Circuit) -> Result<CompileResult, CompilerError> {
580 self.run_internal(circuit, false).map(|(result, _)| result)
581 }
582
583 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 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 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 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 fn lower_routing_basis(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
725 self.apply_routing_basis_decomposition(state)
726 }
727
728 fn lower_physical(&self, state: &mut WorkflowState) -> Result<(), CompilerError> {
730 self.apply_layout_and_routing(state)
731 }
732
733 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 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 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 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 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 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 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 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 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 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 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;