Skip to main content

cqlib_core/compile/sabre/
routing.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. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13use super::cost::NativePlanCost;
14use super::dag::{SabreControlFlow, SabreDag, SabreNode, SabreNodeKind, SabreOperation};
15use super::dense_layout::DenseRoutingLayout;
16use super::heuristic::{SabreConfig, SabreHeuristicConfig};
17use super::layer::{Layer, RequirementPlacement};
18use crate::circuit::value_instruction::storage_operation_to_value;
19use crate::circuit::{
20    Circuit, CircuitParam, ClassicalControlOp, ControlBody, ForOp, IfOp, Instruction, Operation,
21    Parameter, Qubit, StandardGate, SwitchCase, SwitchOp, WhileOp,
22};
23use crate::compile::device_planning::{
24    DeviceGateState, DevicePlanningSession, NativePlanAvailability, NativePlanCatalog,
25    NativePlanSummary,
26};
27use crate::compile::error::DeviceLoweringFailure;
28use crate::compile::knowledge::KnowledgeInstructionKey;
29use crate::compile::parallelism::{ParallelismThresholds, parallel_chunk_size};
30use crate::compile::physical_target::PhysicalLayoutGraph;
31use crate::compile::{CompilerError, SabreRoutingFailure, compare_some_first_by};
32use crate::device::{Device, Layout, LogicalQubit, PhysicalQubit};
33use indexmap::IndexSet;
34use rand::SeedableRng;
35use rand::rngs::StdRng;
36use rand::seq::IndexedRandom;
37use rayon::prelude::*;
38use rustworkx_core::petgraph::Direction;
39use rustworkx_core::petgraph::graph::{NodeIndex, UnGraph};
40use rustworkx_core::petgraph::visit::EdgeRef;
41use rustworkx_core::token_swapper::token_swapper;
42use smallvec::{SmallVec, smallvec};
43use std::cell::{Cell, RefCell};
44use std::cmp::{Ordering, Reverse};
45use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
46use std::hash::{DefaultHasher, Hash, Hasher};
47use std::sync::{Arc, Condvar, Mutex};
48
49const CONTROL_FLOW_EPILOGUE_TRIALS: usize = 4;
50const EAGER_PAIR_STATE_BUDGET: usize = 1_000_000;
51const LAZY_PAIR_CACHE_BUDGET: usize = 100_000;
52const TRIAL_PAIR_CACHE_BUDGET: usize = 4_096;
53const HIGH_PAIR_REUSE_FACTOR: usize = 16;
54const WIDE_FRONT_FRACTION: usize = 8;
55/// Approximate routed-DAG visits required to expose fixed-layout trial groups.
56/// Trial counts below two and small circuits stay on the direct serial path.
57const ROUTING_PARALLELISM_THRESHOLDS: ParallelismThresholds =
58    ParallelismThresholds::new(256, 2_048, 32_768);
59
60/// Routed circuit and layout metadata produced by [`sabre_route`].
61#[derive(Debug, Clone)]
62pub struct SabreRoutingResult {
63    /// Physical circuit with inserted SWAP operations.
64    pub circuit: Circuit,
65    /// Initial logical-to-physical layout used by the selected trial.
66    pub initial_layout: Layout,
67    /// Final logical-to-physical layout after all routed operations.
68    pub final_layout: Layout,
69    /// Number of inserted SWAP operations, including control-flow epilogues.
70    pub swap_count: usize,
71    /// Diagnostics describing routing search behavior.
72    pub diagnostics: SabreRoutingDiagnostics,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub(crate) enum RouteOperationProvenance {
77    Source(usize),
78    Inserted,
79}
80
81#[derive(Debug, Clone)]
82pub(crate) struct TrackedSabreRoutingResult {
83    pub(crate) routing: SabreRoutingResult,
84    pub(crate) provenance: Vec<RouteOperationProvenance>,
85}
86
87/// Diagnostics emitted by SABRE routing.
88#[derive(Debug, Clone, Default, PartialEq)]
89pub struct SabreRoutingDiagnostics {
90    /// Number of routing trials evaluated.
91    pub trials_evaluated: usize,
92    /// Zero-based index of the selected routing trial.
93    pub selected_trial_index: usize,
94    /// Number of times the shortest-path fallback was used.
95    pub fallback_count: usize,
96    /// Number of recursively routed control-flow blocks.
97    pub control_flow_blocks_routed: usize,
98    /// ASAP two-qubit depth of the selected routed operation stream.
99    pub two_qubit_depth: usize,
100    /// Total number of operations in the selected routed operation stream.
101    pub operation_count: usize,
102    /// Predicted native two-qubit operation count after device lowering.
103    pub native_two_qubit_count: usize,
104    /// ASAP two-qubit depth using the selected exact-qargs native plans.
105    pub native_two_qubit_depth: usize,
106    /// ASAP total depth using the selected exact-qargs native plans.
107    pub native_total_depth: usize,
108    /// Predicted total native operation count after device lowering.
109    pub native_operation_count: usize,
110    /// Dynamic `for`/`while` loops whose total execution cost is unknown.
111    pub unknown_loop_count: usize,
112    /// Number of distinct unary/pair requirement signatures prepared.
113    pub requirement_signature_count: usize,
114    /// Pair-placement lower-bound states retained eagerly by the target.
115    pub eager_pair_state_count: usize,
116    /// Lazy pair-state lower-bound probes made by the selected trial.
117    pub lazy_pair_l1_lookup_count: usize,
118    /// Selected-trial lazy pair-state probes served by its local L1 cache.
119    pub lazy_pair_l1_hit_count: usize,
120    /// Sum of pair-state entries retained by the selected trial's bounded L1
121    /// cache and the independent L1 caches of all recursively routed bodies.
122    /// This aggregate can exceed one cache's per-trial capacity.
123    pub lazy_pair_l1_cached_count: usize,
124}
125
126#[derive(Debug, Clone)]
127pub(crate) struct TrialResult {
128    pub(crate) operations: Vec<Operation>,
129    pub(crate) provenance: Vec<RouteOperationProvenance>,
130    pub(crate) final_layout: Layout,
131    pub(crate) swap_count: usize,
132    pub(crate) fallback_count: usize,
133    pub(crate) control_flow_blocks_routed: usize,
134    lazy_pair_l1_lookup_count: usize,
135    lazy_pair_l1_hit_count: usize,
136    lazy_pair_l1_cached_count: usize,
137    pub(crate) quality: TrialQuality,
138}
139
140/// Compact per-trial route representation.
141///
142/// Topology-only trials retain ordinary operations by shared source plus mapped
143/// qargs, and inserted SWAPs as physical endpoint pairs. Full operations are
144/// built only when a trial participates in a depth tie or wins the search.
145#[derive(Debug, Clone, Default)]
146struct CompactRoutePlan {
147    steps: Vec<CompactRouteStep>,
148}
149
150#[derive(Debug, Clone)]
151enum CompactRouteStep {
152    Owned {
153        operation: Box<Operation>,
154        source_order: Option<usize>,
155    },
156    Mapped {
157        source: Arc<Operation>,
158        qubits: SmallVec<[Qubit; 3]>,
159        source_order: Option<usize>,
160    },
161    Swap([PhysicalQubit; 2]),
162}
163
164impl CompactRoutePlan {
165    fn push_operation(&mut self, operation: Operation, source_order: Option<usize>) {
166        self.steps.push(CompactRouteStep::Owned {
167            operation: Box::new(operation),
168            source_order,
169        });
170    }
171
172    fn push_mapped_operation(
173        &mut self,
174        source: Arc<Operation>,
175        qubits: SmallVec<[Qubit; 3]>,
176        source_order: Option<usize>,
177    ) {
178        self.steps.push(CompactRouteStep::Mapped {
179            source,
180            qubits,
181            source_order,
182        });
183    }
184
185    fn push_swap(&mut self, swap: [PhysicalQubit; 2]) {
186        self.steps.push(CompactRouteStep::Swap(swap));
187    }
188
189    fn last_operation(&self) -> Option<&Operation> {
190        match self.steps.last() {
191            Some(CompactRouteStep::Owned { operation, .. }) => Some(operation),
192            Some(CompactRouteStep::Mapped { source, .. }) => Some(source),
193            Some(CompactRouteStep::Swap(_)) | None => None,
194        }
195    }
196
197    fn pop_last_operation(&mut self) -> Option<Operation> {
198        match self.steps.last() {
199            Some(CompactRouteStep::Owned { .. } | CompactRouteStep::Mapped { .. }) => {
200                match self.steps.pop() {
201                    Some(CompactRouteStep::Owned { operation, .. }) => Some(*operation),
202                    Some(CompactRouteStep::Mapped { source, qubits, .. }) => Some(Operation {
203                        instruction: source.instruction.clone(),
204                        qubits,
205                        params: source.params.clone(),
206                        label: source.label.clone(),
207                    }),
208                    _ => unreachable!("the last route-plan step was checked as an operation"),
209                }
210            }
211            Some(CompactRouteStep::Swap(_)) | None => None,
212        }
213    }
214
215    fn materialize(&self, target: &RoutingTarget) -> Result<Vec<Operation>, CompilerError> {
216        let mut operations = Vec::with_capacity(self.steps.len());
217        for step in &self.steps {
218            match step {
219                CompactRouteStep::Owned { operation, .. } => operations.push((**operation).clone()),
220                CompactRouteStep::Mapped { source, qubits, .. } => operations.push(Operation {
221                    instruction: source.instruction.clone(),
222                    qubits: qubits.clone(),
223                    params: source.params.clone(),
224                    label: source.label.clone(),
225                }),
226                CompactRouteStep::Swap(swap) => operations.push(target.swap_operation(*swap)?),
227            }
228        }
229        Ok(operations)
230    }
231
232    fn provenance(&self) -> Vec<RouteOperationProvenance> {
233        self.steps
234            .iter()
235            .map(|step| match step {
236                CompactRouteStep::Owned { source_order, .. } => source_order.map_or(
237                    RouteOperationProvenance::Inserted,
238                    RouteOperationProvenance::Source,
239                ),
240                CompactRouteStep::Mapped { source_order, .. } => source_order.map_or(
241                    RouteOperationProvenance::Inserted,
242                    RouteOperationProvenance::Source,
243                ),
244                CompactRouteStep::Swap(_) => RouteOperationProvenance::Inserted,
245            })
246            .collect()
247    }
248}
249
250#[derive(Debug, Clone)]
251pub(crate) struct UnscoredTrial {
252    plan: CompactRoutePlan,
253    pub(crate) final_layout: Layout,
254    pub(crate) swap_count: usize,
255    pub(crate) fallback_count: usize,
256    pub(crate) control_flow_blocks_routed: usize,
257    lazy_pair_l1_lookup_count: usize,
258    lazy_pair_l1_hit_count: usize,
259    lazy_pair_l1_cached_count: usize,
260    native_cost: NativeCircuitCost,
261    two_qubit_operation_count: usize,
262    operation_count: usize,
263}
264
265impl UnscoredTrial {
266    pub(crate) fn abstract_quality(&self) -> AbstractTrialQuality {
267        AbstractTrialQuality {
268            swap_count: self.swap_count,
269            two_qubit_depth: 0,
270            operation_count: self.operation_count,
271            two_qubit_operation_count: self.two_qubit_operation_count,
272        }
273    }
274
275    fn materialize_operations(
276        &self,
277        target: &RoutingTarget,
278    ) -> Result<Vec<Operation>, CompilerError> {
279        self.plan.materialize(target)
280    }
281
282    fn append_swaps(
283        &mut self,
284        target: &RoutingTarget,
285        swaps: impl IntoIterator<Item = [PhysicalQubit; 2]>,
286    ) -> Result<(), CompilerError> {
287        for swap in swaps {
288            self.plan.push_swap(swap);
289            self.swap_count = self.swap_count.saturating_add(1);
290            self.operation_count = self.operation_count.saturating_add(1);
291            self.two_qubit_operation_count = self.two_qubit_operation_count.saturating_add(1);
292            if target.native_cost_enabled {
293                self.native_cost.append_gate(target.swap_cost(swap)?);
294            }
295        }
296        Ok(())
297    }
298}
299
300#[derive(Debug, Clone)]
301pub(crate) struct RankedTrial {
302    trial: UnscoredTrial,
303    abstract_quality: AbstractTrialQuality,
304    native_two_qubit_ops: usize,
305    native_total_ops: usize,
306    unknown_loop_count: usize,
307    native_two_qubit_depth: Option<usize>,
308    native_total_depth: Option<usize>,
309    materialized_operations: Option<Vec<Operation>>,
310}
311
312impl RankedTrial {
313    fn from_unscored(trial: UnscoredTrial, target: &RoutingTarget) -> Result<Self, CompilerError> {
314        let abstract_quality = trial.abstract_quality();
315        let (native_two_qubit_ops, native_total_ops, unknown_loop_count) =
316            if target.native_cost_enabled {
317                let static_native = trial.native_cost.static_native.unwrap_or_default();
318                (
319                    static_native.native_two_qubit_ops as usize,
320                    static_native.native_total_ops as usize,
321                    trial.native_cost.path.unknown_loop_count,
322                )
323            } else {
324                (
325                    abstract_quality.two_qubit_operation_count,
326                    abstract_quality.operation_count,
327                    0,
328                )
329            };
330        Ok(Self {
331            trial,
332            abstract_quality,
333            native_two_qubit_ops,
334            native_total_ops,
335            unknown_loop_count,
336            native_two_qubit_depth: None,
337            native_total_depth: None,
338            materialized_operations: None,
339        })
340    }
341
342    fn ensure_materialized_operations(
343        &mut self,
344        target: &RoutingTarget,
345    ) -> Result<&[Operation], CompilerError> {
346        if self.materialized_operations.is_none() {
347            self.materialized_operations = Some(self.trial.materialize_operations(target)?);
348        }
349        Ok(self
350            .materialized_operations
351            .as_deref()
352            .expect("materialized route operations were initialized"))
353    }
354
355    fn ensure_abstract_two_qubit_depth(
356        &mut self,
357        target: &RoutingTarget,
358    ) -> Result<usize, CompilerError> {
359        if self.abstract_quality.two_qubit_depth == 0
360            && self.abstract_quality.two_qubit_operation_count != 0
361        {
362            self.abstract_quality.two_qubit_depth =
363                two_qubit_depth(self.ensure_materialized_operations(target)?);
364        }
365        Ok(self.abstract_quality.two_qubit_depth)
366    }
367
368    fn ensure_native_two_qubit_depth(
369        &mut self,
370        target: &RoutingTarget,
371    ) -> Result<usize, CompilerError> {
372        if let Some(depth) = self.native_two_qubit_depth {
373            return Ok(depth);
374        }
375        let depth = if target.native_cost_enabled {
376            self.ensure_materialized_operations(target)?;
377            native_two_qubit_depth(
378                self.materialized_operations
379                    .as_deref()
380                    .expect("materialized route operations were initialized"),
381                target,
382            )?
383        } else {
384            self.ensure_abstract_two_qubit_depth(target)?
385        };
386        self.native_two_qubit_depth = Some(depth);
387        Ok(depth)
388    }
389
390    fn ensure_native_total_depth(
391        &mut self,
392        target: &RoutingTarget,
393    ) -> Result<usize, CompilerError> {
394        if let Some(depth) = self.native_total_depth {
395            return Ok(depth);
396        }
397        let depth = if target.native_cost_enabled {
398            self.ensure_materialized_operations(target)?;
399            native_total_depth(
400                self.materialized_operations
401                    .as_deref()
402                    .expect("materialized route operations were initialized"),
403                target,
404            )?
405        } else {
406            self.abstract_quality.operation_count
407        };
408        self.native_total_depth = Some(depth);
409        Ok(depth)
410    }
411
412    pub(crate) fn swap_count(&self) -> usize {
413        self.trial.swap_count
414    }
415
416    /// Search-local fingerprint of the routed structure, independent of proxy
417    /// quality. It distinguishes routes with equal counts/depths so a bounded
418    /// beam can preserve more than one downstream optimization opportunity.
419    /// This key is not persisted and is not expected to remain stable across
420    /// Rust versions or platforms.
421    pub(crate) fn structure_fingerprint(&self) -> u64 {
422        let mut hasher = DefaultHasher::new();
423        for step in &self.trial.plan.steps {
424            match step {
425                CompactRouteStep::Owned {
426                    operation,
427                    source_order,
428                } => {
429                    0_u8.hash(&mut hasher);
430                    source_order.hash(&mut hasher);
431                    operation.qubits.hash(&mut hasher);
432                }
433                CompactRouteStep::Mapped {
434                    qubits,
435                    source_order,
436                    ..
437                } => {
438                    1_u8.hash(&mut hasher);
439                    source_order.hash(&mut hasher);
440                    qubits.hash(&mut hasher);
441                }
442                CompactRouteStep::Swap(pair) => {
443                    2_u8.hash(&mut hasher);
444                    pair.hash(&mut hasher);
445                }
446            }
447        }
448        for (logical, physical) in self.trial.final_layout.l2p_map() {
449            logical.hash(&mut hasher);
450            physical.hash(&mut hasher);
451        }
452        hasher.finish()
453    }
454
455    pub(crate) fn quality(
456        &mut self,
457        target: &RoutingTarget,
458    ) -> Result<TrialQuality, CompilerError> {
459        Ok(TrialQuality {
460            abstract_quality: AbstractTrialQuality {
461                two_qubit_depth: self.ensure_abstract_two_qubit_depth(target)?,
462                ..self.abstract_quality
463            },
464            native_two_qubit_ops: self.native_two_qubit_ops,
465            native_two_qubit_depth: self.ensure_native_two_qubit_depth(target)?,
466            native_total_depth: self.ensure_native_total_depth(target)?,
467            native_total_ops: self.native_total_ops,
468            unknown_loop_count: self.unknown_loop_count,
469        })
470    }
471
472    pub(crate) fn finish(self, target: &RoutingTarget) -> Result<TrialResult, CompilerError> {
473        self.finish_impl(target, true)
474    }
475
476    pub(crate) fn finish_without_provenance(
477        self,
478        target: &RoutingTarget,
479    ) -> Result<TrialResult, CompilerError> {
480        self.finish_impl(target, false)
481    }
482
483    fn finish_impl(
484        mut self,
485        target: &RoutingTarget,
486        retain_provenance: bool,
487    ) -> Result<TrialResult, CompilerError> {
488        let abstract_two_qubit_depth = self.ensure_abstract_two_qubit_depth(target)?;
489        let native_two_qubit_depth = self.ensure_native_two_qubit_depth(target)?;
490        let native_total_depth = self.ensure_native_total_depth(target)?;
491        self.ensure_materialized_operations(target)?;
492        let operations = self
493            .materialized_operations
494            .take()
495            .expect("materialized route operations were initialized");
496        self.abstract_quality.two_qubit_depth = abstract_two_qubit_depth;
497        let quality = TrialQuality {
498            abstract_quality: self.abstract_quality,
499            native_two_qubit_ops: self.native_two_qubit_ops,
500            native_two_qubit_depth,
501            native_total_depth,
502            native_total_ops: self.native_total_ops,
503            unknown_loop_count: self.unknown_loop_count,
504        };
505        let provenance = if retain_provenance {
506            self.trial.plan.provenance()
507        } else {
508            Vec::new()
509        };
510        let trial = self.trial;
511        Ok(TrialResult {
512            operations,
513            provenance,
514            final_layout: trial.final_layout,
515            swap_count: trial.swap_count,
516            fallback_count: trial.fallback_count,
517            control_flow_blocks_routed: trial.control_flow_blocks_routed,
518            lazy_pair_l1_lookup_count: trial.lazy_pair_l1_lookup_count,
519            lazy_pair_l1_hit_count: trial.lazy_pair_l1_hit_count,
520            lazy_pair_l1_cached_count: trial.lazy_pair_l1_cached_count,
521            quality,
522        })
523    }
524}
525
526#[derive(Debug, Clone, Copy, Default, PartialEq)]
527pub(crate) struct AbstractTrialQuality {
528    pub(crate) swap_count: usize,
529    pub(crate) two_qubit_depth: usize,
530    pub(crate) operation_count: usize,
531    pub(crate) two_qubit_operation_count: usize,
532}
533
534#[derive(Debug, Clone, Copy, Default, PartialEq)]
535pub(crate) struct TrialQuality {
536    pub(crate) abstract_quality: AbstractTrialQuality,
537    pub(crate) native_two_qubit_ops: usize,
538    pub(crate) native_two_qubit_depth: usize,
539    pub(crate) native_total_depth: usize,
540    pub(crate) native_total_ops: usize,
541    pub(crate) unknown_loop_count: usize,
542}
543
544/// Routes `circuit` from `initial_layout` by inserting SWAP operations.
545///
546/// The returned circuit uses physical qubit identifiers as its circuit qubits.
547/// Two-qubit operations in the routed circuit are adjacent in the usable
548/// physical topology.  Control-flow bodies are routed recursively and restored
549/// to their entry layout before leaving the block.
550pub fn sabre_route(
551    circuit: &Circuit,
552    device: &Device,
553    initial_layout: &Layout,
554    config: &SabreConfig,
555) -> Result<SabreRoutingResult, CompilerError> {
556    sabre_route_topology_with_tracking(circuit, device, initial_layout, config, false)
557        .map(|result| result.routing)
558}
559
560pub(crate) fn sabre_route_with_provenance_and_session_on_physical(
561    circuit: &Circuit,
562    device: &Device,
563    physical: &PhysicalLayoutGraph,
564    initial_layout: &Layout,
565    config: &SabreConfig,
566    planning_session: &DevicePlanningSession,
567) -> Result<TrackedSabreRoutingResult, CompilerError> {
568    config.validate()?;
569    let sabre = SabreDag::from_operations(circuit.operations())?;
570    let target =
571        RoutingTarget::from_device_with_session(device, physical, &sabre, planning_session)?;
572    sabre_route_with_prepared_target(circuit, &sabre, &target, initial_layout, config, true)
573}
574
575pub(crate) fn sabre_route_with_provenance_on_target(
576    circuit: &Circuit,
577    target: &RoutingTarget,
578    initial_layout: &Layout,
579    config: &SabreConfig,
580) -> Result<TrackedSabreRoutingResult, CompilerError> {
581    config.validate()?;
582    let sabre = SabreDag::from_operations(circuit.operations())?;
583    sabre_route_with_prepared_target(circuit, &sabre, target, initial_layout, config, true)
584}
585
586fn sabre_route_topology_with_tracking(
587    circuit: &Circuit,
588    device: &Device,
589    initial_layout: &Layout,
590    config: &SabreConfig,
591    retain_provenance: bool,
592) -> Result<TrackedSabreRoutingResult, CompilerError> {
593    config.validate()?;
594    // Build a dense, reusable view of the physical topology once. The routing
595    // loop indexes into this structure heavily for adjacency, distance, and
596    // deterministic candidate ordering.
597    let physical = PhysicalLayoutGraph::from_device(device)?;
598    let sabre = SabreDag::from_operations(circuit.operations())?;
599    let target = RoutingTarget::from_physical(&physical)?;
600    sabre_route_with_prepared_target(
601        circuit,
602        &sabre,
603        &target,
604        initial_layout,
605        config,
606        retain_provenance,
607    )
608}
609
610fn sabre_route_with_prepared_target(
611    circuit: &Circuit,
612    sabre: &SabreDag,
613    target: &RoutingTarget,
614    initial_layout: &Layout,
615    config: &SabreConfig,
616    retain_provenance: bool,
617) -> Result<TrackedSabreRoutingResult, CompilerError> {
618    let metadata = PreparedRouteMetadata::new(sabre, target)?;
619    sabre_route_prepared_impl(
620        circuit,
621        sabre,
622        target,
623        &metadata,
624        initial_layout,
625        config,
626        retain_provenance,
627    )
628}
629
630fn sabre_route_prepared_impl(
631    circuit: &Circuit,
632    sabre: &SabreDag,
633    target: &RoutingTarget,
634    metadata: &PreparedRouteMetadata,
635    initial_layout: &Layout,
636    config: &SabreConfig,
637    retain_provenance: bool,
638) -> Result<TrackedSabreRoutingResult, CompilerError> {
639    let logical_qubits = circuit
640        .qubits()
641        .into_iter()
642        .map(LogicalQubit::from_qubit)
643        .collect::<Vec<_>>();
644    let initial_layout =
645        normalize_initial_layout_for_target(&logical_qubits, target, initial_layout)?;
646    validate_reachable_interactions_for_target_with_metadata(
647        sabre,
648        target,
649        metadata,
650        &initial_layout,
651    )?;
652    // Trials share the normalized layout and DAG but use independent seeds for
653    // tie-breaking. Selection stays deterministic for a configured seed because
654    // result comparison falls back to the trial index.
655    let seeds = trial_seeds(config.seed, config.routing_trials);
656    let work_units = seeds.len().saturating_mul(sabre.graph.node_count());
657    let evaluate = |best, (index, seed)| {
658        let trial = route_ranked_trial_with_metadata(
659            sabre,
660            target,
661            metadata,
662            &initial_layout,
663            &config.heuristic,
664            seed,
665        )?;
666        select_ranked_trial(best, Some((index, trial)), target)
667    };
668    let best = if let Some(chunk_size) =
669        parallel_chunk_size(seeds.len(), work_units, ROUTING_PARALLELISM_THRESHOLDS)
670    {
671        seeds
672            .into_par_iter()
673            .enumerate()
674            .chunks(chunk_size)
675            .map(|chunk| chunk.into_iter().try_fold(None, &evaluate))
676            .try_reduce(
677                || None,
678                |left, right| select_ranked_trial(left, right, target),
679            )?
680    } else {
681        seeds.into_iter().enumerate().try_fold(None, evaluate)?
682    };
683    let (best_index, best) = best.expect("routing_trials is validated to be non-zero");
684
685    let best = if retain_provenance {
686        best.finish(target)?
687    } else {
688        best.finish_without_provenance(target)?
689    };
690    finish_sabre_route(
691        circuit,
692        target,
693        initial_layout,
694        best,
695        best_index,
696        config.routing_trials,
697    )
698}
699
700fn select_ranked_trial(
701    left: Option<(usize, RankedTrial)>,
702    right: Option<(usize, RankedTrial)>,
703    target: &RoutingTarget,
704) -> Result<Option<(usize, RankedTrial)>, CompilerError> {
705    match (left, right) {
706        (Some(mut left), Some(mut right)) => {
707            if compare_ranked_trials(&mut left.1, (0, left.0), &mut right.1, (0, right.0), target)?
708                .is_le()
709            {
710                Ok(Some(left))
711            } else {
712                Ok(Some(right))
713            }
714        }
715        (Some(trial), None) | (None, Some(trial)) => Ok(Some(trial)),
716        (None, None) => Ok(None),
717    }
718}
719
720pub(crate) fn route_ranked_trial_with_metadata(
721    sabre: &SabreDag,
722    target: &RoutingTarget,
723    metadata: &PreparedRouteMetadata,
724    initial_layout: &Layout,
725    heuristic: &SabreHeuristicConfig,
726    seed: u64,
727) -> Result<RankedTrial, CompilerError> {
728    let trial = route_unscored_trial_with_metadata(
729        sabre,
730        target,
731        metadata,
732        initial_layout,
733        heuristic,
734        seed,
735    )?;
736    RankedTrial::from_unscored(trial, target)
737}
738
739pub(crate) fn finish_sabre_route(
740    circuit: &Circuit,
741    target: &RoutingTarget,
742    initial_layout: Layout,
743    mut best: TrialResult,
744    best_index: usize,
745    trials_evaluated: usize,
746) -> Result<TrackedSabreRoutingResult, CompilerError> {
747    // Routing rewrites operation qubits but keeps symbolic parameters by index.
748    // Rebuild the routed circuit's parameter table in first-use order, then
749    // remap nested control-flow bodies to the new table.
750    let mut parameter_order = IndexSet::<Parameter>::new();
751    for operation in &best.operations {
752        for param in &operation.params {
753            if let CircuitParam::Index(index) = param {
754                let parameter = circuit
755                    .parameters()
756                    .get_index(*index as usize)
757                    .cloned()
758                    .ok_or(crate::circuit::CircuitError::InvalidParameterIndex(*index))?;
759                parameter_order.insert(parameter);
760            }
761        }
762    }
763    for parameter in circuit.parameters() {
764        parameter_order.insert(parameter.clone());
765    }
766    let parameter_indices = circuit
767        .parameters()
768        .iter()
769        .map(|parameter| {
770            parameter_order
771                .get_index_of(parameter)
772                .expect("source parameters are included in routed parameter order")
773                as u32
774        })
775        .collect::<Vec<_>>();
776    let routed_operations = std::mem::take(&mut best.operations)
777        .into_iter()
778        .map(|operation| {
779            let operation = remap_parameter_indices(operation, &parameter_indices)?;
780            storage_operation_to_value(operation, &|param| match param {
781                CircuitParam::Fixed(value) => Ok((*value).into()),
782                CircuitParam::Index(index) => parameter_order
783                    .get_index(*index as usize)
784                    .cloned()
785                    .map(Into::into)
786                    .ok_or(crate::circuit::CircuitError::InvalidParameterIndex(*index)),
787            })
788            .map_err(CompilerError::Circuit)
789        })
790        .collect::<Result<Vec<_>, CompilerError>>()?;
791    let mut routed = Circuit::from_operations(
792        target
793            .physical_qubits
794            .iter()
795            .copied()
796            .map(PhysicalQubit::qubit)
797            .collect(),
798        routed_operations,
799        Some(circuit.classical_vars().to_vec()),
800        Some(circuit.classical_values().to_vec()),
801    )?;
802    for parameter in parameter_order {
803        routed.add_parameter(parameter);
804    }
805    routed.set_global_phase(circuit.global_phase());
806    Ok(TrackedSabreRoutingResult {
807        routing: SabreRoutingResult {
808            circuit: routed,
809            initial_layout,
810            final_layout: best.final_layout,
811            swap_count: best.swap_count,
812            diagnostics: SabreRoutingDiagnostics {
813                trials_evaluated,
814                selected_trial_index: best_index,
815                fallback_count: best.fallback_count,
816                control_flow_blocks_routed: best.control_flow_blocks_routed,
817                two_qubit_depth: best.quality.abstract_quality.two_qubit_depth,
818                operation_count: best.quality.abstract_quality.operation_count,
819                native_two_qubit_count: best.quality.native_two_qubit_ops,
820                native_two_qubit_depth: best.quality.native_two_qubit_depth,
821                native_total_depth: best.quality.native_total_depth,
822                native_operation_count: best.quality.native_total_ops,
823                unknown_loop_count: best.quality.unknown_loop_count,
824                requirement_signature_count: target.requirements.len(),
825                eager_pair_state_count: target.eager_pair_state_count,
826                lazy_pair_l1_lookup_count: best.lazy_pair_l1_lookup_count,
827                lazy_pair_l1_hit_count: best.lazy_pair_l1_hit_count,
828                lazy_pair_l1_cached_count: best.lazy_pair_l1_cached_count,
829            },
830        },
831        provenance: best.provenance,
832    })
833}
834
835pub(crate) fn route_unscored_trial_with_metadata(
836    sabre: &SabreDag,
837    target: &RoutingTarget,
838    metadata: &PreparedRouteMetadata,
839    initial_layout: &Layout,
840    heuristic: &SabreHeuristicConfig,
841    seed: u64,
842) -> Result<UnscoredTrial, CompilerError> {
843    route_trial_with_metadata(
844        sabre,
845        target,
846        metadata,
847        initial_layout,
848        heuristic,
849        seed,
850        true,
851    )
852}
853
854pub(crate) fn refine_layout_with_metadata(
855    sabre: &SabreDag,
856    target: &RoutingTarget,
857    metadata: &PreparedRouteMetadata,
858    initial_layout: &Layout,
859    heuristic: &SabreHeuristicConfig,
860    seed: u64,
861) -> Result<Layout, CompilerError> {
862    route_trial_with_metadata(
863        sabre,
864        target,
865        metadata,
866        initial_layout,
867        heuristic,
868        seed,
869        false,
870    )
871    .map(|trial| trial.final_layout)
872}
873
874fn route_trial_with_metadata(
875    sabre: &SabreDag,
876    target: &RoutingTarget,
877    metadata: &PreparedRouteMetadata,
878    initial_layout: &Layout,
879    heuristic: &SabreHeuristicConfig,
880    seed: u64,
881    emit_operations: bool,
882) -> Result<UnscoredTrial, CompilerError> {
883    let mut output = TrialOutput::new(seed, emit_operations);
884    let mut state = RoutingState::new(sabre, target, metadata, initial_layout, heuristic, seed)?;
885
886    // Initial operations are dependency-free one-qubit or non-quantum work.
887    // They can be emitted immediately under the starting layout.
888    if output.emit_operations {
889        for operation in &sabre.initial {
890            output.push_mapped_operation(
891                &operation.operation,
892                Some(operation.source_order),
893                &state.layout,
894                target,
895            )?;
896        }
897    }
898
899    state.update_route(
900        sabre,
901        target,
902        metadata,
903        heuristic,
904        &mut output,
905        &sabre.first_layer,
906        None,
907    )?;
908    state.populate_extended_set(sabre, target)?;
909
910    let mut routable_nodes = Vec::with_capacity(2);
911    let mut current_swaps = Vec::new();
912    let mut mapping_cycles = MappingCycleDetector::new(state.layout_hash);
913    let mut search_steps_since_decay_reset = 0usize;
914    while !state.front_layer.is_empty() {
915        current_swaps.clear();
916        mapping_cycles.reset(state.layout_hash);
917        let mut repeated_mapping = false;
918        // Search accumulates speculative SWAPs until at least one front-layer
919        // node becomes adjacent. Those SWAPs are emitted only when the routed
920        // node is actually emitted, preserving a compact operation stream.
921        while routable_nodes.is_empty()
922            && !repeated_mapping
923            && current_swaps.len() < heuristic.attempt_limit
924        {
925            let best_swap =
926                state.choose_best_swap(target, heuristic, current_swaps.last().copied())?;
927            state.apply_swap(best_swap.physical, target)?;
928            current_swaps.push(best_swap.emitted);
929            repeated_mapping = mapping_cycles.record_hash(state.layout_hash);
930            let executable =
931                |requirement, placement| target.terminal_cost_for(requirement, placement).is_some();
932            for candidate in best_swap
933                .indices
934                .into_iter()
935                .filter_map(|index| state.front_layer.routable_node_on_index(index, &executable))
936            {
937                if !routable_nodes.contains(&candidate) {
938                    routable_nodes.push(candidate);
939                }
940            }
941
942            if let Some(increment) = heuristic.decay_increment {
943                search_steps_since_decay_reset += 1;
944                if search_steps_since_decay_reset >= heuristic.decay_reset {
945                    for value in &mut state.decay {
946                        *value = 1.0;
947                    }
948                    search_steps_since_decay_reset = 0;
949                } else {
950                    state.decay[best_swap.indices[0]] += increment;
951                    state.decay[best_swap.indices[1]] += increment;
952                }
953            }
954        }
955
956        if routable_nodes.is_empty() {
957            // The heuristic failed to make progress within its attempt budget.
958            // Roll back speculative swaps, then force progress along a shortest
959            // path so the router cannot livelock on a poor local score.
960            for swap in current_swaps.drain(..).rev() {
961                state.apply_swap(swap, target)?;
962            }
963            output.fallback_count += 1;
964            let forced = state.force_enable_closest_node(target, &mut current_swaps)?;
965            routable_nodes.extend(forced);
966        }
967
968        let distance = |requirement, placement| {
969            target.distance_for_cached(requirement, placement, Some(&state.lower_bound_cache))
970        };
971        for node in &routable_nodes {
972            state.front_layer.remove(*node, &distance)?;
973        }
974        state.update_route(
975            sabre,
976            target,
977            metadata,
978            heuristic,
979            &mut output,
980            &routable_nodes,
981            Some(&mut current_swaps),
982        )?;
983        state.populate_extended_set(sabre, target)?;
984        if metadata.high_pair_reuse
985            && state.front_layer.len().saturating_mul(WIDE_FRONT_FRACTION)
986                >= target.physical_qubits.len()
987            && heuristic.decay_increment.is_some()
988        {
989            state.decay.fill(1.0);
990            search_steps_since_decay_reset = 0;
991        }
992        routable_nodes.clear();
993    }
994
995    let own_lazy_stats = state.lower_bound_cache.stats();
996    Ok(UnscoredTrial {
997        plan: output.plan,
998        final_layout: state.layout.to_layout(&target.physical_qubits)?,
999        swap_count: output.swap_count,
1000        fallback_count: output.fallback_count,
1001        control_flow_blocks_routed: output.control_flow_blocks_routed,
1002        lazy_pair_l1_lookup_count: output
1003            .lazy_pair_l1_lookup_count
1004            .saturating_add(own_lazy_stats.lookup_count),
1005        lazy_pair_l1_hit_count: output
1006            .lazy_pair_l1_hit_count
1007            .saturating_add(own_lazy_stats.hit_count),
1008        lazy_pair_l1_cached_count: output
1009            .lazy_pair_l1_cached_count
1010            .saturating_add(own_lazy_stats.cached_count),
1011        native_cost: output.native_cost,
1012        two_qubit_operation_count: output.two_qubit_operation_count,
1013        operation_count: output.operation_count,
1014    })
1015}
1016
1017/// Derives per-trial seeds from an optional workflow seed.
1018///
1019/// A configured seed is the stable base for index-derived trial streams. This
1020/// gives reproducible but distinct tie-breaking streams without depending on
1021/// execution order. When no seed is configured, a random base is chosen.
1022pub(crate) fn trial_seeds(seed: Option<u64>, count: usize) -> Vec<u64> {
1023    let base = seed.unwrap_or_else(rand::random);
1024    (0..count)
1025        .map(|index| {
1026            let mut value = base ^ (index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15);
1027            value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
1028            value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
1029            value ^ (value >> 31)
1030        })
1031        .collect()
1032}
1033
1034/// Normalizes an initial layout against a device's usable physical topology.
1035///
1036/// The returned layout contains the supplied logical qubits and every usable
1037/// physical qubit from `device`. Logical qubits must already be mapped by
1038/// `initial_layout`; extra usable physical qubits remain vacant.
1039pub fn normalize_initial_layout(
1040    logical_qubits: &[LogicalQubit],
1041    device: &Device,
1042    initial_layout: &Layout,
1043) -> Result<Layout, CompilerError> {
1044    let physical = PhysicalLayoutGraph::from_device(device)?;
1045    let target = RoutingTarget::from_physical(&physical)?;
1046    normalize_initial_layout_for_target(logical_qubits, &target, initial_layout)
1047}
1048
1049pub(crate) fn normalize_initial_layout_for_target(
1050    logical_qubits: &[LogicalQubit],
1051    target: &RoutingTarget,
1052    initial_layout: &Layout,
1053) -> Result<Layout, CompilerError> {
1054    let mut mapping = BTreeMap::new();
1055    for logical in logical_qubits {
1056        let physical = initial_layout.get_physical(*logical).ok_or_else(|| {
1057            CompilerError::InvalidInput(format!(
1058                "sabre initial layout does not map logical qubit {logical}"
1059            ))
1060        })?;
1061        if !target.physical_set.contains(&physical) {
1062            return Err(CompilerError::InvalidInput(format!(
1063                "sabre initial layout maps logical qubit {logical} to unusable physical qubit {physical}"
1064            )));
1065        }
1066        mapping.insert(*logical, physical);
1067    }
1068    Layout::new(
1069        logical_qubits.to_vec(),
1070        target.physical_qubits.clone(),
1071        Some(mapping),
1072    )
1073    .map_err(|error| {
1074        CompilerError::InvalidInput(format!("sabre initial layout is invalid: {error}"))
1075    })
1076}
1077
1078/// Validates that every two-qubit interaction in `circuit` is reachable.
1079///
1080/// The check uses the usable physical topology of `device` and the logical to
1081/// physical mapping in `initial_layout`. It validates reachability, not current
1082/// adjacency; non-adjacent but connected interactions can still be routed by
1083/// SABRE.
1084pub fn validate_reachable_interactions(
1085    circuit: &Circuit,
1086    device: &Device,
1087    initial_layout: &Layout,
1088) -> Result<(), CompilerError> {
1089    let physical = PhysicalLayoutGraph::from_device(device)?;
1090    let sabre = SabreDag::from_operations(circuit.operations())?;
1091    let target = RoutingTarget::from_physical(&physical)?;
1092    let logical_qubits = circuit
1093        .qubits()
1094        .into_iter()
1095        .map(LogicalQubit::from_qubit)
1096        .collect::<Vec<_>>();
1097    let initial_layout =
1098        normalize_initial_layout_for_target(&logical_qubits, &target, initial_layout)?;
1099    validate_reachable_interactions_for_target(&sabre, &target, &initial_layout)
1100}
1101
1102#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1103struct InteractionOperation {
1104    instruction: KnowledgeInstructionKey,
1105    qarg_roles: SmallVec<[u8; 2]>,
1106}
1107
1108#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1109enum InteractionSignature {
1110    /// Topology-only routing model for arbitrary one-qubit operations.
1111    GenericUnary,
1112    /// Layout refinement DAGs deliberately omit concrete operations.
1113    GenericPair,
1114    Unary(Vec<InteractionOperation>),
1115    Pair(Vec<InteractionOperation>),
1116}
1117
1118#[derive(Debug, Clone)]
1119enum RequirementTable {
1120    Unary {
1121        terminals: Vec<Option<NativePlanCost>>,
1122        lower_bounds: Vec<Option<RouteLowerBound>>,
1123    },
1124    Pair {
1125        terminals: BTreeMap<[usize; 2], NativePlanCost>,
1126        lower_bounds: Option<PairStateTable<RouteLowerBound>>,
1127    },
1128}
1129
1130#[derive(Debug, Clone, Default)]
1131struct StructuralNativePlan {
1132    cost: NativePlanCost,
1133    leaves: Vec<SmallVec<[PhysicalQubit; 2]>>,
1134}
1135
1136impl StructuralNativePlan {
1137    fn from_summary(summary: &NativePlanSummary) -> Self {
1138        Self {
1139            cost: structural_native_cost(summary),
1140            leaves: summary
1141                .leaves
1142                .iter()
1143                .map(|leaf| leaf.ordered_qargs.clone())
1144                .collect(),
1145        }
1146    }
1147}
1148
1149fn structural_native_cost(summary: &NativePlanSummary) -> NativePlanCost {
1150    NativePlanCost {
1151        native_two_qubit_ops: summary.native_two_qubit_ops,
1152        native_total_ops: summary.native_total_ops,
1153        ..NativePlanCost::default()
1154    }
1155}
1156
1157#[derive(Debug, Clone)]
1158struct PairStateTable<T> {
1159    width: usize,
1160    values: Vec<Option<T>>,
1161}
1162
1163impl<T: Copy> PairStateTable<T> {
1164    fn new(width: usize) -> Self {
1165        Self {
1166            width,
1167            values: vec![None; width.saturating_mul(width.saturating_sub(1))],
1168        }
1169    }
1170
1171    fn get(&self, left: usize, right: usize) -> Option<T> {
1172        Self::index(self.width, left, right)
1173            .and_then(|index| self.values.get(index))
1174            .copied()
1175            .flatten()
1176    }
1177
1178    fn set(&mut self, left: usize, right: usize, value: T) {
1179        if let Some(index) = Self::index(self.width, left, right) {
1180            self.values[index] = Some(value);
1181        }
1182    }
1183
1184    fn state_count(&self) -> usize {
1185        self.values.len()
1186    }
1187
1188    fn index(width: usize, left: usize, right: usize) -> Option<usize> {
1189        if width < 2 || left >= width || right >= width || left == right {
1190            return None;
1191        }
1192        let right_without_diagonal = if right < left { right } else { right - 1 };
1193        left.checked_mul(width - 1)?
1194            .checked_add(right_without_diagonal)
1195    }
1196}
1197
1198#[derive(Debug, Default)]
1199struct LazyPairCache {
1200    values: HashMap<(usize, usize, usize), Option<RouteLowerBound>>,
1201    flights: HashMap<(usize, usize, usize), Arc<LazyPairFlight>>,
1202}
1203
1204#[derive(Debug)]
1205struct LazyPairFlight {
1206    state: Mutex<LazyPairFlightState>,
1207    ready: Condvar,
1208}
1209
1210#[derive(Debug, Clone, Copy)]
1211enum LazyPairFlightState {
1212    Pending,
1213    Ready(Option<RouteLowerBound>),
1214    Aborted,
1215}
1216
1217impl Default for LazyPairFlight {
1218    fn default() -> Self {
1219        Self {
1220            state: Mutex::new(LazyPairFlightState::Pending),
1221            ready: Condvar::new(),
1222        }
1223    }
1224}
1225
1226struct LazyPairComputation<'a> {
1227    cache: &'a Mutex<LazyPairCache>,
1228    key: (usize, usize, usize),
1229    flight: Arc<LazyPairFlight>,
1230    completed: bool,
1231}
1232
1233impl LazyPairComputation<'_> {
1234    fn publish(mut self, value: Option<RouteLowerBound>) {
1235        {
1236            let mut state = self
1237                .flight
1238                .state
1239                .lock()
1240                .unwrap_or_else(|poisoned| poisoned.into_inner());
1241            *state = LazyPairFlightState::Ready(value);
1242        }
1243        {
1244            let mut cache = self
1245                .cache
1246                .lock()
1247                .unwrap_or_else(|poisoned| poisoned.into_inner());
1248            if cache.values.len() < LAZY_PAIR_CACHE_BUDGET {
1249                cache.values.entry(self.key).or_insert(value);
1250            }
1251            cache.flights.remove(&self.key);
1252        }
1253        self.completed = true;
1254        self.flight.ready.notify_all();
1255    }
1256}
1257
1258impl Drop for LazyPairComputation<'_> {
1259    fn drop(&mut self) {
1260        if self.completed {
1261            return;
1262        }
1263        {
1264            let mut state = self
1265                .flight
1266                .state
1267                .lock()
1268                .unwrap_or_else(|poisoned| poisoned.into_inner());
1269            *state = LazyPairFlightState::Aborted;
1270        }
1271        {
1272            let mut cache = self
1273                .cache
1274                .lock()
1275                .unwrap_or_else(|poisoned| poisoned.into_inner());
1276            if cache
1277                .flights
1278                .get(&self.key)
1279                .is_some_and(|flight| Arc::ptr_eq(flight, &self.flight))
1280            {
1281                cache.flights.remove(&self.key);
1282            }
1283        }
1284        self.flight.ready.notify_all();
1285    }
1286}
1287
1288#[derive(Debug, Clone, Copy, Default)]
1289struct TrialPairCacheStats {
1290    lookup_count: usize,
1291    hit_count: usize,
1292    cached_count: usize,
1293}
1294
1295#[derive(Debug, Default)]
1296struct TrialPairCache {
1297    values: RefCell<HashMap<(usize, usize, usize), Option<RouteLowerBound>>>,
1298    lookup_count: Cell<usize>,
1299    hit_count: Cell<usize>,
1300}
1301
1302impl TrialPairCache {
1303    fn get(&self, key: &(usize, usize, usize)) -> Option<Option<RouteLowerBound>> {
1304        self.lookup_count
1305            .set(self.lookup_count.get().saturating_add(1));
1306        let value = self.values.borrow().get(key).copied();
1307        if value.is_some() {
1308            self.hit_count.set(self.hit_count.get().saturating_add(1));
1309        }
1310        value
1311    }
1312
1313    fn insert(&self, key: (usize, usize, usize), value: Option<RouteLowerBound>) {
1314        let mut values = self.values.borrow_mut();
1315        if values.len() < TRIAL_PAIR_CACHE_BUDGET {
1316            values.entry(key).or_insert(value);
1317        }
1318    }
1319
1320    fn stats(&self) -> TrialPairCacheStats {
1321        TrialPairCacheStats {
1322            lookup_count: self.lookup_count.get(),
1323            hit_count: self.hit_count.get(),
1324            cached_count: self.values.borrow().len(),
1325        }
1326    }
1327}
1328
1329#[derive(Debug, Clone, Copy, Default, PartialEq)]
1330struct RouteLowerBound {
1331    remaining_swaps: u32,
1332    native: NativePlanCost,
1333}
1334
1335#[derive(Debug, Clone, Copy)]
1336struct MovementNeighbor {
1337    index: usize,
1338    swap: VerifiedSwap,
1339}
1340
1341#[derive(Debug, Clone, Copy)]
1342struct VerifiedSwap {
1343    emitted_indices: [usize; 2],
1344    cost: NativePlanCost,
1345}
1346
1347#[derive(Debug, Clone, Copy)]
1348struct MovementEdge {
1349    endpoints: [usize; 2],
1350    swap: VerifiedSwap,
1351}
1352
1353impl RouteLowerBound {
1354    fn with_swap(self, swap: NativePlanCost) -> Self {
1355        Self {
1356            remaining_swaps: self.remaining_swaps.saturating_add(1),
1357            native: swap.combine(self.native),
1358        }
1359    }
1360
1361    fn combine(self, other: Self) -> Self {
1362        Self {
1363            remaining_swaps: self.remaining_swaps.saturating_add(other.remaining_swaps),
1364            native: self.native.combine(other.native),
1365        }
1366    }
1367
1368    fn compare(self, other: Self) -> Ordering {
1369        compare_optional_native_cost(Some(self.native), Some(other.native))
1370            .then_with(|| self.remaining_swaps.cmp(&other.remaining_swaps))
1371    }
1372}
1373
1374#[derive(Debug, Clone)]
1375pub(crate) struct RoutingTarget {
1376    pub(crate) physical_qubits: Vec<PhysicalQubit>,
1377    physical_set: BTreeSet<PhysicalQubit>,
1378    physical_index: BTreeMap<PhysicalQubit, usize>,
1379    neighbors_by_index: Vec<Vec<MovementNeighbor>>,
1380    interaction_ids: HashMap<InteractionSignature, usize>,
1381    requirements: Vec<RequirementTable>,
1382    native_plans: HashMap<DeviceGateState, StructuralNativePlan>,
1383    native_unsupported: HashMap<DeviceGateState, DeviceLoweringFailure>,
1384    native_cost_enabled: bool,
1385    eager_pair_state_count: usize,
1386    lazy_pair_cache: Arc<Mutex<LazyPairCache>>,
1387    graph: UnGraph<(), ()>,
1388    graph_index: BTreeMap<PhysicalQubit, NodeIndex>,
1389    physical_by_index: Vec<PhysicalQubit>,
1390    topology_distance_values: Box<[u32]>,
1391}
1392
1393struct PreparedRoutingParts {
1394    movement_edges: Vec<MovementEdge>,
1395    interaction_ids: HashMap<InteractionSignature, usize>,
1396    requirements: Vec<RequirementTable>,
1397    native_plans: HashMap<DeviceGateState, StructuralNativePlan>,
1398    native_unsupported: HashMap<DeviceGateState, DeviceLoweringFailure>,
1399    native_cost_enabled: bool,
1400}
1401
1402impl RoutingTarget {
1403    pub(crate) fn uses_native_costs(&self) -> bool {
1404        self.native_cost_enabled
1405    }
1406
1407    /// Builds the indexed routing view used by SABRE scoring.
1408    ///
1409    /// The target keeps both semantic physical-qubit ids and dense indices.
1410    /// Dense indices make layer scoring cheap; semantic ids keep diagnostics
1411    /// and emitted SWAP operations stable.
1412    pub(crate) fn from_physical(physical: &PhysicalLayoutGraph) -> Result<Self, CompilerError> {
1413        let count = physical.physical_qubits().len();
1414        let edges = undirected_topology_edges(physical);
1415        let movement_edges = edges
1416            .iter()
1417            .copied()
1418            .map(|(left, right)| MovementEdge {
1419                endpoints: [left, right],
1420                swap: VerifiedSwap {
1421                    emitted_indices: [left, right],
1422                    cost: NativePlanCost::default(),
1423                },
1424            })
1425            .collect::<Vec<_>>();
1426        let unary_terminals = vec![Some(NativePlanCost::default()); count];
1427        let unary_lower_bounds = unary_route_lower_bounds(
1428            &movement_adjacency(count, &movement_edges),
1429            &unary_terminals,
1430        );
1431        let mut pair_terminals = BTreeMap::new();
1432        for &(left, right) in &edges {
1433            pair_terminals.insert([left, right], NativePlanCost::default());
1434            pair_terminals.insert([right, left], NativePlanCost::default());
1435        }
1436        Self::from_prepared_parts(
1437            physical,
1438            PreparedRoutingParts {
1439                movement_edges,
1440                interaction_ids: HashMap::from([
1441                    (InteractionSignature::GenericUnary, 0),
1442                    (InteractionSignature::GenericPair, 1),
1443                ]),
1444                requirements: vec![
1445                    RequirementTable::Unary {
1446                        terminals: unary_terminals,
1447                        lower_bounds: unary_lower_bounds,
1448                    },
1449                    RequirementTable::Pair {
1450                        lower_bounds: None,
1451                        terminals: pair_terminals,
1452                    },
1453                ],
1454                native_plans: HashMap::new(),
1455                native_unsupported: HashMap::new(),
1456                native_cost_enabled: false,
1457            },
1458        )
1459    }
1460
1461    /// Builds a device-aware target whose movement edges are exactly those on
1462    /// which the final device lowerer can realize an emitted SWAP.
1463    pub(crate) fn from_device_with_session(
1464        device: &Device,
1465        physical: &PhysicalLayoutGraph,
1466        sabre: &SabreDag,
1467        planning_session: &DevicePlanningSession,
1468    ) -> Result<Self, CompilerError> {
1469        Self::from_device_with_pair_state_budget_and_session(
1470            device,
1471            physical,
1472            sabre,
1473            EAGER_PAIR_STATE_BUDGET,
1474            planning_session,
1475        )
1476    }
1477
1478    fn from_device_with_pair_state_budget_and_session(
1479        device: &Device,
1480        physical: &PhysicalLayoutGraph,
1481        sabre: &SabreDag,
1482        pair_state_budget: usize,
1483        planning_session: &DevicePlanningSession,
1484    ) -> Result<Self, CompilerError> {
1485        let topology_edges = undirected_topology_edges(physical);
1486        let physical_qubits = physical.physical_qubits();
1487        if !device_declares_routing_capability(device, physical_qubits) {
1488            return Err(CompilerError::InvalidInput(format!(
1489                "exact device-native SABRE routing requires native capabilities on device '{}'",
1490                device.name()
1491            )));
1492        }
1493        let mut pair_state_budget = pair_state_budget;
1494        let signatures = sabre.ordered_interaction_signatures()?;
1495        let interaction_ids = signatures
1496            .iter()
1497            .cloned()
1498            .enumerate()
1499            .map(|(index, signature)| (signature, index))
1500            .collect::<HashMap<_, _>>();
1501
1502        let roots = collect_device_plan_roots(physical_qubits, &topology_edges, &signatures);
1503
1504        let catalog = NativePlanCatalog::build_with_session(planning_session, roots)?;
1505        let native_identity = NativePlanCost::default();
1506        let native_plans = catalog
1507            .iter()
1508            .map(|(state, summary)| (state.clone(), StructuralNativePlan::from_summary(summary)))
1509            .collect();
1510        let native_unsupported = catalog
1511            .iter_availability()
1512            .filter_map(|(state, availability)| {
1513                let NativePlanAvailability::Unsupported(failure) = availability else {
1514                    return None;
1515                };
1516                Some((state.clone(), failure.as_ref().clone()))
1517            })
1518            .collect();
1519        let count = physical_qubits.len();
1520        let mut movement_edges = Vec::new();
1521        for &(left_index, right_index) in &topology_edges {
1522            let mut candidates = [[left_index, right_index], [right_index, left_index]]
1523                .into_iter()
1524                .filter_map(|emitted_indices| {
1525                    let state = DeviceGateState::standard(
1526                        StandardGate::SWAP,
1527                        smallvec![
1528                            physical_qubits[emitted_indices[0]],
1529                            physical_qubits[emitted_indices[1]]
1530                        ],
1531                    );
1532                    catalog.summary(&state).map(|summary| VerifiedSwap {
1533                        emitted_indices,
1534                        cost: structural_native_cost(summary),
1535                    })
1536                })
1537                .collect::<Vec<_>>();
1538            candidates.sort_by(|left, right| {
1539                compare_optional_native_cost(Some(left.cost), Some(right.cost)).then_with(|| {
1540                    left.emitted_indices
1541                        .map(|index| physical_qubits[index])
1542                        .cmp(&right.emitted_indices.map(|index| physical_qubits[index]))
1543                })
1544            });
1545            if let Some(swap) = candidates.into_iter().next() {
1546                movement_edges.push(MovementEdge {
1547                    endpoints: [left_index, right_index],
1548                    swap,
1549                });
1550            }
1551        }
1552
1553        let neighbors = movement_adjacency(count, &movement_edges);
1554        let requirements = build_device_requirement_tables(
1555            &signatures,
1556            DeviceRequirementInputs {
1557                physical_qubits,
1558                topology_edges: &topology_edges,
1559                movement_edges: &movement_edges,
1560                neighbors: &neighbors,
1561                catalog: &catalog,
1562                native_identity,
1563            },
1564            &mut pair_state_budget,
1565        );
1566
1567        Self::from_prepared_parts(
1568            physical,
1569            PreparedRoutingParts {
1570                movement_edges,
1571                interaction_ids,
1572                requirements,
1573                native_plans,
1574                native_unsupported,
1575                native_cost_enabled: true,
1576            },
1577        )
1578    }
1579
1580    fn from_prepared_parts(
1581        physical: &PhysicalLayoutGraph,
1582        parts: PreparedRoutingParts,
1583    ) -> Result<Self, CompilerError> {
1584        let PreparedRoutingParts {
1585            movement_edges,
1586            interaction_ids,
1587            requirements,
1588            native_plans,
1589            native_unsupported,
1590            native_cost_enabled,
1591        } = parts;
1592        let physical_qubits = physical.physical_qubits().to_vec();
1593        let physical_set = physical_qubits.iter().copied().collect::<BTreeSet<_>>();
1594        let mut graph = UnGraph::with_capacity(physical_qubits.len(), 0);
1595        let mut graph_index = BTreeMap::new();
1596        let mut physical_index = BTreeMap::new();
1597        let mut physical_by_index = Vec::with_capacity(physical_qubits.len());
1598
1599        for (dense_index, physical) in physical_qubits.iter().copied().enumerate() {
1600            let graph_node = graph.add_node(());
1601            graph_index.insert(physical, graph_node);
1602            physical_index.insert(physical, dense_index);
1603            physical_by_index.push(physical);
1604        }
1605        let mut neighbors_by_index = vec![Vec::new(); physical_qubits.len()];
1606
1607        for edge in movement_edges {
1608            let [left_index, right_index] = edge.endpoints;
1609            let left = physical_qubits[left_index];
1610            let right = physical_qubits[right_index];
1611            neighbors_by_index[left_index].push(MovementNeighbor {
1612                index: right_index,
1613                swap: edge.swap,
1614            });
1615            neighbors_by_index[right_index].push(MovementNeighbor {
1616                index: left_index,
1617                swap: edge.swap,
1618            });
1619            graph.add_edge(graph_index[&left], graph_index[&right], ());
1620        }
1621        for items in &mut neighbors_by_index {
1622            items.sort_unstable_by_key(|neighbor| physical_qubits[neighbor.index]);
1623        }
1624
1625        let eager_pair_state_count = requirements
1626            .iter()
1627            .filter_map(|requirement| match requirement {
1628                RequirementTable::Pair {
1629                    lower_bounds: Some(lower_bounds),
1630                    ..
1631                } => Some(lower_bounds.state_count()),
1632                RequirementTable::Unary { .. }
1633                | RequirementTable::Pair {
1634                    lower_bounds: None, ..
1635                } => None,
1636            })
1637            .sum();
1638
1639        let topology_distances = physical.distances().clone();
1640        let topology_distance_values = (0..physical_qubits.len())
1641            .flat_map(|left| {
1642                let distances = &topology_distances;
1643                (0..physical_qubits.len())
1644                    .map(move |right| distances.distance_by_index(left, right).unwrap_or(u32::MAX))
1645            })
1646            .collect::<Vec<_>>()
1647            .into_boxed_slice();
1648
1649        Ok(Self {
1650            physical_qubits,
1651            physical_set,
1652            physical_index,
1653            neighbors_by_index,
1654            interaction_ids,
1655            requirements,
1656            native_plans,
1657            native_unsupported,
1658            native_cost_enabled,
1659            eager_pair_state_count,
1660            lazy_pair_cache: Arc::new(Mutex::new(LazyPairCache::default())),
1661            graph,
1662            graph_index,
1663            physical_by_index,
1664            topology_distance_values,
1665        })
1666    }
1667
1668    fn physical_index(&self, physical: PhysicalQubit) -> Result<usize, CompilerError> {
1669        self.physical_index.get(&physical).copied().ok_or_else(|| {
1670            CompilerError::InvalidInput(format!(
1671                "physical qubit {physical} is not usable in the target topology"
1672            ))
1673        })
1674    }
1675
1676    fn physical_at(&self, index: usize) -> Result<PhysicalQubit, CompilerError> {
1677        self.physical_qubits.get(index).copied().ok_or_else(|| {
1678            CompilerError::InvariantViolation(format!(
1679                "physical index {index} is outside target topology of length {}",
1680                self.physical_qubits.len()
1681            ))
1682        })
1683    }
1684
1685    #[inline(always)]
1686    fn distance_for_cached(
1687        &self,
1688        requirement: usize,
1689        placement: RequirementPlacement,
1690        cache: Option<&TrialPairCache>,
1691    ) -> Result<f64, CompilerError> {
1692        if !self.native_cost_enabled {
1693            return match placement {
1694                RequirementPlacement::Unary(_) => Ok(1.0),
1695                RequirementPlacement::Pair([left, right]) => {
1696                    let distance = self.topology_distance(left, right).ok_or_else(|| {
1697                            CompilerError::InvalidInput(format!(
1698                                "routing requirement {requirement} at {placement:?} cannot reach an executable terminal using lowerable SWAPs"
1699                            ))
1700                        })?;
1701                    Ok(f64::from(distance))
1702                }
1703            };
1704        }
1705        self.route_lower_bound_for_cached(requirement, placement, cache)
1706            .map(|bound| f64::from(bound.remaining_swaps) + 1.0)
1707            .ok_or_else(|| {
1708                CompilerError::InvalidInput(format!(
1709                    "routing requirement {requirement} at {placement:?} cannot reach an executable terminal using lowerable SWAPs"
1710                ))
1711            })
1712    }
1713
1714    #[inline(always)]
1715    fn topology_distance(&self, left: usize, right: usize) -> Option<u32> {
1716        let width = self.physical_qubits.len();
1717        left.checked_mul(width)
1718            .and_then(|offset| offset.checked_add(right))
1719            .and_then(|index| self.topology_distance_values.get(index))
1720            .copied()
1721            .filter(|distance| *distance != u32::MAX)
1722    }
1723
1724    fn terminal_cost_for(
1725        &self,
1726        requirement: usize,
1727        placement: RequirementPlacement,
1728    ) -> Option<NativePlanCost> {
1729        match (self.requirements.get(requirement)?, placement) {
1730            (RequirementTable::Unary { terminals, .. }, RequirementPlacement::Unary(physical)) => {
1731                terminals.get(physical).copied().flatten()
1732            }
1733            (
1734                RequirementTable::Pair { terminals, .. },
1735                RequirementPlacement::Pair([left, right]),
1736            ) => terminals.get(&[left, right]).copied(),
1737            _ => None,
1738        }
1739    }
1740
1741    fn verified_swap(
1742        &self,
1743        swap: [PhysicalQubit; 2],
1744    ) -> Result<([PhysicalQubit; 2], VerifiedSwap), CompilerError> {
1745        let left = self.physical_index(swap[0])?;
1746        let right = self.physical_index(swap[1])?;
1747        let verified = self.neighbors_by_index[left]
1748            .iter()
1749            .find(|neighbor| neighbor.index == right)
1750            .map(|neighbor| neighbor.swap)
1751            .ok_or_else(|| {
1752                CompilerError::InvariantViolation(format!(
1753                    "SABRE attempted to emit non-movement SWAP({}, {})",
1754                    swap[0], swap[1]
1755                ))
1756            })?;
1757        let emitted = verified
1758            .emitted_indices
1759            .map(|index| self.physical_qubits[index]);
1760        Ok((emitted, verified))
1761    }
1762
1763    fn swap_cost(&self, swap: [PhysicalQubit; 2]) -> Result<NativePlanCost, CompilerError> {
1764        let (_, verified) = self.verified_swap(swap)?;
1765        Ok(verified.cost)
1766    }
1767
1768    fn swap_operation(&self, swap: [PhysicalQubit; 2]) -> Result<Operation, CompilerError> {
1769        let (emitted, _) = self.verified_swap(swap)?;
1770        if self.native_cost_enabled {
1771            let state =
1772                DeviceGateState::standard(StandardGate::SWAP, SmallVec::from_slice(&emitted));
1773            if !self.native_plans.contains_key(&state) {
1774                let detail = self.native_unsupported.get(&state).map_or_else(
1775                    || "native SWAP plan was not prepared".to_string(),
1776                    ToString::to_string,
1777                );
1778                return Err(CompilerError::InvariantViolation(format!(
1779                    "SABRE attempted to emit SWAP({}, {}) without a verified exact-qargs native plan: {detail}",
1780                    emitted[0], emitted[1]
1781                )));
1782            }
1783        }
1784        Ok(swap_operation(emitted))
1785    }
1786
1787    fn has_terminal(&self, requirement: usize) -> bool {
1788        match self.requirements.get(requirement) {
1789            Some(RequirementTable::Unary { terminals, .. }) => {
1790                terminals.iter().any(Option::is_some)
1791            }
1792            Some(RequirementTable::Pair { terminals, .. }) => !terminals.is_empty(),
1793            None => false,
1794        }
1795    }
1796
1797    fn distance_steps_for(
1798        &self,
1799        requirement: usize,
1800        placement: RequirementPlacement,
1801    ) -> Option<u32> {
1802        self.route_lower_bound_for_cached(requirement, placement, None)
1803            .map(|bound| bound.remaining_swaps)
1804    }
1805
1806    fn route_lower_bound_for_cached(
1807        &self,
1808        requirement: usize,
1809        placement: RequirementPlacement,
1810        cache: Option<&TrialPairCache>,
1811    ) -> Option<RouteLowerBound> {
1812        if !self.native_cost_enabled {
1813            let remaining_swaps = match placement {
1814                RequirementPlacement::Unary(physical) => {
1815                    (physical < self.physical_qubits.len()).then_some(0)?
1816                }
1817                RequirementPlacement::Pair([left, right]) => {
1818                    self.topology_distance(left, right)?.checked_sub(1)?
1819                }
1820            };
1821            return Some(RouteLowerBound {
1822                remaining_swaps,
1823                native: NativePlanCost::default(),
1824            });
1825        }
1826        match (self.requirements.get(requirement)?, placement) {
1827            (
1828                RequirementTable::Unary { lower_bounds, .. },
1829                RequirementPlacement::Unary(physical),
1830            ) => lower_bounds.get(physical).copied().flatten(),
1831            (
1832                RequirementTable::Pair {
1833                    terminals,
1834                    lower_bounds,
1835                },
1836                RequirementPlacement::Pair([left, right]),
1837            ) => lower_bounds.as_ref().map_or_else(
1838                || {
1839                    let key = (requirement, left, right);
1840                    if let Some(cache) = cache
1841                        && let Some(value) = cache.get(&key)
1842                    {
1843                        return value;
1844                    }
1845                    let value =
1846                        self.lazy_pair_route_lower_bound(requirement, terminals, left, right);
1847                    if let Some(cache) = cache {
1848                        cache.insert(key, value);
1849                    }
1850                    value
1851                },
1852                |bounds| bounds.get(left, right),
1853            ),
1854            _ => None,
1855        }
1856    }
1857
1858    fn lazy_pair_route_lower_bound(
1859        &self,
1860        requirement: usize,
1861        terminals: &BTreeMap<[usize; 2], NativePlanCost>,
1862        left: usize,
1863        right: usize,
1864    ) -> Option<RouteLowerBound> {
1865        let key = (requirement, left, right);
1866        lazy_pair_lookup_or_compute(&self.lazy_pair_cache, key, || {
1867            pair_route_lower_bound_from_state(&self.neighbors_by_index, terminals, [left, right])
1868        })
1869    }
1870
1871    fn interaction_id_for_node(
1872        &self,
1873        sabre: &SabreDag,
1874        node: NodeIndex,
1875    ) -> Result<usize, CompilerError> {
1876        let signature = sabre.graph[node].interaction_signature()?;
1877        self.interaction_ids
1878            .get(&signature)
1879            .copied()
1880            .or_else(|| {
1881                let generic = match &sabre.graph[node].kind {
1882                    SabreNodeKind::Unary(_) => InteractionSignature::GenericUnary,
1883                    SabreNodeKind::TwoQ(_)
1884                    | SabreNodeKind::Synchronize
1885                    | SabreNodeKind::ControlFlow(_) => InteractionSignature::GenericPair,
1886                };
1887                self.interaction_ids.get(&generic).copied()
1888            })
1889            .ok_or_else(|| {
1890                CompilerError::InvariantViolation(
1891                    "routing target has no generic interaction model".to_string(),
1892                )
1893            })
1894    }
1895
1896    fn native_cost(&self, state: &DeviceGateState) -> Option<NativePlanCost> {
1897        self.native_plans.get(state).map(|plan| plan.cost)
1898    }
1899
1900    fn native_plan(&self, state: &DeviceGateState) -> Option<&StructuralNativePlan> {
1901        self.native_plans.get(state)
1902    }
1903
1904    fn unsupported_native_plan(&self, state: &DeviceGateState) -> Option<&DeviceLoweringFailure> {
1905        self.native_unsupported.get(state)
1906    }
1907}
1908
1909fn lazy_pair_lookup_or_compute(
1910    cache: &Mutex<LazyPairCache>,
1911    key: (usize, usize, usize),
1912    compute: impl FnOnce() -> Option<RouteLowerBound>,
1913) -> Option<RouteLowerBound> {
1914    enum Lookup {
1915        Ready(Option<RouteLowerBound>),
1916        Wait(Arc<LazyPairFlight>),
1917        Compute(Arc<LazyPairFlight>),
1918    }
1919
1920    let mut compute = Some(compute);
1921    loop {
1922        let lookup = {
1923            let mut cache = cache
1924                .lock()
1925                .unwrap_or_else(|poisoned| poisoned.into_inner());
1926            if let Some(value) = cache.values.get(&key).copied() {
1927                Lookup::Ready(value)
1928            } else if let Some(flight) = cache.flights.get(&key) {
1929                Lookup::Wait(Arc::clone(flight))
1930            } else {
1931                let flight = Arc::new(LazyPairFlight::default());
1932                cache.flights.insert(key, Arc::clone(&flight));
1933                Lookup::Compute(flight)
1934            }
1935        };
1936
1937        match lookup {
1938            Lookup::Ready(value) => return value,
1939            Lookup::Wait(flight) => {
1940                let mut state = flight
1941                    .state
1942                    .lock()
1943                    .unwrap_or_else(|poisoned| poisoned.into_inner());
1944                while matches!(*state, LazyPairFlightState::Pending) {
1945                    state = flight
1946                        .ready
1947                        .wait(state)
1948                        .unwrap_or_else(|poisoned| poisoned.into_inner());
1949                }
1950                match *state {
1951                    LazyPairFlightState::Ready(value) => return value,
1952                    LazyPairFlightState::Aborted => continue,
1953                    LazyPairFlightState::Pending => continue,
1954                }
1955            }
1956            Lookup::Compute(flight) => {
1957                let computation = LazyPairComputation {
1958                    cache,
1959                    key,
1960                    flight,
1961                    completed: false,
1962                };
1963                let compute = compute
1964                    .take()
1965                    .expect("a lazy pair lookup computes at most once");
1966                let value = compute();
1967                computation.publish(value);
1968                return value;
1969            }
1970        }
1971    }
1972}
1973
1974fn undirected_topology_edges(physical: &PhysicalLayoutGraph) -> Vec<(usize, usize)> {
1975    physical.undirected_edges_by_index().collect()
1976}
1977
1978fn device_declares_routing_capability(device: &Device, physical_qubits: &[PhysicalQubit]) -> bool {
1979    if !device.native_gates().is_empty() {
1980        return true;
1981    }
1982    if physical_qubits.iter().copied().any(|physical| {
1983        device
1984            .qubit_properties(physical)
1985            .is_some_and(|properties| !properties.native_instructions().is_empty())
1986    }) {
1987        return true;
1988    }
1989    let usable = physical_qubits.iter().copied().collect::<BTreeSet<_>>();
1990    device.topology().undirected_edges().any(|(left, right)| {
1991        usable.contains(&left)
1992            && usable.contains(&right)
1993            && [
1994                device.edge_properties(left, right),
1995                device.edge_properties(right, left),
1996            ]
1997            .into_iter()
1998            .flatten()
1999            .any(|properties| !properties.native_instructions().is_empty())
2000    })
2001}
2002
2003impl SabreNode {
2004    fn interaction_signature(&self) -> Result<InteractionSignature, CompilerError> {
2005        let logicals: SmallVec<[LogicalQubit; 2]> = match &self.kind {
2006            SabreNodeKind::Unary(logical) => smallvec![*logical],
2007            SabreNodeKind::TwoQ(pair) => SmallVec::from_slice(pair),
2008            SabreNodeKind::Synchronize | SabreNodeKind::ControlFlow(_) => {
2009                return Ok(InteractionSignature::GenericPair);
2010            }
2011        };
2012        if self.operations.is_empty() {
2013            return Ok(match &self.kind {
2014                SabreNodeKind::Unary(_) => InteractionSignature::GenericUnary,
2015                SabreNodeKind::TwoQ(_)
2016                | SabreNodeKind::Synchronize
2017                | SabreNodeKind::ControlFlow(_) => InteractionSignature::GenericPair,
2018            });
2019        }
2020
2021        let mut operations = Vec::new();
2022        for operation in &self.operations {
2023            let Some(instruction) =
2024                KnowledgeInstructionKey::from_instruction(&operation.instruction)
2025            else {
2026                match operation.instruction {
2027                    Instruction::UnitaryGate(_) | Instruction::CircuitGate(_) => {
2028                        return Err(CompilerError::InvalidInput(format!(
2029                            "sabre device preparation requires {} to be decomposed before routing",
2030                            operation.instruction
2031                        )));
2032                    }
2033                    _ => continue,
2034                }
2035            };
2036            if matches!(
2037                instruction,
2038                KnowledgeInstructionKey::Standard(StandardGate::GPhase)
2039            ) {
2040                continue;
2041            }
2042            let mut qarg_roles = SmallVec::new();
2043            for qubit in &operation.qubits {
2044                let logical = LogicalQubit::from_qubit(*qubit);
2045                let Some(role) = logicals.iter().position(|candidate| *candidate == logical) else {
2046                    return Err(CompilerError::InvariantViolation(format!(
2047                        "folded SABRE node on {logicals:?} contains operation {} on unrelated logical qubit {logical}",
2048                        operation.instruction
2049                    )));
2050                };
2051                qarg_roles.push(role as u8);
2052            }
2053            operations.push(InteractionOperation {
2054                instruction,
2055                qarg_roles,
2056            });
2057        }
2058        if operations.is_empty() {
2059            Ok(match &self.kind {
2060                SabreNodeKind::Unary(_) => InteractionSignature::GenericUnary,
2061                SabreNodeKind::TwoQ(_)
2062                | SabreNodeKind::Synchronize
2063                | SabreNodeKind::ControlFlow(_) => InteractionSignature::GenericPair,
2064            })
2065        } else {
2066            match logicals.len() {
2067                1 => Ok(InteractionSignature::Unary(operations)),
2068                2 => Ok(InteractionSignature::Pair(operations)),
2069                arity => Err(CompilerError::InvariantViolation(format!(
2070                    "routing interaction signature has unsupported arity {arity}"
2071                ))),
2072            }
2073        }
2074    }
2075}
2076
2077impl SabreDag {
2078    fn collect_interaction_signatures(
2079        &self,
2080        output: &mut HashSet<InteractionSignature>,
2081    ) -> Result<(), CompilerError> {
2082        for node in self.graph.node_weights() {
2083            match &node.kind {
2084                SabreNodeKind::Unary(_) | SabreNodeKind::TwoQ(_) => {
2085                    output.insert(node.interaction_signature()?);
2086                }
2087                SabreNodeKind::ControlFlow(SabreControlFlow::If {
2088                    then_body,
2089                    else_body,
2090                    ..
2091                }) => {
2092                    then_body.collect_interaction_signatures(output)?;
2093                    if let Some(else_body) = else_body {
2094                        else_body.collect_interaction_signatures(output)?;
2095                    }
2096                }
2097                SabreNodeKind::ControlFlow(
2098                    SabreControlFlow::While { body, .. } | SabreControlFlow::For { body, .. },
2099                ) => body.collect_interaction_signatures(output)?,
2100                SabreNodeKind::ControlFlow(SabreControlFlow::Switch { cases, default, .. }) => {
2101                    for case in cases {
2102                        case.body.collect_interaction_signatures(output)?;
2103                    }
2104                    if let Some(default) = default {
2105                        default.collect_interaction_signatures(output)?;
2106                    }
2107                }
2108                SabreNodeKind::Synchronize => {}
2109            }
2110        }
2111        Ok(())
2112    }
2113
2114    fn ordered_interaction_signatures(&self) -> Result<Vec<InteractionSignature>, CompilerError> {
2115        let mut signatures = HashSet::from([
2116            InteractionSignature::GenericUnary,
2117            InteractionSignature::GenericPair,
2118        ]);
2119        self.collect_interaction_signatures(&mut signatures)?;
2120        let mut signatures = signatures.into_iter().collect::<Vec<_>>();
2121        signatures.sort();
2122        Ok(signatures)
2123    }
2124}
2125
2126fn states_for_requirement(
2127    operations: &[InteractionOperation],
2128    ordered_qargs: &[PhysicalQubit],
2129) -> Vec<DeviceGateState> {
2130    operations
2131        .iter()
2132        .map(|operation| DeviceGateState {
2133            instruction: operation.instruction.clone(),
2134            ordered_qargs: operation
2135                .qarg_roles
2136                .iter()
2137                .map(|role| ordered_qargs[usize::from(*role)])
2138                .collect(),
2139        })
2140        .collect()
2141}
2142
2143fn combined_catalog_cost(
2144    catalog: &NativePlanCatalog,
2145    states: Vec<DeviceGateState>,
2146) -> Option<NativePlanCost> {
2147    let mut cost = None;
2148    for state in states {
2149        let summary = catalog.summary(&state)?;
2150        let next = structural_native_cost(summary);
2151        cost = Some(cost.map_or(next, |current: NativePlanCost| current.combine(next)));
2152    }
2153    Some(cost.unwrap_or_default())
2154}
2155
2156fn collect_device_plan_roots(
2157    physical_qubits: &[PhysicalQubit],
2158    topology_edges: &[(usize, usize)],
2159    signatures: &[InteractionSignature],
2160) -> Vec<DeviceGateState> {
2161    let mut roots = Vec::new();
2162    for signature in signatures {
2163        match signature {
2164            InteractionSignature::Unary(operations) => {
2165                for &physical in physical_qubits {
2166                    roots.extend(states_for_requirement(operations, &[physical]));
2167                }
2168            }
2169            InteractionSignature::GenericUnary
2170            | InteractionSignature::GenericPair
2171            | InteractionSignature::Pair(_) => {}
2172        }
2173    }
2174    for &(left_index, right_index) in topology_edges {
2175        let left = physical_qubits[left_index];
2176        let right = physical_qubits[right_index];
2177        for ordered in [[left, right], [right, left]] {
2178            roots.push(DeviceGateState::standard(
2179                StandardGate::SWAP,
2180                SmallVec::from_slice(&ordered),
2181            ));
2182        }
2183        for signature in signatures {
2184            let InteractionSignature::Pair(operations) = signature else {
2185                continue;
2186            };
2187            for ordered in [[left, right], [right, left]] {
2188                roots.extend(states_for_requirement(operations, &ordered));
2189            }
2190        }
2191    }
2192    roots
2193}
2194
2195struct DeviceRequirementInputs<'a> {
2196    physical_qubits: &'a [PhysicalQubit],
2197    topology_edges: &'a [(usize, usize)],
2198    movement_edges: &'a [MovementEdge],
2199    neighbors: &'a [Vec<MovementNeighbor>],
2200    catalog: &'a NativePlanCatalog,
2201    native_identity: NativePlanCost,
2202}
2203
2204enum PreparedRequirementTerminals {
2205    Unary(Vec<Option<NativePlanCost>>),
2206    Pair(BTreeMap<[usize; 2], NativePlanCost>),
2207}
2208
2209fn build_device_requirement_tables(
2210    signatures: &[InteractionSignature],
2211    inputs: DeviceRequirementInputs<'_>,
2212    pair_state_budget: &mut usize,
2213) -> Vec<RequirementTable> {
2214    let DeviceRequirementInputs {
2215        physical_qubits,
2216        topology_edges,
2217        movement_edges,
2218        neighbors,
2219        catalog,
2220        native_identity,
2221    } = inputs;
2222    let count = physical_qubits.len();
2223    let mut prepared = signatures
2224        .iter()
2225        .map(|signature| match signature {
2226            InteractionSignature::GenericUnary => {
2227                PreparedRequirementTerminals::Unary(vec![Some(native_identity); count])
2228            }
2229            InteractionSignature::GenericPair => {
2230                let mut terminals = BTreeMap::new();
2231                for edge in movement_edges {
2232                    let [left, right] = edge.endpoints;
2233                    terminals.insert([left, right], native_identity);
2234                    terminals.insert([right, left], native_identity);
2235                }
2236                PreparedRequirementTerminals::Pair(terminals)
2237            }
2238            InteractionSignature::Unary(operations) => {
2239                let mut terminals = vec![None; count];
2240                for (physical_index, physical) in physical_qubits.iter().copied().enumerate() {
2241                    let states = states_for_requirement(operations, &[physical]);
2242                    if let Some(cost) = combined_catalog_cost(catalog, states) {
2243                        terminals[physical_index] = Some(cost);
2244                    }
2245                }
2246                PreparedRequirementTerminals::Unary(terminals)
2247            }
2248            InteractionSignature::Pair(operations) => {
2249                let mut terminals = BTreeMap::new();
2250                for &(left_index, right_index) in topology_edges {
2251                    for (source, target) in [(left_index, right_index), (right_index, left_index)] {
2252                        let ordered = [physical_qubits[source], physical_qubits[target]];
2253                        let states = states_for_requirement(operations, &ordered);
2254                        if let Some(cost) = combined_catalog_cost(catalog, states) {
2255                            terminals.insert([source, target], cost);
2256                        }
2257                    }
2258                }
2259                PreparedRequirementTerminals::Pair(terminals)
2260            }
2261        })
2262        .collect::<Vec<_>>();
2263
2264    // Refinement DAGs deliberately use a generic pair signature. It is
2265    // terminal wherever at least one exact source interaction is terminal;
2266    // final route scoring still checks the exact folded signature. Collect the
2267    // final terminal set before building any lower-bound table, so the generic
2268    // table is built once. Exact source signatures receive eager-state budget
2269    // first; the generic refinement table uses only the remaining budget.
2270    if let Some(generic_index) = signatures
2271        .iter()
2272        .position(|signature| matches!(signature, InteractionSignature::GenericPair))
2273    {
2274        let generic_terminals = prepared
2275            .iter()
2276            .enumerate()
2277            .filter(|(index, _)| *index != generic_index)
2278            .map(|(_, requirement)| requirement)
2279            .filter_map(|requirement| match requirement {
2280                PreparedRequirementTerminals::Pair(terminals) => Some(terminals.keys().copied()),
2281                PreparedRequirementTerminals::Unary(_) => None,
2282            })
2283            .flatten()
2284            .collect::<BTreeSet<_>>();
2285        if let PreparedRequirementTerminals::Pair(terminals) = &mut prepared[generic_index] {
2286            terminals.extend(
2287                generic_terminals
2288                    .into_iter()
2289                    .map(|placement| (placement, native_identity)),
2290            );
2291        }
2292    }
2293
2294    let mut prepared = prepared.into_iter().enumerate().collect::<Vec<_>>();
2295    prepared
2296        .sort_by_key(|(index, _)| matches!(signatures[*index], InteractionSignature::GenericPair));
2297    let mut requirements = BTreeMap::new();
2298    for (index, terminals) in prepared {
2299        let requirement = match terminals {
2300            PreparedRequirementTerminals::Unary(terminals) => RequirementTable::Unary {
2301                lower_bounds: unary_route_lower_bounds(neighbors, &terminals),
2302                terminals,
2303            },
2304            PreparedRequirementTerminals::Pair(terminals) => RequirementTable::Pair {
2305                lower_bounds: eager_pair_route_lower_bounds(
2306                    neighbors,
2307                    &terminals,
2308                    pair_state_budget,
2309                ),
2310                terminals,
2311            },
2312        };
2313        requirements.insert(index, requirement);
2314    }
2315    requirements.into_values().collect()
2316}
2317
2318fn movement_adjacency(count: usize, edges: &[MovementEdge]) -> Vec<Vec<MovementNeighbor>> {
2319    let mut sparse_costs = vec![BTreeMap::new(); count];
2320    for edge in edges {
2321        let [left, right] = edge.endpoints;
2322        sparse_costs[left].insert(right, edge.swap);
2323        sparse_costs[right].insert(left, edge.swap);
2324    }
2325    sparse_costs
2326        .into_iter()
2327        .map(|adjacent| {
2328            adjacent
2329                .into_iter()
2330                .map(|(index, swap)| MovementNeighbor { index, swap })
2331                .collect()
2332        })
2333        .collect()
2334}
2335
2336fn unary_route_lower_bounds(
2337    swap_neighbors: &[Vec<MovementNeighbor>],
2338    terminals: &[Option<NativePlanCost>],
2339) -> Vec<Option<RouteLowerBound>> {
2340    let mut bounds = terminals
2341        .iter()
2342        .map(|terminal| {
2343            terminal.map(|native| RouteLowerBound {
2344                remaining_swaps: 0,
2345                native,
2346            })
2347        })
2348        .collect::<Vec<_>>();
2349    let mut queue = bounds
2350        .iter()
2351        .enumerate()
2352        .filter_map(|(physical, bound)| bound.map(|_| physical))
2353        .collect::<VecDeque<_>>();
2354    while let Some(physical) = queue.pop_front() {
2355        let current = bounds[physical].expect("queued unary state has a lower bound");
2356        for &neighbor in &swap_neighbors[physical] {
2357            let predecessor = neighbor.index;
2358            let candidate = current.with_swap(neighbor.swap.cost);
2359            if bounds[predecessor].is_none_or(|previous| candidate.compare(previous).is_lt()) {
2360                bounds[predecessor] = Some(candidate);
2361                queue.push_back(predecessor);
2362            }
2363        }
2364    }
2365    bounds
2366}
2367
2368fn pair_after_swap(pair: [usize; 2], swap: [usize; 2]) -> [usize; 2] {
2369    pair.map(|physical| {
2370        if physical == swap[0] {
2371            swap[1]
2372        } else if physical == swap[1] {
2373            swap[0]
2374        } else {
2375            physical
2376        }
2377    })
2378}
2379
2380fn pair_route_lower_bounds(
2381    swap_neighbors: &[Vec<MovementNeighbor>],
2382    terminals: &BTreeMap<[usize; 2], NativePlanCost>,
2383) -> PairStateTable<RouteLowerBound> {
2384    let count = swap_neighbors.len();
2385    let mut bounds = PairStateTable::new(count);
2386    let mut queue = VecDeque::new();
2387    for (&[left, right], &native) in terminals {
2388        if left == right || left >= count || right >= count {
2389            continue;
2390        }
2391        bounds.set(
2392            left,
2393            right,
2394            RouteLowerBound {
2395                remaining_swaps: 0,
2396                native,
2397            },
2398        );
2399        queue.push_back((left, right));
2400    }
2401
2402    while let Some((left, right)) = queue.pop_front() {
2403        let current = bounds
2404            .get(left, right)
2405            .expect("queued pair state has a lower bound");
2406        for endpoint in [left, right] {
2407            for &neighbor in &swap_neighbors[endpoint] {
2408                let [previous_left, previous_right] =
2409                    pair_after_swap([left, right], [endpoint, neighbor.index]);
2410                let candidate = current.with_swap(neighbor.swap.cost);
2411                if bounds
2412                    .get(previous_left, previous_right)
2413                    .is_none_or(|bound| candidate.compare(bound).is_lt())
2414                {
2415                    bounds.set(previous_left, previous_right, candidate);
2416                    queue.push_back((previous_left, previous_right));
2417                }
2418            }
2419        }
2420    }
2421    bounds
2422}
2423
2424fn eager_pair_route_lower_bounds(
2425    swap_neighbors: &[Vec<MovementNeighbor>],
2426    terminals: &BTreeMap<[usize; 2], NativePlanCost>,
2427    remaining_budget: &mut usize,
2428) -> Option<PairStateTable<RouteLowerBound>> {
2429    let count = swap_neighbors.len();
2430    let states = count.saturating_mul(count.saturating_sub(1));
2431    if states > *remaining_budget {
2432        return None;
2433    }
2434    *remaining_budget -= states;
2435    Some(pair_route_lower_bounds(swap_neighbors, terminals))
2436}
2437
2438fn pair_route_lower_bound_from_state(
2439    swap_neighbors: &[Vec<MovementNeighbor>],
2440    terminals: &BTreeMap<[usize; 2], NativePlanCost>,
2441    start: [usize; 2],
2442) -> Option<RouteLowerBound> {
2443    let count = swap_neighbors.len();
2444    PairStateTable::<RouteLowerBound>::index(count, start[0], start[1])?;
2445    let mut bounds = BTreeMap::<[usize; 2], RouteLowerBound>::new();
2446    let mut queue = BinaryHeap::new();
2447    for (&placement, &native) in terminals {
2448        if PairStateTable::<RouteLowerBound>::index(count, placement[0], placement[1]).is_none() {
2449            continue;
2450        }
2451        let bound = RouteLowerBound {
2452            remaining_swaps: 0,
2453            native,
2454        };
2455        bounds.insert(placement, bound);
2456        queue.push(Reverse((native.native_two_qubit_ops, 0_u32, placement)));
2457    }
2458
2459    while let Some(Reverse((native_two_qubit_ops, remaining_swaps, placement))) = queue.pop() {
2460        let current = bounds.get(&placement).copied()?;
2461        if current.native.native_two_qubit_ops != native_two_qubit_ops
2462            || current.remaining_swaps != remaining_swaps
2463        {
2464            continue;
2465        }
2466        if placement == start {
2467            return Some(current);
2468        }
2469        for endpoint in placement {
2470            for &neighbor in &swap_neighbors[endpoint] {
2471                let predecessor = pair_after_swap(placement, [endpoint, neighbor.index]);
2472                let candidate = current.with_swap(neighbor.swap.cost);
2473                if bounds
2474                    .get(&predecessor)
2475                    .is_none_or(|previous| candidate.compare(*previous).is_lt())
2476                {
2477                    bounds.insert(predecessor, candidate);
2478                    queue.push(Reverse((
2479                        candidate.native.native_two_qubit_ops,
2480                        candidate.remaining_swaps,
2481                        predecessor,
2482                    )));
2483                }
2484            }
2485        }
2486    }
2487    None
2488}
2489
2490#[derive(Debug, Clone)]
2491pub(crate) struct PreparedRouteMetadata {
2492    required_predecessors: Vec<u32>,
2493    requirement_ids: Vec<Option<usize>>,
2494    /// Prepared metadata for control-flow bodies, indexed by parent DAG node.
2495    /// Bodies use `SabreControlFlow::bodies` canonical traversal order.
2496    nested: Vec<Vec<PreparedRouteMetadata>>,
2497    high_pair_reuse: bool,
2498}
2499
2500impl PreparedRouteMetadata {
2501    pub(crate) fn new(sabre: &SabreDag, target: &RoutingTarget) -> Result<Self, CompilerError> {
2502        let mut required_predecessors = vec![0; sabre.graph.node_count()];
2503        for edge in sabre.graph.edge_references() {
2504            required_predecessors[edge.target().index()] += 1;
2505        }
2506        let requirement_ids = sabre
2507            .graph
2508            .node_indices()
2509            .map(|node| match sabre.graph[node].kind {
2510                SabreNodeKind::Unary(_) | SabreNodeKind::TwoQ(_) => {
2511                    target.interaction_id_for_node(sabre, node).map(Some)
2512                }
2513                SabreNodeKind::Synchronize | SabreNodeKind::ControlFlow(_) => Ok(None),
2514            })
2515            .collect::<Result<Vec<_>, CompilerError>>()?;
2516        let nested = sabre
2517            .graph
2518            .node_indices()
2519            .map(|node| match &sabre.graph[node].kind {
2520                SabreNodeKind::ControlFlow(flow) => flow
2521                    .bodies()
2522                    .into_iter()
2523                    .map(|body| Self::new(body, target))
2524                    .collect(),
2525                _ => Ok(Vec::new()),
2526            })
2527            .collect::<Result<Vec<_>, CompilerError>>()?;
2528        let mut two_qubit_count = 0usize;
2529        let mut unique_pairs = HashSet::new();
2530        for node in sabre.graph.node_weights() {
2531            if let SabreNodeKind::TwoQ([left, right]) = &node.kind {
2532                two_qubit_count += 1;
2533                let pair = if left <= right {
2534                    (*left, *right)
2535                } else {
2536                    (*right, *left)
2537                };
2538                unique_pairs.insert(pair);
2539            }
2540        }
2541        let high_pair_reuse = !unique_pairs.is_empty()
2542            && two_qubit_count >= unique_pairs.len().saturating_mul(HIGH_PAIR_REUSE_FACTOR);
2543        Ok(Self {
2544            required_predecessors,
2545            requirement_ids,
2546            nested,
2547            high_pair_reuse,
2548        })
2549    }
2550}
2551
2552#[derive(Debug)]
2553struct RoutingState {
2554    layout: DenseRoutingLayout,
2555    layout_hash: u64,
2556    front_layer: Layer,
2557    lookahead_layers: Vec<Layer>,
2558    required_predecessors: Vec<u32>,
2559    requirement_ids: Vec<Option<usize>>,
2560    lower_bound_cache: TrialPairCache,
2561    decay: Vec<f64>,
2562    active_marks: Vec<u32>,
2563    active_generation: u32,
2564    active_index_scratch: Vec<usize>,
2565    candidate_scratch: Vec<SwapChoice>,
2566    scored_scratch: Vec<ScoredCandidate>,
2567    eligible_scratch: Vec<ScoredCandidate>,
2568    lookahead_next_scratch: Vec<NodeIndex>,
2569    lookahead_visit_scratch: Vec<NodeIndex>,
2570    lookahead_decrements: Vec<u32>,
2571    lookahead_touched_scratch: Vec<usize>,
2572    rng: StdRng,
2573}
2574
2575#[derive(Debug, Clone, Copy)]
2576struct SwapChoice {
2577    physical: [PhysicalQubit; 2],
2578    emitted: [PhysicalQubit; 2],
2579    indices: [usize; 2],
2580    cost: NativePlanCost,
2581}
2582
2583#[derive(Debug, Clone, Copy)]
2584struct ScoredCandidate {
2585    choice: SwapChoice,
2586    adjusted_score: f64,
2587    /// `None` means the exact native route cost has not been queried yet.
2588    /// `Some(None)` records an unreachable candidate and must be cached too.
2589    route_cost: Option<Option<RouteLowerBound>>,
2590}
2591
2592impl ScoredCandidate {
2593    fn cached_route_cost(
2594        &mut self,
2595        calculate: impl FnOnce(SwapChoice) -> Option<RouteLowerBound>,
2596    ) -> Option<RouteLowerBound> {
2597        *self
2598            .route_cost
2599            .get_or_insert_with(|| calculate(self.choice))
2600    }
2601
2602    fn prepared_route_cost(self) -> Option<RouteLowerBound> {
2603        self.route_cost
2604            .expect("native route cost must be cached before candidate comparison")
2605    }
2606}
2607
2608impl RoutingState {
2609    fn prepared_requirement_id(
2610        requirement_ids: &[Option<usize>],
2611        node: NodeIndex,
2612    ) -> Result<usize, CompilerError> {
2613        requirement_ids
2614            .get(node.index())
2615            .copied()
2616            .flatten()
2617            .ok_or_else(|| {
2618                CompilerError::InvariantViolation(format!(
2619                    "routing node {} has no prepared requirement id",
2620                    node.index()
2621                ))
2622            })
2623    }
2624
2625    /// Creates mutable state for one SABRE routing trial.
2626    ///
2627    /// `required_predecessors` is the mutable readiness counter for DAG
2628    /// scheduling. Lookahead temporarily edits the same counters and restores
2629    /// them before returning to the real routing loop.
2630    fn new(
2631        sabre: &SabreDag,
2632        target: &RoutingTarget,
2633        metadata: &PreparedRouteMetadata,
2634        layout: &Layout,
2635        heuristic: &SabreHeuristicConfig,
2636        seed: u64,
2637    ) -> Result<Self, CompilerError> {
2638        let layout = DenseRoutingLayout::from_layout(layout, &target.physical_qubits)?;
2639        let layout_hash = MappingCycleDetector::mapping_hash(layout.signature());
2640        Ok(Self {
2641            layout,
2642            layout_hash,
2643            front_layer: Layer::new(sabre.graph.node_count(), target.physical_qubits.len()),
2644            lookahead_layers: vec![
2645                Layer::new(
2646                    sabre.graph.node_count(),
2647                    target.physical_qubits.len()
2648                );
2649                heuristic.lookahead_weights.len()
2650            ],
2651            required_predecessors: metadata.required_predecessors.clone(),
2652            requirement_ids: metadata.requirement_ids.clone(),
2653            lower_bound_cache: TrialPairCache::default(),
2654            decay: vec![1.0; target.physical_qubits.len()],
2655            active_marks: vec![0; target.physical_qubits.len()],
2656            active_generation: 0,
2657            active_index_scratch: Vec::new(),
2658            candidate_scratch: Vec::new(),
2659            scored_scratch: Vec::new(),
2660            eligible_scratch: Vec::new(),
2661            lookahead_next_scratch: Vec::new(),
2662            lookahead_visit_scratch: Vec::new(),
2663            lookahead_decrements: vec![0; sabre.graph.node_count()],
2664            lookahead_touched_scratch: Vec::new(),
2665            rng: StdRng::seed_from_u64(seed),
2666        })
2667    }
2668
2669    /// Applies a physical SWAP to the layout and all cached layer scores.
2670    ///
2671    /// The layout and every cached layer must move together; otherwise future
2672    /// SWAP deltas would be scored against stale physical positions.
2673    fn apply_swap(
2674        &mut self,
2675        swap: [PhysicalQubit; 2],
2676        target: &RoutingTarget,
2677    ) -> Result<(), CompilerError> {
2678        let swap_indices = [
2679            target.physical_index(swap[0])?,
2680            target.physical_index(swap[1])?,
2681        ];
2682        let before_left = self.layout.signature()[swap_indices[0]];
2683        let before_right = self.layout.signature()[swap_indices[1]];
2684        if target.native_cost_enabled {
2685            let distance = |requirement, placement| {
2686                target.distance_for_cached(requirement, placement, Some(&self.lower_bound_cache))
2687            };
2688            self.front_layer.apply_swap(swap_indices, &distance)?;
2689            for layer in &mut self.lookahead_layers {
2690                layer.apply_swap(swap_indices, &distance)?;
2691            }
2692        } else {
2693            let distances = target.topology_distance_values.as_ref();
2694            let width = target.physical_qubits.len();
2695            self.front_layer
2696                .apply_topology_swap(swap_indices, distances, width)?;
2697            for layer in &mut self.lookahead_layers {
2698                layer.apply_topology_swap(swap_indices, distances, width)?;
2699            }
2700        }
2701        self.layout
2702            .swap_physical_indices(swap_indices[0], swap_indices[1]);
2703        self.layout_hash = MappingCycleDetector::hash_after_swap(
2704            self.layout_hash,
2705            swap_indices,
2706            before_left,
2707            before_right,
2708        );
2709        Ok(())
2710    }
2711
2712    #[allow(clippy::too_many_arguments)]
2713    fn update_route(
2714        &mut self,
2715        sabre: &SabreDag,
2716        target: &RoutingTarget,
2717        metadata: &PreparedRouteMetadata,
2718        heuristic: &SabreHeuristicConfig,
2719        output: &mut TrialOutput,
2720        nodes: &[NodeIndex],
2721        initial_swaps: Option<&mut Vec<[PhysicalQubit; 2]>>,
2722    ) -> Result<(), CompilerError> {
2723        let mut to_visit = nodes.iter().copied().collect::<VecDeque<_>>();
2724        let mut pending_swaps = initial_swaps;
2725
2726        while let Some(node_id) = to_visit.pop_front() {
2727            let node = &sabre.graph[node_id];
2728            match &node.kind {
2729                SabreNodeKind::Unary(logical) => {
2730                    let physical_index = self.layout.physical_index(*logical)?;
2731                    let requirement =
2732                        Self::prepared_requirement_id(&self.requirement_ids, node_id)?;
2733                    let placement = RequirementPlacement::Unary(physical_index);
2734                    if target.terminal_cost_for(requirement, placement).is_none() {
2735                        let distance = |requirement, placement| {
2736                            target.distance_for_cached(
2737                                requirement,
2738                                placement,
2739                                Some(&self.lower_bound_cache),
2740                            )
2741                        };
2742                        self.front_layer
2743                            .insert(node_id, requirement, placement, &distance)?;
2744                        continue;
2745                    }
2746                    output.apply_pending_swaps(target, pending_swaps.take())?;
2747                    if output.emit_operations {
2748                        for operation in &node.operations {
2749                            output.push_mapped_operation(
2750                                &operation.operation,
2751                                Some(operation.source_order),
2752                                &self.layout,
2753                                target,
2754                            )?;
2755                        }
2756                    }
2757                }
2758                SabreNodeKind::TwoQ(pair) => {
2759                    // A two-qubit node that is still non-adjacent becomes part
2760                    // of the front layer. Adjacent nodes flush any pending
2761                    // SWAPs and are emitted under the current layout.
2762                    let physical = [
2763                        target.physical_at(self.layout.physical_index(pair[0])?)?,
2764                        target.physical_at(self.layout.physical_index(pair[1])?)?,
2765                    ];
2766                    let interaction =
2767                        Self::prepared_requirement_id(&self.requirement_ids, node_id)?;
2768                    let physical_indices = [
2769                        target.physical_index(physical[0])?,
2770                        target.physical_index(physical[1])?,
2771                    ];
2772                    if target
2773                        .terminal_cost_for(
2774                            interaction,
2775                            RequirementPlacement::Pair(physical_indices),
2776                        )
2777                        .is_none()
2778                    {
2779                        let distance = |requirement, placement| {
2780                            target.distance_for_cached(
2781                                requirement,
2782                                placement,
2783                                Some(&self.lower_bound_cache),
2784                            )
2785                        };
2786                        self.front_layer.insert(
2787                            node_id,
2788                            interaction,
2789                            RequirementPlacement::Pair(physical_indices),
2790                            &distance,
2791                        )?;
2792                        continue;
2793                    }
2794                    output.apply_pending_swaps(target, pending_swaps.take())?;
2795                    if output.emit_operations {
2796                        for operation in &node.operations {
2797                            output.push_mapped_operation(
2798                                &operation.operation,
2799                                Some(operation.source_order),
2800                                &self.layout,
2801                                target,
2802                            )?;
2803                        }
2804                    }
2805                }
2806                SabreNodeKind::Synchronize => {
2807                    // Synchronize nodes preserve parent-level ordering around
2808                    // classical or barrier-like effects but do not add an
2809                    // adjacency constraint of their own.
2810                    output.apply_pending_swaps(target, pending_swaps.take())?;
2811                    if output.emit_operations {
2812                        for operation in &node.operations {
2813                            output.push_mapped_operation(
2814                                &operation.operation,
2815                                Some(operation.source_order),
2816                                &self.layout,
2817                                target,
2818                            )?;
2819                        }
2820                    }
2821                }
2822                SabreNodeKind::ControlFlow(flow) => {
2823                    // Control-flow bodies are routed recursively from the
2824                    // current entry layout and restore that layout on exit, so
2825                    // parent routing can continue with a single layout state.
2826                    output.apply_pending_swaps(target, pending_swaps.take())?;
2827                    self.route_control_flow_node(
2828                        flow,
2829                        &node.operations,
2830                        metadata.nested.get(node_id.index()).ok_or_else(|| {
2831                            CompilerError::InvariantViolation(format!(
2832                                "routing node {} has no prepared nested metadata slot",
2833                                node_id.index()
2834                            ))
2835                        })?,
2836                        target,
2837                        heuristic,
2838                        output,
2839                    )?;
2840                }
2841            }
2842
2843            for edge in sabre.graph.edges_directed(node_id, Direction::Outgoing) {
2844                let successor = edge.target();
2845                self.required_predecessors[successor.index()] -= 1;
2846                if self.required_predecessors[successor.index()] == 0 {
2847                    to_visit.push_back(successor);
2848                }
2849            }
2850        }
2851
2852        if pending_swaps.is_some() {
2853            return Err(CompilerError::InvariantViolation(
2854                "sabre selected swaps that did not route any front-layer node".to_string(),
2855            ));
2856        }
2857        Ok(())
2858    }
2859
2860    fn route_control_flow_node(
2861        &mut self,
2862        flow: &SabreControlFlow,
2863        operations: &[SabreOperation],
2864        body_metadata: &[PreparedRouteMetadata],
2865        target: &RoutingTarget,
2866        heuristic: &SabreHeuristicConfig,
2867        output: &mut TrialOutput,
2868    ) -> Result<(), CompilerError> {
2869        let Some((first, rest)) = operations.split_first() else {
2870            return Ok(());
2871        };
2872        let entry_layout = self.layout.to_layout(&target.physical_qubits)?;
2873        // The SABRE DAG keeps the representative control-flow operation first
2874        // and may attach additional bookkeeping operations after it. Rebuild the
2875        // first operation with routed bodies, then map the remaining operations
2876        // through the unchanged parent layout.
2877        let emit_operations = output.emit_operations;
2878        let mut metadata = body_metadata.iter();
2879        let routed = match flow {
2880            SabreControlFlow::If {
2881                condition,
2882                then_body,
2883                else_body,
2884            } => {
2885                let then_result = route_control_flow_body(
2886                    then_body,
2887                    target,
2888                    metadata
2889                        .next()
2890                        .ok_or_else(|| missing_nested_metadata("if then"))?,
2891                    &entry_layout,
2892                    heuristic,
2893                    output.next_nested_seed(),
2894                    emit_operations,
2895                )?;
2896                let else_result = else_body
2897                    .as_ref()
2898                    .map(|body| {
2899                        route_control_flow_body(
2900                            body,
2901                            target,
2902                            metadata
2903                                .next()
2904                                .ok_or_else(|| missing_nested_metadata("if else"))?,
2905                            &entry_layout,
2906                            heuristic,
2907                            output.next_nested_seed(),
2908                            emit_operations,
2909                        )
2910                    })
2911                    .transpose()?;
2912                output.merge_nested(&then_result);
2913                if let Some(result) = &else_result {
2914                    output.merge_nested(result);
2915                }
2916                let then_operations = then_result.materialize_operations(target)?;
2917                let else_operations = else_result
2918                    .as_ref()
2919                    .map(|result| result.materialize_operations(target))
2920                    .transpose()?;
2921                let flow = ClassicalControlOp::If(IfOp::new(
2922                    condition.clone(),
2923                    ControlBody::new(then_operations),
2924                    else_operations.map(ControlBody::new),
2925                )?);
2926                let qubits = flow.used_qubits().into_iter().collect();
2927                Operation {
2928                    instruction: Instruction::ClassicalControl(flow),
2929                    qubits,
2930                    params: SmallVec::new(),
2931                    label: first.label.clone(),
2932                }
2933            }
2934            SabreControlFlow::While { condition, body } => {
2935                let body_result = route_control_flow_body(
2936                    body,
2937                    target,
2938                    metadata
2939                        .next()
2940                        .ok_or_else(|| missing_nested_metadata("while"))?,
2941                    &entry_layout,
2942                    heuristic,
2943                    output.next_nested_seed(),
2944                    emit_operations,
2945                )?;
2946                output.merge_nested(&body_result);
2947                let body_operations = body_result.materialize_operations(target)?;
2948                let flow = ClassicalControlOp::While(WhileOp::new(
2949                    condition.clone(),
2950                    ControlBody::new(body_operations),
2951                )?);
2952                let qubits = flow.used_qubits().into_iter().collect();
2953                Operation {
2954                    instruction: Instruction::ClassicalControl(flow),
2955                    qubits,
2956                    params: SmallVec::new(),
2957                    label: first.label.clone(),
2958                }
2959            }
2960            SabreControlFlow::For {
2961                var,
2962                start,
2963                stop,
2964                step,
2965                body,
2966            } => {
2967                let body_result = route_control_flow_body(
2968                    body,
2969                    target,
2970                    metadata
2971                        .next()
2972                        .ok_or_else(|| missing_nested_metadata("for"))?,
2973                    &entry_layout,
2974                    heuristic,
2975                    output.next_nested_seed(),
2976                    emit_operations,
2977                )?;
2978                output.merge_nested(&body_result);
2979                let body_operations = body_result.materialize_operations(target)?;
2980                let flow = ClassicalControlOp::For(ForOp::new(
2981                    *var,
2982                    start.clone(),
2983                    stop.clone(),
2984                    step.clone(),
2985                    ControlBody::new(body_operations),
2986                )?);
2987                let qubits = flow.used_qubits().into_iter().collect();
2988                Operation {
2989                    instruction: Instruction::ClassicalControl(flow),
2990                    qubits,
2991                    params: SmallVec::new(),
2992                    label: first.label.clone(),
2993                }
2994            }
2995            SabreControlFlow::Switch {
2996                target: switch_target,
2997                cases,
2998                default,
2999            } => {
3000                let mut routed_cases = Vec::with_capacity(cases.len());
3001                for case in cases {
3002                    let result = route_control_flow_body(
3003                        &case.body,
3004                        target,
3005                        metadata
3006                            .next()
3007                            .ok_or_else(|| missing_nested_metadata("switch case"))?,
3008                        &entry_layout,
3009                        heuristic,
3010                        output.next_nested_seed(),
3011                        emit_operations,
3012                    )?;
3013                    output.merge_nested(&result);
3014                    routed_cases.push(SwitchCase::new(
3015                        case.value,
3016                        ControlBody::new(result.materialize_operations(target)?),
3017                    ));
3018                }
3019                let routed_default = default
3020                    .as_ref()
3021                    .map(|body| {
3022                        route_control_flow_body(
3023                            body,
3024                            target,
3025                            metadata
3026                                .next()
3027                                .ok_or_else(|| missing_nested_metadata("switch default"))?,
3028                            &entry_layout,
3029                            heuristic,
3030                            output.next_nested_seed(),
3031                            emit_operations,
3032                        )
3033                    })
3034                    .transpose()?;
3035                if let Some(result) = &routed_default {
3036                    output.merge_nested(result);
3037                }
3038                let default_operations = routed_default
3039                    .as_ref()
3040                    .map(|result| result.materialize_operations(target))
3041                    .transpose()?;
3042                let flow = ClassicalControlOp::Switch(SwitchOp::new(
3043                    switch_target.clone(),
3044                    routed_cases,
3045                    default_operations.map(ControlBody::new),
3046                )?);
3047                let qubits = flow.used_qubits().into_iter().collect();
3048                Operation {
3049                    instruction: Instruction::ClassicalControl(flow),
3050                    qubits,
3051                    params: SmallVec::new(),
3052                    label: first.label.clone(),
3053                }
3054            }
3055        };
3056        if output.emit_operations {
3057            output.push_operation(routed, None, target)?;
3058            for operation in rest {
3059                output.push_mapped_operation(
3060                    &operation.operation,
3061                    Some(operation.source_order),
3062                    &self.layout,
3063                    target,
3064                )?;
3065            }
3066        }
3067        Ok(())
3068    }
3069
3070    fn populate_extended_set(
3071        &mut self,
3072        sabre: &SabreDag,
3073        target: &RoutingTarget,
3074    ) -> Result<(), CompilerError> {
3075        // Reuse a dense shadow of the readiness counters and visit buffers.
3076        // Exploration touches only successors of the current front, while the
3077        // real scheduler counters are restored before returning.
3078        let mut next_visit = std::mem::take(&mut self.lookahead_next_scratch);
3079        let mut to_visit = std::mem::take(&mut self.lookahead_visit_scratch);
3080        let mut touched = std::mem::take(&mut self.lookahead_touched_scratch);
3081        next_visit.clear();
3082        to_visit.clear();
3083        touched.clear();
3084        next_visit.extend(self.front_layer.iter_nodes());
3085        self.lookahead_layers.iter_mut().for_each(Layer::clear);
3086
3087        let layout = &self.layout;
3088        let requirement_ids = &self.requirement_ids;
3089        let cache = &self.lower_bound_cache;
3090        let required_predecessors = &mut self.required_predecessors;
3091        let decrements = &mut self.lookahead_decrements;
3092
3093        for layer in &mut self.lookahead_layers {
3094            for node in next_visit.drain(..) {
3095                release_shadow_successors(
3096                    sabre,
3097                    node,
3098                    required_predecessors,
3099                    decrements,
3100                    &mut touched,
3101                    &mut to_visit,
3102                );
3103            }
3104
3105            let mut index = 0;
3106            while index < to_visit.len() {
3107                let node = to_visit[index];
3108                match &sabre.graph[node].kind {
3109                    SabreNodeKind::Unary(logical) => {
3110                        if !target.native_cost_enabled {
3111                            // Topology-only unary requirements are executable
3112                            // at every usable physical position. They do not
3113                            // consume routing horizon: release their successors
3114                            // in the same shadow layer, matching the scheduler.
3115                            release_shadow_successors(
3116                                sabre,
3117                                node,
3118                                required_predecessors,
3119                                decrements,
3120                                &mut touched,
3121                                &mut to_visit,
3122                            );
3123                        } else if let Ok(physical_index) = layout.physical_index(*logical) {
3124                            let requirement = Self::prepared_requirement_id(requirement_ids, node)?;
3125                            let placement = RequirementPlacement::Unary(physical_index);
3126                            let distance = |requirement, placement| {
3127                                target.distance_for_cached(requirement, placement, Some(cache))
3128                            };
3129                            layer.insert(node, requirement, placement, &distance)?;
3130                            next_visit.push(node);
3131                        }
3132                    }
3133                    SabreNodeKind::TwoQ(pair) => {
3134                        if let (Ok(left), Ok(right)) = (
3135                            layout.physical_index(pair[0]),
3136                            layout.physical_index(pair[1]),
3137                        ) {
3138                            let interaction = Self::prepared_requirement_id(requirement_ids, node)?;
3139                            let placement = RequirementPlacement::Pair([left, right]);
3140                            let distance = |requirement, placement| {
3141                                target.distance_for_cached(requirement, placement, Some(cache))
3142                            };
3143                            layer.insert(node, interaction, placement, &distance)?;
3144                            next_visit.push(node);
3145                        }
3146                    }
3147                    SabreNodeKind::Synchronize | SabreNodeKind::ControlFlow(_) => {
3148                        release_shadow_successors(
3149                            sabre,
3150                            node,
3151                            required_predecessors,
3152                            decrements,
3153                            &mut touched,
3154                            &mut to_visit,
3155                        );
3156                    }
3157                }
3158                index += 1;
3159            }
3160            to_visit.clear();
3161        }
3162
3163        for index in touched.iter().copied() {
3164            required_predecessors[index] += decrements[index];
3165            decrements[index] = 0;
3166        }
3167        self.lookahead_next_scratch = next_visit;
3168        self.lookahead_visit_scratch = to_visit;
3169        self.lookahead_touched_scratch = touched;
3170        Ok(())
3171    }
3172
3173    fn choose_best_swap(
3174        &mut self,
3175        target: &RoutingTarget,
3176        heuristic: &SabreHeuristicConfig,
3177        previous_swap: Option<[PhysicalQubit; 2]>,
3178    ) -> Result<SwapChoice, CompilerError> {
3179        if self.front_layer.is_empty() {
3180            return Err(CompilerError::InvariantViolation(
3181                "sabre cannot select a SWAP for an empty front layer".to_string(),
3182            ));
3183        }
3184        self.active_generation = self.active_generation.wrapping_add(1);
3185        if self.active_generation == 0 {
3186            self.active_marks.fill(0);
3187            self.active_generation = 1;
3188        }
3189        let mut active_indices = std::mem::take(&mut self.active_index_scratch);
3190        active_indices.clear();
3191        for active_index in self.front_layer.active_indices() {
3192            if self.active_marks[active_index] != self.active_generation {
3193                self.active_marks[active_index] = self.active_generation;
3194                active_indices.push(active_index);
3195            }
3196        }
3197        // Preserve the candidate order of the previous full physical-table
3198        // scan so seeded tie-breaking remains bit-for-bit deterministic.
3199        active_indices.sort_unstable();
3200
3201        let mut candidates = std::mem::take(&mut self.candidate_scratch);
3202        candidates.clear();
3203        for &active_index in &active_indices {
3204            let active = target.physical_at(active_index)?;
3205            for &movement_neighbor in &target.neighbors_by_index[active_index] {
3206                let neighbor_index = movement_neighbor.index;
3207                if neighbor_index < active_index
3208                    && self.active_marks[neighbor_index] == self.active_generation
3209                {
3210                    continue;
3211                }
3212                let neighbor = target.physical_at(neighbor_index)?;
3213                candidates.push(if active <= neighbor {
3214                    SwapChoice {
3215                        physical: [active, neighbor],
3216                        emitted: movement_neighbor
3217                            .swap
3218                            .emitted_indices
3219                            .map(|index| target.physical_qubits[index]),
3220                        indices: [active_index, neighbor_index],
3221                        cost: movement_neighbor.swap.cost,
3222                    }
3223                } else {
3224                    SwapChoice {
3225                        physical: [neighbor, active],
3226                        emitted: movement_neighbor
3227                            .swap
3228                            .emitted_indices
3229                            .map(|index| target.physical_qubits[index]),
3230                        indices: [neighbor_index, active_index],
3231                        cost: movement_neighbor.swap.cost,
3232                    }
3233                });
3234            }
3235        }
3236        if candidates.len() > 1
3237            && let Some(previous_swap) = previous_swap
3238        {
3239            let previous_swap = if previous_swap[0] <= previous_swap[1] {
3240                previous_swap
3241            } else {
3242                [previous_swap[1], previous_swap[0]]
3243            };
3244            candidates.retain(|candidate| candidate.physical != previous_swap);
3245        }
3246        if candidates.is_empty() {
3247            return Err(CompilerError::TransformFailed {
3248                name: "sabre_route",
3249                reason: "no candidate SWAP can affect the front layer".to_string(),
3250            });
3251        }
3252
3253        let mut best_score = f64::INFINITY;
3254        let mut scored = std::mem::take(&mut self.scored_scratch);
3255        scored.clear();
3256        scored.reserve(candidates.len().saturating_sub(scored.capacity()));
3257        for candidate in candidates.drain(..) {
3258            let structural_score = if target.native_cost_enabled {
3259                let distance = |requirement, placement| {
3260                    target.distance_for_cached(
3261                        requirement,
3262                        placement,
3263                        Some(&self.lower_bound_cache),
3264                    )
3265                };
3266                heuristic_score_after_swap(
3267                    &self.front_layer,
3268                    &self.lookahead_layers,
3269                    heuristic,
3270                    candidate.indices,
3271                    &distance,
3272                )?
3273            } else {
3274                heuristic_topology_score_after_swap(
3275                    &self.front_layer,
3276                    &self.lookahead_layers,
3277                    heuristic,
3278                    candidate.indices,
3279                    target.topology_distance_values.as_ref(),
3280                    target.physical_qubits.len(),
3281                )
3282            };
3283            let score = if heuristic.decay_increment.is_some() {
3284                structural_score
3285                    * self.decay[candidate.indices[0]].max(self.decay[candidate.indices[1]])
3286            } else {
3287                structural_score
3288            };
3289
3290            best_score = best_score.min(score);
3291            scored.push(ScoredCandidate {
3292                choice: candidate,
3293                adjusted_score: score,
3294                route_cost: None,
3295            });
3296        }
3297
3298        let mut eligible = std::mem::take(&mut self.eligible_scratch);
3299        eligible.clear();
3300        let candidate_window = 0.25 * heuristic.basic_weight;
3301        eligible.extend(scored.drain(..).filter(|candidate| {
3302            candidate.adjusted_score <= best_score + candidate_window + heuristic.best_epsilon
3303        }));
3304        if target.native_cost_enabled {
3305            for candidate in &mut eligible {
3306                candidate.cached_route_cost(|choice| self.route_cost_after_swap(target, choice));
3307            }
3308            eligible.sort_by(|left, right| {
3309                compare_optional_route_bound(
3310                    left.prepared_route_cost(),
3311                    right.prepared_route_cost(),
3312                )
3313                .then_with(|| left.adjusted_score.total_cmp(&right.adjusted_score))
3314                .then_with(|| left.choice.physical.cmp(&right.choice.physical))
3315            });
3316            if let Some(best) = eligible.first().copied() {
3317                let best_cost = best.prepared_route_cost();
3318                eligible.retain(|candidate| {
3319                    compare_optional_route_bound(candidate.prepared_route_cost(), best_cost)
3320                        == Ordering::Equal
3321                });
3322            }
3323        }
3324        let best_adjusted = eligible
3325            .iter()
3326            .map(|candidate| candidate.adjusted_score)
3327            .min_by(f64::total_cmp)
3328            .unwrap_or(best_score);
3329        eligible
3330            .retain(|candidate| candidate.adjusted_score <= best_adjusted + heuristic.best_epsilon);
3331        let selected = eligible
3332            .choose(&mut self.rng)
3333            .map(|candidate| candidate.choice)
3334            .ok_or_else(|| {
3335                CompilerError::InvariantViolation("sabre found no best SWAP".to_string())
3336            })?;
3337        candidates.clear();
3338        scored.clear();
3339        eligible.clear();
3340        self.candidate_scratch = candidates;
3341        self.active_index_scratch = active_indices;
3342        self.scored_scratch = scored;
3343        self.eligible_scratch = eligible;
3344        Ok(selected)
3345    }
3346
3347    fn route_cost_after_swap(
3348        &self,
3349        target: &RoutingTarget,
3350        candidate: SwapChoice,
3351    ) -> Option<RouteLowerBound> {
3352        let mut cost = RouteLowerBound {
3353            remaining_swaps: 1,
3354            native: candidate.cost,
3355        };
3356        for (requirement, placement) in self.front_layer.placements_after_swap(candidate.indices) {
3357            cost = cost.combine(target.route_lower_bound_for_cached(
3358                requirement,
3359                placement,
3360                Some(&self.lower_bound_cache),
3361            )?);
3362        }
3363        Some(cost)
3364    }
3365
3366    fn force_enable_closest_node(
3367        &mut self,
3368        target: &RoutingTarget,
3369        current_swaps: &mut Vec<[PhysicalQubit; 2]>,
3370    ) -> Result<Vec<NodeIndex>, CompilerError> {
3371        // Fallback follows the exact unary/pair state lower bound. Each step is
3372        // a lowerable SWAP and strictly reduces the remaining distance.
3373        let mut closest = None;
3374        for (node, requirement, placement) in self.front_layer.iter() {
3375            let distance = target
3376                .route_lower_bound_for_cached(requirement, placement, Some(&self.lower_bound_cache))
3377                .map_or(u32::MAX, |bound| bound.remaining_swaps);
3378            if closest
3379                .as_ref()
3380                .is_none_or(|(_, _, _, closest_distance)| distance < *closest_distance)
3381            {
3382                closest = Some((node, requirement, placement, distance));
3383            }
3384        }
3385        let (closest_node, requirement, mut placement, _) = closest.ok_or_else(|| {
3386            CompilerError::InvariantViolation(
3387                "sabre fallback called with an empty front layer".to_string(),
3388            )
3389        })?;
3390        while let Some(distance) = target
3391            .route_lower_bound_for_cached(requirement, placement, Some(&self.lower_bound_cache))
3392            .map(|bound| bound.remaining_swaps)
3393        {
3394            if distance == 0 {
3395                break;
3396            }
3397            let mut improving = Vec::new();
3398            let endpoints: SmallVec<[usize; 2]> = match placement {
3399                RequirementPlacement::Unary(physical) => smallvec![physical],
3400                RequirementPlacement::Pair(pair) => SmallVec::from_slice(&pair),
3401            };
3402            for endpoint in endpoints {
3403                for &neighbor in &target.neighbors_by_index[endpoint] {
3404                    let swap = [endpoint, neighbor.index];
3405                    let next = placement.after_swap(swap);
3406                    if target
3407                        .route_lower_bound_for_cached(
3408                            requirement,
3409                            next,
3410                            Some(&self.lower_bound_cache),
3411                        )
3412                        .map(|bound| bound.remaining_swaps)
3413                        == Some(distance - 1)
3414                    {
3415                        improving.push((swap, next, neighbor.swap));
3416                    }
3417                }
3418            }
3419            improving.sort_by(|(left_swap, _, left), (right_swap, _, right)| {
3420                compare_optional_native_cost(Some(left.cost), Some(right.cost)).then_with(|| {
3421                    let left = [
3422                        target.physical_qubits[left_swap[0]],
3423                        target.physical_qubits[left_swap[1]],
3424                    ];
3425                    let right = [
3426                        target.physical_qubits[right_swap[0]],
3427                        target.physical_qubits[right_swap[1]],
3428                    ];
3429                    left.cmp(&right)
3430                })
3431            });
3432            let Some((swap_indices, next, verified)) = improving.into_iter().next() else {
3433                return Err(CompilerError::InvariantViolation(format!(
3434                    "routing-state distance {distance} has no improving lowerable SWAP"
3435                )));
3436            };
3437            let swap = [
3438                target.physical_at(swap_indices[0])?,
3439                target.physical_at(swap_indices[1])?,
3440            ];
3441            self.apply_swap(swap, target)?;
3442            current_swaps.push(
3443                verified
3444                    .emitted_indices
3445                    .map(|index| target.physical_qubits[index]),
3446            );
3447            placement = next;
3448        }
3449
3450        let routed = self
3451            .front_layer
3452            .iter()
3453            .filter_map(|(node, requirement, placement)| {
3454                target
3455                    .terminal_cost_for(requirement, placement)
3456                    .is_some()
3457                    .then_some(node)
3458            })
3459            .collect::<Vec<_>>();
3460        if !routed.contains(&closest_node) {
3461            return Err(CompilerError::InvariantViolation(
3462                "routing-state fallback did not enable its selected node".to_string(),
3463            ));
3464        }
3465        Ok(routed)
3466    }
3467}
3468
3469fn release_shadow_successors(
3470    sabre: &SabreDag,
3471    node: NodeIndex,
3472    required_predecessors: &mut [u32],
3473    decrements: &mut [u32],
3474    touched: &mut Vec<usize>,
3475    to_visit: &mut Vec<NodeIndex>,
3476) {
3477    for edge in sabre.graph.edges_directed(node, Direction::Outgoing) {
3478        let successor = edge.target();
3479        let successor_index = successor.index();
3480        if decrements[successor_index] == 0 {
3481            touched.push(successor_index);
3482        }
3483        decrements[successor_index] += 1;
3484        required_predecessors[successor_index] -= 1;
3485        if required_predecessors[successor_index] == 0 {
3486            to_visit.push(successor);
3487        }
3488    }
3489}
3490
3491/// Detects recent mapping cycles with an incrementally maintained hash.
3492///
3493/// A hit is only a search signal: routing rolls back and takes the guaranteed
3494/// progress fallback. Therefore a theoretical hash collision cannot invalidate
3495/// the routed circuit.
3496struct MappingCycleDetector {
3497    current_hash: u64,
3498    recent: VecDeque<u64>,
3499    seen: HashSet<u64>,
3500}
3501
3502impl MappingCycleDetector {
3503    const WINDOW: usize = 32;
3504
3505    fn new(current_hash: u64) -> Self {
3506        Self {
3507            current_hash,
3508            recent: VecDeque::from([current_hash]),
3509            seen: HashSet::from([current_hash]),
3510        }
3511    }
3512
3513    fn reset(&mut self, current_hash: u64) {
3514        self.current_hash = current_hash;
3515        self.recent.clear();
3516        self.recent.push_back(current_hash);
3517        self.seen.clear();
3518        self.seen.insert(current_hash);
3519    }
3520
3521    fn record_hash(&mut self, current_hash: u64) -> bool {
3522        self.current_hash = current_hash;
3523        if self.seen.contains(&self.current_hash) {
3524            return true;
3525        }
3526        self.recent.push_back(self.current_hash);
3527        self.seen.insert(self.current_hash);
3528        if self.recent.len() > Self::WINDOW
3529            && let Some(expired) = self.recent.pop_front()
3530        {
3531            self.seen.remove(&expired);
3532        }
3533        false
3534    }
3535
3536    fn hash_after_swap(
3537        current_hash: u64,
3538        [left, right]: [usize; 2],
3539        before_left: Option<usize>,
3540        before_right: Option<usize>,
3541    ) -> u64 {
3542        current_hash
3543            ^ Self::entry_hash(left, before_left)
3544            ^ Self::entry_hash(right, before_right)
3545            ^ Self::entry_hash(left, before_right)
3546            ^ Self::entry_hash(right, before_left)
3547    }
3548
3549    fn mapping_hash(mapping: &[Option<usize>]) -> u64 {
3550        mapping
3551            .iter()
3552            .copied()
3553            .enumerate()
3554            .fold(0, |hash, (physical, logical)| {
3555                hash ^ Self::entry_hash(physical, logical)
3556            })
3557    }
3558
3559    fn entry_hash(physical: usize, logical: Option<usize>) -> u64 {
3560        let logical = logical.map_or(0, |logical| logical as u64 + 1);
3561        let mut value = (physical as u64)
3562            .wrapping_mul(0x9e37_79b9_7f4a_7c15)
3563            .wrapping_add(logical);
3564        value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
3565        value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
3566        value ^ (value >> 31)
3567    }
3568}
3569
3570fn compare_optional_native_cost(
3571    left: Option<NativePlanCost>,
3572    right: Option<NativePlanCost>,
3573) -> Ordering {
3574    compare_some_first_by(left, right, |left, right| {
3575        left.native_two_qubit_ops.cmp(&right.native_two_qubit_ops)
3576    })
3577}
3578
3579fn compare_optional_route_bound(
3580    left: Option<RouteLowerBound>,
3581    right: Option<RouteLowerBound>,
3582) -> Ordering {
3583    compare_some_first_by(left, right, |left, right| left.compare(right))
3584}
3585
3586fn heuristic_score_after_swap(
3587    front_layer: &Layer,
3588    lookahead_layers: &[Layer],
3589    heuristic: &SabreHeuristicConfig,
3590    swap: [usize; 2],
3591    distances: &impl Fn(usize, RequirementPlacement) -> Result<f64, CompilerError>,
3592) -> Result<f64, CompilerError> {
3593    let mut score = heuristic.basic_weight * front_layer.total_score_after_swap(swap, distances)?;
3594    for (layer, weight) in lookahead_layers
3595        .iter()
3596        .zip(heuristic.lookahead_weights.iter().copied())
3597    {
3598        score +=
3599            weight * layer.total_score_after_swap(swap, distances)? / layer.len().max(1) as f64;
3600    }
3601    Ok(score)
3602}
3603
3604fn heuristic_topology_score_after_swap(
3605    front_layer: &Layer,
3606    lookahead_layers: &[Layer],
3607    heuristic: &SabreHeuristicConfig,
3608    swap: [usize; 2],
3609    distances: &[u32],
3610    width: usize,
3611) -> f64 {
3612    let mut score = heuristic.basic_weight
3613        * front_layer.total_topology_score_after_swap(swap, distances, width);
3614    for (layer, weight) in lookahead_layers
3615        .iter()
3616        .zip(heuristic.lookahead_weights.iter().copied())
3617    {
3618        score += weight * layer.total_topology_score_after_swap(swap, distances, width)
3619            / layer.len().max(1) as f64;
3620    }
3621    score
3622}
3623
3624#[derive(Debug, Default)]
3625struct TrialOutput {
3626    plan: CompactRoutePlan,
3627    emit_operations: bool,
3628    swap_count: usize,
3629    fallback_count: usize,
3630    control_flow_blocks_routed: usize,
3631    lazy_pair_l1_lookup_count: usize,
3632    lazy_pair_l1_hit_count: usize,
3633    lazy_pair_l1_cached_count: usize,
3634    nested_seed_counter: u64,
3635    native_cost: NativeCircuitCost,
3636    two_qubit_operation_count: usize,
3637    operation_count: usize,
3638}
3639
3640impl TrialOutput {
3641    fn new(seed: u64, emit_operations: bool) -> Self {
3642        Self {
3643            nested_seed_counter: seed,
3644            emit_operations,
3645            ..Self::default()
3646        }
3647    }
3648
3649    fn push_operation(
3650        &mut self,
3651        operation: Operation,
3652        source_order: Option<usize>,
3653        target: &RoutingTarget,
3654    ) -> Result<(), CompilerError> {
3655        debug_assert!(self.emit_operations);
3656        self.two_qubit_operation_count = self
3657            .two_qubit_operation_count
3658            .saturating_add(two_qubit_operation_count(std::slice::from_ref(&operation)));
3659        self.operation_count = self
3660            .operation_count
3661            .saturating_add(operation_count(std::slice::from_ref(&operation)));
3662        if target.native_cost_enabled {
3663            self.native_cost
3664                .append_sequence(native_plan_cost_for_operations(
3665                    std::slice::from_ref(&operation),
3666                    target,
3667                )?);
3668        }
3669        self.plan.push_operation(operation, source_order);
3670        Ok(())
3671    }
3672
3673    fn push_mapped_operation(
3674        &mut self,
3675        operation: &Arc<Operation>,
3676        source_order: Option<usize>,
3677        layout: &DenseRoutingLayout,
3678        target: &RoutingTarget,
3679    ) -> Result<(), CompilerError> {
3680        debug_assert!(self.emit_operations);
3681        if target.native_cost_enabled {
3682            let mapped = map_operation_dense(operation, layout, target)?;
3683            return self.push_operation(mapped, source_order, target);
3684        }
3685
3686        let qubits = map_operation_qubits_dense(operation, layout, target)?;
3687        self.operation_count = self.operation_count.saturating_add(1);
3688        if qubits.len() == 2 {
3689            self.two_qubit_operation_count = self.two_qubit_operation_count.saturating_add(1);
3690        }
3691        self.plan
3692            .push_mapped_operation(Arc::clone(operation), qubits, source_order);
3693        Ok(())
3694    }
3695
3696    fn apply_pending_swaps(
3697        &mut self,
3698        target: &RoutingTarget,
3699        swaps: Option<&mut Vec<[PhysicalQubit; 2]>>,
3700    ) -> Result<(), CompilerError> {
3701        if let Some(swaps) = swaps {
3702            self.swap_count += swaps.len();
3703            if self.emit_operations {
3704                for swap in swaps.drain(..) {
3705                    self.plan.push_swap(swap);
3706                    self.operation_count = self.operation_count.saturating_add(1);
3707                    self.two_qubit_operation_count =
3708                        self.two_qubit_operation_count.saturating_add(1);
3709                    if target.native_cost_enabled {
3710                        self.native_cost.append_gate(target.swap_cost(swap)?);
3711                    }
3712                }
3713            } else {
3714                swaps.clear();
3715            }
3716        }
3717        Ok(())
3718    }
3719
3720    fn next_nested_seed(&mut self) -> u64 {
3721        let seed = self.nested_seed_counter;
3722        self.nested_seed_counter = self.nested_seed_counter.wrapping_add(1);
3723        seed
3724    }
3725
3726    fn merge_nested(&mut self, nested: &UnscoredTrial) {
3727        self.swap_count += nested.swap_count;
3728        self.fallback_count += nested.fallback_count;
3729        self.control_flow_blocks_routed += nested.control_flow_blocks_routed + 1;
3730        self.lazy_pair_l1_lookup_count = self
3731            .lazy_pair_l1_lookup_count
3732            .saturating_add(nested.lazy_pair_l1_lookup_count);
3733        self.lazy_pair_l1_hit_count = self
3734            .lazy_pair_l1_hit_count
3735            .saturating_add(nested.lazy_pair_l1_hit_count);
3736        self.lazy_pair_l1_cached_count = self
3737            .lazy_pair_l1_cached_count
3738            .saturating_add(nested.lazy_pair_l1_cached_count);
3739    }
3740}
3741
3742/// Routes a control-flow body and restores its entry layout before exit.
3743///
3744/// A control-flow body must be layout-neutral from the parent router's point of
3745/// view: whichever branch or iteration executes, the body exits with the same
3746/// logical-to-physical mapping it entered with.
3747fn route_control_flow_body(
3748    sabre: &SabreDag,
3749    target: &RoutingTarget,
3750    metadata: &PreparedRouteMetadata,
3751    entry_layout: &Layout,
3752    heuristic: &SabreHeuristicConfig,
3753    seed: u64,
3754    emit_operations: bool,
3755) -> Result<UnscoredTrial, CompilerError> {
3756    let mut result = route_trial_with_metadata(
3757        sabre,
3758        target,
3759        metadata,
3760        entry_layout,
3761        heuristic,
3762        seed,
3763        emit_operations,
3764    )?;
3765    let epilogue_swaps = restore_layout_swaps(target, &result.final_layout, entry_layout, seed)?;
3766    let mut layout = result.final_layout.clone();
3767    for swap in &epilogue_swaps {
3768        layout.swap_physical(swap[0], swap[1]).map_err(|error| {
3769            CompilerError::InvariantViolation(format!(
3770                "sabre control-flow epilogue generated an invalid SWAP: {error}"
3771            ))
3772        })?;
3773    }
3774    let ends_with_control_transfer = emit_operations
3775        && matches!(
3776            result
3777                .plan
3778                .last_operation()
3779                .map(|operation| &operation.instruction),
3780            Some(Instruction::ClassicalControl(
3781                ClassicalControlOp::Break | ClassicalControlOp::Continue
3782            ))
3783        );
3784    let control_transfer = if ends_with_control_transfer {
3785        result.plan.pop_last_operation()
3786    } else {
3787        None
3788    };
3789    if emit_operations {
3790        result.append_swaps(target, epilogue_swaps.iter().copied())?;
3791        if let Some(control_transfer) = control_transfer {
3792            result.plan.push_operation(control_transfer, None);
3793        }
3794    } else {
3795        result.swap_count = result.swap_count.saturating_add(epilogue_swaps.len());
3796    }
3797    result.final_layout = layout;
3798    if result.final_layout.l2p_map() != entry_layout.l2p_map() {
3799        return Err(CompilerError::InvariantViolation(
3800            "sabre control-flow epilogue did not restore the entry layout".to_string(),
3801        ));
3802    }
3803    Ok(result)
3804}
3805
3806fn missing_nested_metadata(body: &str) -> CompilerError {
3807    CompilerError::InvariantViolation(format!(
3808        "control-flow {body} body has no prepared routing metadata"
3809    ))
3810}
3811
3812/// Computes SWAPs that restore one layout to another on the target graph.
3813///
3814/// Token swapping computes an epilogue that returns every live logical qubit to
3815/// its entry physical location. Vacant physical qubits are irrelevant and are
3816/// omitted from the token mapping.
3817fn restore_layout_swaps(
3818    target: &RoutingTarget,
3819    current: &Layout,
3820    desired: &Layout,
3821    seed: u64,
3822) -> Result<Vec<[PhysicalQubit; 2]>, CompilerError> {
3823    let mut mapping_entries = Vec::new();
3824    for physical in desired.physical_qubits() {
3825        let Some(logical) = current.get_logical(physical) else {
3826            continue;
3827        };
3828        let desired_physical = desired.get_physical(logical).ok_or_else(|| {
3829            CompilerError::InvariantViolation(format!(
3830                "desired control-flow layout does not map logical qubit {logical}"
3831            ))
3832        })?;
3833        let current_node = target.graph_index.get(&physical).copied().ok_or_else(|| {
3834            CompilerError::InvariantViolation(format!(
3835                "current control-flow layout contains physical qubit {physical} outside the routing graph"
3836            ))
3837        })?;
3838        let desired_node = target
3839            .graph_index
3840            .get(&desired_physical)
3841            .copied()
3842            .ok_or_else(|| {
3843                CompilerError::InvariantViolation(format!(
3844                    "desired control-flow layout contains physical qubit {desired_physical} outside the routing graph"
3845                ))
3846            })?;
3847        mapping_entries.push((current_node, desired_node));
3848    }
3849    let mapping = mapping_entries.into_iter().collect();
3850
3851    let swaps = token_swapper(
3852        &target.graph,
3853        mapping,
3854        Some(CONTROL_FLOW_EPILOGUE_TRIALS),
3855        Some(seed),
3856        None,
3857    )
3858    .map_err(|error| CompilerError::TransformFailed {
3859        name: "sabre_route",
3860        reason: format!("failed to restore control-flow layout: {error}"),
3861    })?;
3862    swaps
3863        .into_iter()
3864        .map(|(left, right)| {
3865            Ok([
3866                target.physical_by_index[left.index()],
3867                target.physical_by_index[right.index()],
3868            ])
3869        })
3870        .collect()
3871}
3872
3873fn map_operation_dense(
3874    operation: &Operation,
3875    layout: &DenseRoutingLayout,
3876    target: &RoutingTarget,
3877) -> Result<Operation, CompilerError> {
3878    Ok(Operation {
3879        instruction: operation.instruction.clone(),
3880        qubits: map_operation_qubits_dense(operation, layout, target)?,
3881        params: operation.params.clone(),
3882        label: operation.label.clone(),
3883    })
3884}
3885
3886#[inline]
3887fn map_operation_qubits_dense(
3888    operation: &Operation,
3889    layout: &DenseRoutingLayout,
3890    target: &RoutingTarget,
3891) -> Result<SmallVec<[Qubit; 3]>, CompilerError> {
3892    operation
3893        .qubits
3894        .iter()
3895        .copied()
3896        .map(|qubit| {
3897            let physical_index = layout.physical_index(LogicalQubit::from_qubit(qubit))?;
3898            target.physical_at(physical_index).map(PhysicalQubit::qubit)
3899        })
3900        .collect()
3901}
3902
3903/// Remaps operation parameter indices into the routed circuit's parameter table.
3904///
3905/// Parameter indices are scoped to a circuit table, while routed nested
3906/// operations are rebuilt before the final table exists. This function walks
3907/// recursively so every body points at the reordered routed table.
3908fn remap_parameter_indices(
3909    mut operation: Operation,
3910    parameter_indices: &[u32],
3911) -> Result<Operation, CompilerError> {
3912    for param in &mut operation.params {
3913        if let CircuitParam::Index(index) = param {
3914            *index = *parameter_indices
3915                .get(*index as usize)
3916                .ok_or(crate::circuit::CircuitError::InvalidParameterIndex(*index))?;
3917        }
3918    }
3919    operation.instruction = match &operation.instruction {
3920        Instruction::ClassicalControl(ClassicalControlOp::If(op)) => {
3921            let then_body = op
3922                .then_body()
3923                .operations()
3924                .iter()
3925                .cloned()
3926                .map(|operation| remap_parameter_indices(operation, parameter_indices))
3927                .collect::<Result<Vec<_>, _>>()?;
3928            let else_body = op
3929                .else_body()
3930                .map(|body| {
3931                    body.operations()
3932                        .iter()
3933                        .cloned()
3934                        .map(|operation| remap_parameter_indices(operation, parameter_indices))
3935                        .collect::<Result<Vec<_>, _>>()
3936                        .map(ControlBody::new)
3937                })
3938                .transpose()?;
3939            Instruction::ClassicalControl(ClassicalControlOp::If(IfOp::new(
3940                op.condition().clone(),
3941                ControlBody::new(then_body),
3942                else_body,
3943            )?))
3944        }
3945        Instruction::ClassicalControl(ClassicalControlOp::While(op)) => {
3946            let body = op
3947                .body()
3948                .operations()
3949                .iter()
3950                .cloned()
3951                .map(|operation| remap_parameter_indices(operation, parameter_indices))
3952                .collect::<Result<Vec<_>, _>>()?;
3953            Instruction::ClassicalControl(ClassicalControlOp::While(WhileOp::new(
3954                op.condition().clone(),
3955                ControlBody::new(body),
3956            )?))
3957        }
3958        Instruction::ClassicalControl(ClassicalControlOp::For(op)) => {
3959            let body = op
3960                .body()
3961                .operations()
3962                .iter()
3963                .cloned()
3964                .map(|operation| remap_parameter_indices(operation, parameter_indices))
3965                .collect::<Result<Vec<_>, _>>()?;
3966            Instruction::ClassicalControl(ClassicalControlOp::For(ForOp::new(
3967                op.var(),
3968                op.start().clone(),
3969                op.stop().clone(),
3970                op.step().clone(),
3971                ControlBody::new(body),
3972            )?))
3973        }
3974        Instruction::ClassicalControl(ClassicalControlOp::Switch(op)) => {
3975            let cases = op
3976                .cases()
3977                .iter()
3978                .map(|case| {
3979                    let body = case
3980                        .body()
3981                        .operations()
3982                        .iter()
3983                        .cloned()
3984                        .map(|operation| remap_parameter_indices(operation, parameter_indices))
3985                        .collect::<Result<Vec<_>, _>>()?;
3986                    Ok(SwitchCase::new(case.value(), ControlBody::new(body)))
3987                })
3988                .collect::<Result<Vec<_>, CompilerError>>()?;
3989            let default = op
3990                .default()
3991                .map(|body| {
3992                    body.operations()
3993                        .iter()
3994                        .cloned()
3995                        .map(|operation| remap_parameter_indices(operation, parameter_indices))
3996                        .collect::<Result<Vec<_>, _>>()
3997                        .map(ControlBody::new)
3998                })
3999                .transpose()?;
4000            Instruction::ClassicalControl(ClassicalControlOp::Switch(SwitchOp::new(
4001                op.target().clone(),
4002                cases,
4003                default,
4004            )?))
4005        }
4006        _ => operation.instruction.clone(),
4007    };
4008    Ok(operation)
4009}
4010
4011fn physical_for(layout: &Layout, logical: LogicalQubit) -> Result<PhysicalQubit, CompilerError> {
4012    layout.get_physical(logical).ok_or_else(|| {
4013        CompilerError::InvariantViolation(format!(
4014            "sabre layout does not map logical qubit {logical}"
4015        ))
4016    })
4017}
4018
4019/// One exact movement-component assignment for all logical qubits.
4020#[derive(Debug, Clone)]
4021pub(crate) struct MovementComponentAssignment {
4022    pub(crate) components: Vec<Vec<PhysicalQubit>>,
4023    /// Component index in the same order as the requested logical-qubit list.
4024    pub(crate) logical_components: Vec<usize>,
4025}
4026
4027#[derive(Debug, Clone)]
4028pub(crate) enum ComponentAssignmentSearch {
4029    Found(MovementComponentAssignment),
4030    ProvenInfeasible,
4031    BudgetExhausted { expansions: usize },
4032}
4033
4034#[derive(Debug)]
4035struct PairComponentConstraint {
4036    left: usize,
4037    right: usize,
4038    allowed: BTreeSet<(usize, usize)>,
4039}
4040
4041#[derive(Debug, Clone)]
4042enum RequirementComponentTerminals {
4043    Unary(BTreeSet<usize>),
4044    Pair(BTreeSet<(usize, usize)>),
4045}
4046
4047/// Solves component-level placement constraints induced by unary and ordered
4048/// pair requirements. The result distinguishes a proof of infeasibility from
4049/// exhaustion of the caller-provided expansion budget.
4050pub(crate) fn movement_component_assignment(
4051    sabre: &SabreDag,
4052    target: &RoutingTarget,
4053    logical_qubits: &[LogicalQubit],
4054    expansion_budget: usize,
4055) -> Result<ComponentAssignmentSearch, CompilerError> {
4056    let component_indices = target.movement_component_indices();
4057    let components = component_indices
4058        .iter()
4059        .map(|component| {
4060            component
4061                .iter()
4062                .map(|index| target.physical_qubits[*index])
4063                .collect::<Vec<_>>()
4064        })
4065        .collect::<Vec<_>>();
4066    let mut component_by_physical = vec![0usize; target.physical_qubits.len()];
4067    for (component, physicals) in component_indices.iter().enumerate() {
4068        for &physical in physicals {
4069            component_by_physical[physical] = component;
4070        }
4071    }
4072    let logical_index = logical_qubits
4073        .iter()
4074        .copied()
4075        .enumerate()
4076        .map(|(index, logical)| (logical, index))
4077        .collect::<BTreeMap<_, _>>();
4078    let all_components = (0..components.len()).collect::<BTreeSet<_>>();
4079    let mut domains = vec![all_components; logical_qubits.len()];
4080    let mut pair_constraints = Vec::new();
4081    let mut terminal_components = vec![None; target.requirements.len()];
4082    collect_component_constraints(
4083        sabre,
4084        target,
4085        &component_by_physical,
4086        &mut terminal_components,
4087        &logical_index,
4088        &mut domains,
4089        &mut pair_constraints,
4090    )?;
4091    if domains.iter().any(BTreeSet::is_empty) {
4092        return Ok(ComponentAssignmentSearch::ProvenInfeasible);
4093    }
4094
4095    let capacities = components.iter().map(Vec::len).collect::<Vec<_>>();
4096    let mut remaining = capacities;
4097    let mut assignment = vec![None; logical_qubits.len()];
4098    let mut budget = expansion_budget;
4099    let result = assign_logical_components(
4100        &domains,
4101        &pair_constraints,
4102        &mut remaining,
4103        &mut assignment,
4104        &mut budget,
4105    );
4106    match result {
4107        ComponentSearchProgress::Found => {
4108            let logical_components = assignment
4109                .into_iter()
4110                .collect::<Option<Vec<_>>>()
4111                .ok_or_else(|| {
4112                    CompilerError::InvariantViolation(
4113                        "component search reported success with an incomplete assignment"
4114                            .to_string(),
4115                    )
4116                })?;
4117            Ok(ComponentAssignmentSearch::Found(
4118                MovementComponentAssignment {
4119                    components,
4120                    logical_components,
4121                },
4122            ))
4123        }
4124        ComponentSearchProgress::ProvenInfeasible => {
4125            Ok(ComponentAssignmentSearch::ProvenInfeasible)
4126        }
4127        ComponentSearchProgress::BudgetExhausted => {
4128            Ok(ComponentAssignmentSearch::BudgetExhausted {
4129                expansions: expansion_budget - budget,
4130            })
4131        }
4132    }
4133}
4134
4135impl RoutingTarget {
4136    fn movement_component_indices(&self) -> Vec<Vec<usize>> {
4137        let mut unseen = (0..self.physical_qubits.len()).collect::<BTreeSet<_>>();
4138        let mut components = Vec::new();
4139        while let Some(start) = unseen.pop_first() {
4140            let mut queue = VecDeque::from([start]);
4141            let mut component = Vec::new();
4142            while let Some(physical) = queue.pop_front() {
4143                component.push(physical);
4144                for neighbor in &self.neighbors_by_index[physical] {
4145                    if unseen.remove(&neighbor.index) {
4146                        queue.push_back(neighbor.index);
4147                    }
4148                }
4149            }
4150            component.sort_unstable_by_key(|index| self.physical_qubits[*index]);
4151            components.push(component);
4152        }
4153        components.sort_unstable_by_key(|component| self.physical_qubits[component[0]]);
4154        components
4155    }
4156}
4157
4158fn collect_component_constraints(
4159    sabre: &SabreDag,
4160    target: &RoutingTarget,
4161    component_by_physical: &[usize],
4162    terminal_components: &mut [Option<RequirementComponentTerminals>],
4163    logical_index: &BTreeMap<LogicalQubit, usize>,
4164    domains: &mut [BTreeSet<usize>],
4165    pairs: &mut Vec<PairComponentConstraint>,
4166) -> Result<(), CompilerError> {
4167    for node_index in sabre.graph.node_indices() {
4168        let node = &sabre.graph[node_index];
4169        match &node.kind {
4170            SabreNodeKind::Unary(logical) => {
4171                let index = *logical_index.get(logical).ok_or_else(|| {
4172                    CompilerError::InvariantViolation(format!(
4173                        "unary component constraint references unknown logical qubit {logical}"
4174                    ))
4175                })?;
4176                let requirement = target.interaction_id_for_node(sabre, node_index)?;
4177                let RequirementComponentTerminals::Unary(allowed) =
4178                    requirement_component_terminals(
4179                        target,
4180                        requirement,
4181                        component_by_physical,
4182                        terminal_components,
4183                    )?
4184                else {
4185                    return Err(CompilerError::InvariantViolation(format!(
4186                        "unary node uses pair routing requirement {requirement}"
4187                    )));
4188                };
4189                domains[index].retain(|component| allowed.contains(component));
4190            }
4191            SabreNodeKind::TwoQ(logicals) => {
4192                let left = *logical_index.get(&logicals[0]).ok_or_else(|| {
4193                    CompilerError::InvariantViolation(format!(
4194                        "pair component constraint references unknown logical qubit {}",
4195                        logicals[0]
4196                    ))
4197                })?;
4198                let right = *logical_index.get(&logicals[1]).ok_or_else(|| {
4199                    CompilerError::InvariantViolation(format!(
4200                        "pair component constraint references unknown logical qubit {}",
4201                        logicals[1]
4202                    ))
4203                })?;
4204                let requirement = target.interaction_id_for_node(sabre, node_index)?;
4205                let RequirementComponentTerminals::Pair(allowed) = requirement_component_terminals(
4206                    target,
4207                    requirement,
4208                    component_by_physical,
4209                    terminal_components,
4210                )?
4211                else {
4212                    return Err(CompilerError::InvariantViolation(format!(
4213                        "pair node uses unary routing requirement {requirement}"
4214                    )));
4215                };
4216                if allowed.is_empty() {
4217                    domains[left].clear();
4218                    domains[right].clear();
4219                } else {
4220                    domains[left].retain(|component| {
4221                        allowed.iter().any(|(allowed, _)| allowed == component)
4222                    });
4223                    domains[right].retain(|component| {
4224                        allowed.iter().any(|(_, allowed)| allowed == component)
4225                    });
4226                    pairs.push(PairComponentConstraint {
4227                        left,
4228                        right,
4229                        allowed: allowed.clone(),
4230                    });
4231                }
4232            }
4233            SabreNodeKind::ControlFlow(SabreControlFlow::If {
4234                then_body,
4235                else_body,
4236                ..
4237            }) => {
4238                collect_component_constraints(
4239                    then_body,
4240                    target,
4241                    component_by_physical,
4242                    terminal_components,
4243                    logical_index,
4244                    domains,
4245                    pairs,
4246                )?;
4247                if let Some(else_body) = else_body {
4248                    collect_component_constraints(
4249                        else_body,
4250                        target,
4251                        component_by_physical,
4252                        terminal_components,
4253                        logical_index,
4254                        domains,
4255                        pairs,
4256                    )?;
4257                }
4258            }
4259            SabreNodeKind::ControlFlow(
4260                SabreControlFlow::While { body, .. } | SabreControlFlow::For { body, .. },
4261            ) => collect_component_constraints(
4262                body,
4263                target,
4264                component_by_physical,
4265                terminal_components,
4266                logical_index,
4267                domains,
4268                pairs,
4269            )?,
4270            SabreNodeKind::ControlFlow(SabreControlFlow::Switch { cases, default, .. }) => {
4271                for case in cases {
4272                    collect_component_constraints(
4273                        &case.body,
4274                        target,
4275                        component_by_physical,
4276                        terminal_components,
4277                        logical_index,
4278                        domains,
4279                        pairs,
4280                    )?;
4281                }
4282                if let Some(default) = default {
4283                    collect_component_constraints(
4284                        default,
4285                        target,
4286                        component_by_physical,
4287                        terminal_components,
4288                        logical_index,
4289                        domains,
4290                        pairs,
4291                    )?;
4292                }
4293            }
4294            SabreNodeKind::Synchronize => {}
4295        }
4296    }
4297    Ok(())
4298}
4299
4300fn requirement_component_terminals<'a>(
4301    target: &RoutingTarget,
4302    requirement: usize,
4303    component_by_physical: &[usize],
4304    cache: &'a mut [Option<RequirementComponentTerminals>],
4305) -> Result<&'a RequirementComponentTerminals, CompilerError> {
4306    let entry = cache.get_mut(requirement).ok_or_else(|| {
4307        CompilerError::InvariantViolation(format!(
4308            "routing requirement {requirement} has no component cache entry"
4309        ))
4310    })?;
4311    if entry.is_none() {
4312        *entry = Some(match target.requirements.get(requirement) {
4313            Some(RequirementTable::Unary { terminals, .. }) => {
4314                RequirementComponentTerminals::Unary(
4315                    terminals
4316                        .iter()
4317                        .enumerate()
4318                        .filter_map(|(physical, terminal)| {
4319                            terminal.map(|_| component_by_physical[physical])
4320                        })
4321                        .collect(),
4322                )
4323            }
4324            Some(RequirementTable::Pair { terminals, .. }) => RequirementComponentTerminals::Pair(
4325                terminals
4326                    .keys()
4327                    .map(|[left, right]| {
4328                        (component_by_physical[*left], component_by_physical[*right])
4329                    })
4330                    .collect(),
4331            ),
4332            None => {
4333                return Err(CompilerError::InvariantViolation(format!(
4334                    "routing requirement {requirement} is missing"
4335                )));
4336            }
4337        });
4338    }
4339    entry.as_ref().ok_or_else(|| {
4340        CompilerError::InvariantViolation(format!(
4341            "routing requirement {requirement} component terminals were not initialized"
4342        ))
4343    })
4344}
4345
4346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4347enum ComponentSearchProgress {
4348    Found,
4349    ProvenInfeasible,
4350    BudgetExhausted,
4351}
4352
4353fn assign_logical_components(
4354    domains: &[BTreeSet<usize>],
4355    constraints: &[PairComponentConstraint],
4356    remaining: &mut [usize],
4357    assignment: &mut [Option<usize>],
4358    budget: &mut usize,
4359) -> ComponentSearchProgress {
4360    if assignment.iter().all(Option::is_some) {
4361        return ComponentSearchProgress::Found;
4362    }
4363    let Some((logical, candidates)) = assignment
4364        .iter()
4365        .enumerate()
4366        .filter(|(_, assigned)| assigned.is_none())
4367        .map(|(logical, _)| {
4368            let candidates = domains[logical]
4369                .iter()
4370                .copied()
4371                .filter(|component| remaining[*component] > 0)
4372                .filter(|component| {
4373                    component_assignment_is_consistent(logical, *component, constraints, assignment)
4374                })
4375                .collect::<Vec<_>>();
4376            (logical, candidates)
4377        })
4378        .min_by_key(|(logical, candidates)| (candidates.len(), *logical))
4379    else {
4380        return ComponentSearchProgress::ProvenInfeasible;
4381    };
4382    if candidates.is_empty() {
4383        return ComponentSearchProgress::ProvenInfeasible;
4384    }
4385    for component in candidates {
4386        if *budget == 0 {
4387            return ComponentSearchProgress::BudgetExhausted;
4388        }
4389        *budget -= 1;
4390        assignment[logical] = Some(component);
4391        remaining[component] -= 1;
4392        let forward_consistent = assignment
4393            .iter()
4394            .enumerate()
4395            .filter(|(_, assigned)| assigned.is_none())
4396            .all(|(unassigned, _)| {
4397                domains[unassigned].iter().copied().any(|candidate| {
4398                    remaining[candidate] > 0
4399                        && component_assignment_is_consistent(
4400                            unassigned,
4401                            candidate,
4402                            constraints,
4403                            assignment,
4404                        )
4405                })
4406            });
4407        if forward_consistent {
4408            match assign_logical_components(domains, constraints, remaining, assignment, budget) {
4409                ComponentSearchProgress::Found => return ComponentSearchProgress::Found,
4410                ComponentSearchProgress::BudgetExhausted => {
4411                    remaining[component] += 1;
4412                    assignment[logical] = None;
4413                    return ComponentSearchProgress::BudgetExhausted;
4414                }
4415                ComponentSearchProgress::ProvenInfeasible => {}
4416            }
4417        }
4418        remaining[component] += 1;
4419        assignment[logical] = None;
4420    }
4421    ComponentSearchProgress::ProvenInfeasible
4422}
4423
4424fn component_assignment_is_consistent(
4425    logical: usize,
4426    component: usize,
4427    constraints: &[PairComponentConstraint],
4428    assignment: &[Option<usize>],
4429) -> bool {
4430    constraints.iter().all(|constraint| {
4431        if constraint.left == logical {
4432            assignment[constraint.right]
4433                .is_none_or(|right| constraint.allowed.contains(&(component, right)))
4434        } else if constraint.right == logical {
4435            assignment[constraint.left]
4436                .is_none_or(|left| constraint.allowed.contains(&(left, component)))
4437        } else {
4438            true
4439        }
4440    })
4441}
4442
4443/// Validates that every interaction in a SABRE DAG is physically reachable.
4444///
4445/// Reachability is recursive because control-flow bodies are routed with the
4446/// same physical topology and entry-layout contract as parent operations.
4447pub(crate) fn validate_reachable_interactions_for_target(
4448    sabre: &SabreDag,
4449    target: &RoutingTarget,
4450    layout: &Layout,
4451) -> Result<(), CompilerError> {
4452    match interaction_reachability_for_target(sabre, target, layout)? {
4453        InteractionReachability::Reachable => Ok(()),
4454        InteractionReachability::UnreachableUnary {
4455            logical,
4456            physical: _,
4457            cause: RequirementReachabilityFailure::NoExecutableTerminal,
4458        } => Err(CompilerError::SabreRoutingFailed(
4459            SabreRoutingFailure::NoExecutableUnaryTerminal { logical },
4460        )),
4461        InteractionReachability::UnreachableUnary {
4462            logical,
4463            physical,
4464            cause: RequirementReachabilityFailure::MovementDisconnected,
4465        } => Err(CompilerError::SabreRoutingFailed(
4466            SabreRoutingFailure::UnreachableUnaryPlacement { logical, physical },
4467        )),
4468        InteractionReachability::UnreachablePair {
4469            logical,
4470            physical: _,
4471            cause: RequirementReachabilityFailure::NoExecutableTerminal,
4472        } => Err(CompilerError::SabreRoutingFailed(
4473            SabreRoutingFailure::NoExecutablePairTerminal { logical },
4474        )),
4475        InteractionReachability::UnreachablePair {
4476            logical,
4477            physical,
4478            cause: RequirementReachabilityFailure::MovementDisconnected,
4479        } => Err(CompilerError::SabreRoutingFailed(
4480            SabreRoutingFailure::UnreachablePairPlacement { logical, physical },
4481        )),
4482    }
4483}
4484
4485pub(crate) fn validate_reachable_interactions_for_target_with_metadata(
4486    sabre: &SabreDag,
4487    target: &RoutingTarget,
4488    metadata: &PreparedRouteMetadata,
4489    layout: &Layout,
4490) -> Result<(), CompilerError> {
4491    match interaction_reachability_for_target_with_metadata(sabre, target, metadata, layout)? {
4492        InteractionReachability::Reachable => Ok(()),
4493        InteractionReachability::UnreachableUnary {
4494            logical,
4495            cause: RequirementReachabilityFailure::NoExecutableTerminal,
4496            ..
4497        } => Err(CompilerError::SabreRoutingFailed(
4498            SabreRoutingFailure::NoExecutableUnaryTerminal { logical },
4499        )),
4500        InteractionReachability::UnreachableUnary {
4501            logical,
4502            physical,
4503            cause: RequirementReachabilityFailure::MovementDisconnected,
4504        } => Err(CompilerError::SabreRoutingFailed(
4505            SabreRoutingFailure::UnreachableUnaryPlacement { logical, physical },
4506        )),
4507        InteractionReachability::UnreachablePair {
4508            logical,
4509            cause: RequirementReachabilityFailure::NoExecutableTerminal,
4510            ..
4511        } => Err(CompilerError::SabreRoutingFailed(
4512            SabreRoutingFailure::NoExecutablePairTerminal { logical },
4513        )),
4514        InteractionReachability::UnreachablePair {
4515            logical,
4516            physical,
4517            cause: RequirementReachabilityFailure::MovementDisconnected,
4518        } => Err(CompilerError::SabreRoutingFailed(
4519            SabreRoutingFailure::UnreachablePairPlacement { logical, physical },
4520        )),
4521    }
4522}
4523
4524#[derive(Debug, Clone, PartialEq, Eq)]
4525pub(crate) enum InteractionReachability {
4526    Reachable,
4527    UnreachableUnary {
4528        logical: LogicalQubit,
4529        physical: PhysicalQubit,
4530        cause: RequirementReachabilityFailure,
4531    },
4532    UnreachablePair {
4533        logical: [LogicalQubit; 2],
4534        physical: [PhysicalQubit; 2],
4535        cause: RequirementReachabilityFailure,
4536    },
4537}
4538
4539#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4540pub(crate) enum RequirementReachabilityFailure {
4541    NoExecutableTerminal,
4542    MovementDisconnected,
4543}
4544
4545pub(crate) fn interaction_reachability_for_target(
4546    sabre: &SabreDag,
4547    target: &RoutingTarget,
4548    layout: &Layout,
4549) -> Result<InteractionReachability, CompilerError> {
4550    let metadata = PreparedRouteMetadata::new(sabre, target)?;
4551    interaction_reachability_for_target_with_metadata(sabre, target, &metadata, layout)
4552}
4553
4554pub(crate) fn interaction_reachability_for_target_with_metadata(
4555    sabre: &SabreDag,
4556    target: &RoutingTarget,
4557    metadata: &PreparedRouteMetadata,
4558    layout: &Layout,
4559) -> Result<InteractionReachability, CompilerError> {
4560    for node_index in sabre.graph.node_indices() {
4561        let node = &sabre.graph[node_index];
4562        let requirement = || {
4563            metadata
4564                .requirement_ids
4565                .get(node_index.index())
4566                .copied()
4567                .flatten()
4568                .ok_or_else(|| {
4569                    CompilerError::InvariantViolation(format!(
4570                        "reachability node {} has no prepared requirement id",
4571                        node_index.index()
4572                    ))
4573                })
4574        };
4575        let mut nested = metadata
4576            .nested
4577            .get(node_index.index())
4578            .ok_or_else(|| {
4579                CompilerError::InvariantViolation(format!(
4580                    "reachability node {} has no prepared nested metadata slot",
4581                    node_index.index()
4582                ))
4583            })?
4584            .iter();
4585        match &node.kind {
4586            SabreNodeKind::Unary(logical) => {
4587                let physical = physical_for(layout, *logical)?;
4588                let physical_index = target.physical_index(physical)?;
4589                let requirement = requirement()?;
4590                if target
4591                    .distance_steps_for(requirement, RequirementPlacement::Unary(physical_index))
4592                    .is_none()
4593                {
4594                    return Ok(InteractionReachability::UnreachableUnary {
4595                        logical: *logical,
4596                        physical,
4597                        cause: if target.has_terminal(requirement) {
4598                            RequirementReachabilityFailure::MovementDisconnected
4599                        } else {
4600                            RequirementReachabilityFailure::NoExecutableTerminal
4601                        },
4602                    });
4603                }
4604            }
4605            SabreNodeKind::TwoQ(pair) => {
4606                let left = physical_for(layout, pair[0])?;
4607                let right = physical_for(layout, pair[1])?;
4608                let left_index = target.physical_index(left)?;
4609                let right_index = target.physical_index(right)?;
4610                let interaction = requirement()?;
4611                if target
4612                    .distance_steps_for(
4613                        interaction,
4614                        RequirementPlacement::Pair([left_index, right_index]),
4615                    )
4616                    .is_none()
4617                {
4618                    return Ok(InteractionReachability::UnreachablePair {
4619                        logical: *pair,
4620                        physical: [left, right],
4621                        cause: if target.has_terminal(interaction) {
4622                            RequirementReachabilityFailure::MovementDisconnected
4623                        } else {
4624                            RequirementReachabilityFailure::NoExecutableTerminal
4625                        },
4626                    });
4627                }
4628            }
4629            SabreNodeKind::ControlFlow(SabreControlFlow::If {
4630                then_body,
4631                else_body,
4632                ..
4633            }) => {
4634                let reachable = interaction_reachability_for_target_with_metadata(
4635                    then_body,
4636                    target,
4637                    nested
4638                        .next()
4639                        .ok_or_else(|| missing_nested_metadata("if then"))?,
4640                    layout,
4641                )?;
4642                if reachable != InteractionReachability::Reachable {
4643                    return Ok(reachable);
4644                }
4645                if let Some(else_body) = else_body {
4646                    let reachable = interaction_reachability_for_target_with_metadata(
4647                        else_body,
4648                        target,
4649                        nested
4650                            .next()
4651                            .ok_or_else(|| missing_nested_metadata("if else"))?,
4652                        layout,
4653                    )?;
4654                    if reachable != InteractionReachability::Reachable {
4655                        return Ok(reachable);
4656                    }
4657                }
4658            }
4659            SabreNodeKind::ControlFlow(
4660                SabreControlFlow::While { body, .. } | SabreControlFlow::For { body, .. },
4661            ) => {
4662                let reachable = interaction_reachability_for_target_with_metadata(
4663                    body,
4664                    target,
4665                    nested
4666                        .next()
4667                        .ok_or_else(|| missing_nested_metadata("loop"))?,
4668                    layout,
4669                )?;
4670                if reachable != InteractionReachability::Reachable {
4671                    return Ok(reachable);
4672                }
4673            }
4674            SabreNodeKind::ControlFlow(SabreControlFlow::Switch { cases, default, .. }) => {
4675                for case in cases {
4676                    let reachable = interaction_reachability_for_target_with_metadata(
4677                        &case.body,
4678                        target,
4679                        nested
4680                            .next()
4681                            .ok_or_else(|| missing_nested_metadata("switch case"))?,
4682                        layout,
4683                    )?;
4684                    if reachable != InteractionReachability::Reachable {
4685                        return Ok(reachable);
4686                    }
4687                }
4688                if let Some(default) = default {
4689                    let reachable = interaction_reachability_for_target_with_metadata(
4690                        default,
4691                        target,
4692                        nested
4693                            .next()
4694                            .ok_or_else(|| missing_nested_metadata("switch default"))?,
4695                        layout,
4696                    )?;
4697                    if reachable != InteractionReachability::Reachable {
4698                        return Ok(reachable);
4699                    }
4700                }
4701            }
4702            SabreNodeKind::Synchronize => {}
4703        }
4704    }
4705    Ok(InteractionReachability::Reachable)
4706}
4707
4708fn swap_operation(swap: [PhysicalQubit; 2]) -> Operation {
4709    Operation {
4710        instruction: Instruction::Standard(StandardGate::SWAP),
4711        qubits: smallvec![swap[0].qubit(), swap[1].qubit()],
4712        params: SmallVec::new(),
4713        label: None,
4714    }
4715}
4716
4717pub(crate) fn compare_ranked_trials(
4718    left: &mut RankedTrial,
4719    left_index: (usize, usize),
4720    right: &mut RankedTrial,
4721    right_index: (usize, usize),
4722    target: &RoutingTarget,
4723) -> Result<Ordering, CompilerError> {
4724    let count_order = left.native_two_qubit_ops.cmp(&right.native_two_qubit_ops);
4725    if !count_order.is_eq() {
4726        return Ok(count_order);
4727    }
4728
4729    let depth_order = left
4730        .ensure_native_two_qubit_depth(target)?
4731        .cmp(&right.ensure_native_two_qubit_depth(target)?);
4732    if !depth_order.is_eq() {
4733        return Ok(depth_order);
4734    }
4735
4736    Ok(left
4737        .ensure_native_total_depth(target)?
4738        .cmp(&right.ensure_native_total_depth(target)?)
4739        .then_with(|| left_index.cmp(&right_index)))
4740}
4741
4742fn native_cost_for_routed_operation(
4743    operation: &Operation,
4744    target: &RoutingTarget,
4745) -> Result<NativePlanCost, CompilerError> {
4746    let state = DeviceGateState::from_instruction(
4747        &operation.instruction,
4748        operation
4749            .qubits
4750            .iter()
4751            .copied()
4752            .map(PhysicalQubit::from_qubit)
4753            .collect(),
4754    )
4755    .ok_or_else(|| {
4756        CompilerError::InvariantViolation(format!(
4757            "missing native-cost key for routed operation {}",
4758            operation.instruction
4759        ))
4760    })?;
4761    if let Some(cost) = target.native_cost(&state) {
4762        return Ok(cost);
4763    }
4764    if let Some(failure) = target.unsupported_native_plan(&state) {
4765        return Err(CompilerError::DeviceLoweringFailed(failure.clone()));
4766    }
4767    Err(CompilerError::InvariantViolation(format!(
4768        "routed operation {} on {:?} was not prepared in the native plan catalog",
4769        operation.instruction, state.ordered_qargs
4770    )))
4771}
4772
4773#[derive(Debug, Clone, Copy, Default)]
4774struct NativeCircuitCost {
4775    static_native: Option<NativePlanCost>,
4776    path: ExecutionPathCost,
4777}
4778
4779#[derive(Debug, Clone, Copy, Default, PartialEq)]
4780struct ExecutionPathCost {
4781    native_two_qubit_ops: u32,
4782    native_total_ops: u32,
4783    unknown_loop_count: usize,
4784}
4785
4786impl NativeCircuitCost {
4787    fn append_gate(&mut self, cost: NativePlanCost) {
4788        self.static_native = Some(match self.static_native {
4789            Some(current) => current.combine(cost),
4790            None => cost,
4791        });
4792        self.path.append_gate(cost);
4793    }
4794
4795    fn append_sequence(&mut self, other: Self) {
4796        self.static_native = combine_optional_native_cost(self.static_native, other.static_native);
4797        self.path.append_sequence(other.path);
4798    }
4799
4800    fn add_static_branch(&mut self, branch: Self) {
4801        self.static_native = combine_optional_native_cost(self.static_native, branch.static_native);
4802    }
4803
4804    fn append_worst_branch(&mut self, left: Self, right: Self) {
4805        self.path.append_sequence(left.path.worse(right.path));
4806    }
4807}
4808
4809impl ExecutionPathCost {
4810    fn append_gate(&mut self, cost: NativePlanCost) {
4811        self.native_two_qubit_ops = self
4812            .native_two_qubit_ops
4813            .saturating_add(cost.native_two_qubit_ops);
4814        self.native_total_ops = self.native_total_ops.saturating_add(cost.native_total_ops);
4815    }
4816
4817    fn append_sequence(&mut self, other: Self) {
4818        self.native_two_qubit_ops = self
4819            .native_two_qubit_ops
4820            .saturating_add(other.native_two_qubit_ops);
4821        self.native_total_ops = self.native_total_ops.saturating_add(other.native_total_ops);
4822        self.unknown_loop_count = self
4823            .unknown_loop_count
4824            .saturating_add(other.unknown_loop_count);
4825    }
4826
4827    fn repeated(mut self, count: u128) -> Self {
4828        self.native_two_qubit_ops =
4829            (u128::from(self.native_two_qubit_ops) * count).min(u128::from(u32::MAX)) as u32;
4830        self.native_total_ops =
4831            (u128::from(self.native_total_ops) * count).min(u128::from(u32::MAX)) as u32;
4832        self.unknown_loop_count = (self.unknown_loop_count as u128)
4833            .saturating_mul(count)
4834            .min(usize::MAX as u128) as usize;
4835        self
4836    }
4837
4838    fn worse(self, other: Self) -> Self {
4839        if self.compare(other).is_ge() {
4840            self
4841        } else {
4842            other
4843        }
4844    }
4845
4846    fn compare(self, other: Self) -> Ordering {
4847        self.unknown_loop_count
4848            .cmp(&other.unknown_loop_count)
4849            .then_with(|| self.native_two_qubit_ops.cmp(&other.native_two_qubit_ops))
4850            .then_with(|| self.native_total_ops.cmp(&other.native_total_ops))
4851    }
4852}
4853
4854fn combine_optional_native_cost(
4855    left: Option<NativePlanCost>,
4856    right: Option<NativePlanCost>,
4857) -> Option<NativePlanCost> {
4858    match (left, right) {
4859        (Some(left), Some(right)) => Some(left.combine(right)),
4860        (Some(value), None) | (None, Some(value)) => Some(value),
4861        (None, None) => None,
4862    }
4863}
4864
4865fn native_plan_cost_for_operations(
4866    operations: &[Operation],
4867    target: &RoutingTarget,
4868) -> Result<NativeCircuitCost, CompilerError> {
4869    let mut total = NativeCircuitCost::default();
4870    for operation in operations {
4871        match &operation.instruction {
4872            Instruction::Standard(StandardGate::GPhase) => {}
4873            Instruction::Standard(_) | Instruction::McGate(_) => {
4874                let cost = native_cost_for_routed_operation(operation, target)?;
4875                total.append_gate(cost);
4876            }
4877            Instruction::ClassicalControl(control) => match control {
4878                ClassicalControlOp::If(op) => {
4879                    let then_cost =
4880                        native_plan_cost_for_operations(op.then_body().operations(), target)?;
4881                    let else_cost = op
4882                        .else_body()
4883                        .map(|body| native_plan_cost_for_operations(body.operations(), target))
4884                        .transpose()?
4885                        .unwrap_or_default();
4886                    total.add_static_branch(then_cost);
4887                    total.add_static_branch(else_cost);
4888                    total.append_worst_branch(then_cost, else_cost);
4889                }
4890                ClassicalControlOp::While(op) => {
4891                    let mut body = native_plan_cost_for_operations(op.body().operations(), target)?;
4892                    body.path.unknown_loop_count = body.path.unknown_loop_count.saturating_add(1);
4893                    total.append_sequence(body);
4894                }
4895                ClassicalControlOp::For(op) => {
4896                    let mut body = native_plan_cost_for_operations(op.body().operations(), target)?;
4897                    if let Some(iterations) = op.static_iteration_count() {
4898                        total.static_native = combine_optional_native_cost(
4899                            total.static_native,
4900                            repeat_native_cost(body.static_native, iterations),
4901                        );
4902                        body.static_native = None;
4903                        body.path = body.path.repeated(iterations);
4904                    } else {
4905                        total.add_static_branch(body);
4906                        body.static_native = None;
4907                        body.path.unknown_loop_count =
4908                            body.path.unknown_loop_count.saturating_add(1);
4909                    }
4910                    total.append_sequence(body);
4911                }
4912                ClassicalControlOp::Switch(op) => {
4913                    let mut worst_path = None::<ExecutionPathCost>;
4914                    for case in op.cases() {
4915                        let branch =
4916                            native_plan_cost_for_operations(case.body().operations(), target)?;
4917                        total.add_static_branch(branch);
4918                        worst_path = Some(
4919                            worst_path.map_or(branch.path, |current| current.worse(branch.path)),
4920                        );
4921                    }
4922                    if let Some(body) = op.default() {
4923                        let branch = native_plan_cost_for_operations(body.operations(), target)?;
4924                        total.add_static_branch(branch);
4925                        worst_path = Some(
4926                            worst_path.map_or(branch.path, |current| current.worse(branch.path)),
4927                        );
4928                    }
4929                    if let Some(worst_path) = worst_path {
4930                        total.path.append_sequence(worst_path);
4931                    }
4932                }
4933                ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
4934            },
4935            Instruction::ClassicalData(_) | Instruction::Directive(_) | Instruction::Delay => {}
4936            Instruction::UnitaryGate(_) | Instruction::CircuitGate(_) => {
4937                return Err(CompilerError::InvariantViolation(format!(
4938                    "unlowerable routed operation {} reached native trial scoring",
4939                    operation.instruction
4940                )));
4941            }
4942        }
4943    }
4944    Ok(total)
4945}
4946
4947fn repeat_native_cost(cost: Option<NativePlanCost>, count: u128) -> Option<NativePlanCost> {
4948    cost.map(|cost| NativePlanCost {
4949        native_two_qubit_ops: (u128::from(cost.native_two_qubit_ops) * count)
4950            .min(u128::from(u32::MAX)) as u32,
4951        native_total_ops: (u128::from(cost.native_total_ops) * count).min(u128::from(u32::MAX))
4952            as u32,
4953        ..NativePlanCost::default()
4954    })
4955}
4956
4957fn native_two_qubit_depth(
4958    operations: &[Operation],
4959    target: &RoutingTarget,
4960) -> Result<usize, CompilerError> {
4961    let mut qubit_depths = BTreeMap::<Qubit, usize>::new();
4962    let mut max_depth = 0;
4963    for operation in operations {
4964        let local_depth = operation_local_native_two_qubit_depth(operation, target)?;
4965        if local_depth == 0 {
4966            continue;
4967        }
4968        let base = operation
4969            .qubits
4970            .iter()
4971            .map(|qubit| qubit_depths.get(qubit).copied().unwrap_or(0))
4972            .max()
4973            .unwrap_or(0);
4974        let depth = base + local_depth;
4975        for qubit in &operation.qubits {
4976            qubit_depths.insert(*qubit, depth);
4977        }
4978        max_depth = max_depth.max(depth);
4979    }
4980    Ok(max_depth)
4981}
4982
4983fn native_total_depth(
4984    operations: &[Operation],
4985    target: &RoutingTarget,
4986) -> Result<usize, CompilerError> {
4987    let mut qubit_depths = BTreeMap::<Qubit, usize>::new();
4988    let mut max_depth = 0;
4989    for operation in operations {
4990        let local_depth = operation_local_native_total_depth(operation, target)?;
4991        if local_depth == 0 {
4992            continue;
4993        }
4994        let base = operation
4995            .qubits
4996            .iter()
4997            .map(|qubit| qubit_depths.get(qubit).copied().unwrap_or(0))
4998            .max()
4999            .unwrap_or(0);
5000        let depth = base.saturating_add(local_depth);
5001        for qubit in &operation.qubits {
5002            qubit_depths.insert(*qubit, depth);
5003        }
5004        max_depth = max_depth.max(depth);
5005    }
5006    Ok(max_depth)
5007}
5008
5009fn operation_local_native_two_qubit_depth(
5010    operation: &Operation,
5011    target: &RoutingTarget,
5012) -> Result<usize, CompilerError> {
5013    match &operation.instruction {
5014        Instruction::Standard(StandardGate::GPhase) => Ok(0),
5015        Instruction::Standard(_) | Instruction::McGate(_) => {
5016            let state = DeviceGateState::from_instruction(
5017                &operation.instruction,
5018                operation
5019                    .qubits
5020                    .iter()
5021                    .copied()
5022                    .map(PhysicalQubit::from_qubit)
5023                    .collect(),
5024            )
5025            .ok_or_else(|| {
5026                CompilerError::InvariantViolation(format!(
5027                    "missing native-depth key for routed operation {}",
5028                    operation.instruction
5029                ))
5030            })?;
5031            match target.native_plan(&state) {
5032                Some(plan) => Ok(structural_plan_depth(plan, true)),
5033                None => {
5034                    if let Some(failure) = target.unsupported_native_plan(&state) {
5035                        return Err(CompilerError::DeviceLoweringFailed(failure.clone()));
5036                    }
5037                    Err(CompilerError::InvariantViolation(format!(
5038                        "routed operation {} on {:?} was not prepared in the native plan catalog",
5039                        operation.instruction, state.ordered_qargs
5040                    )))
5041                }
5042            }
5043        }
5044        Instruction::ClassicalControl(ClassicalControlOp::If(op)) => {
5045            let then_depth = native_two_qubit_depth(op.then_body().operations(), target)?;
5046            let else_depth = op
5047                .else_body()
5048                .map(|body| native_two_qubit_depth(body.operations(), target))
5049                .transpose()?
5050                .unwrap_or(0);
5051            Ok(then_depth.max(else_depth))
5052        }
5053        Instruction::ClassicalControl(ClassicalControlOp::While(op)) => {
5054            native_two_qubit_depth(op.body().operations(), target)
5055        }
5056        Instruction::ClassicalControl(ClassicalControlOp::For(op)) => {
5057            let body_depth = native_two_qubit_depth(op.body().operations(), target)?;
5058            let iterations = op
5059                .static_iteration_count()
5060                .unwrap_or(1)
5061                .min(usize::MAX as u128) as usize;
5062            Ok(body_depth.saturating_mul(iterations))
5063        }
5064        Instruction::ClassicalControl(ClassicalControlOp::Switch(op)) => {
5065            let mut depth = 0;
5066            for case in op.cases() {
5067                depth = depth.max(native_two_qubit_depth(case.body().operations(), target)?);
5068            }
5069            if let Some(body) = op.default() {
5070                depth = depth.max(native_two_qubit_depth(body.operations(), target)?);
5071            }
5072            Ok(depth)
5073        }
5074        _ => Ok(0),
5075    }
5076}
5077
5078fn operation_local_native_total_depth(
5079    operation: &Operation,
5080    target: &RoutingTarget,
5081) -> Result<usize, CompilerError> {
5082    match &operation.instruction {
5083        Instruction::Standard(StandardGate::GPhase) => Ok(0),
5084        Instruction::Standard(_) | Instruction::McGate(_) => {
5085            let state = DeviceGateState::from_instruction(
5086                &operation.instruction,
5087                operation
5088                    .qubits
5089                    .iter()
5090                    .copied()
5091                    .map(PhysicalQubit::from_qubit)
5092                    .collect(),
5093            )
5094            .ok_or_else(|| {
5095                CompilerError::InvariantViolation(format!(
5096                    "missing native-total-depth key for routed operation {}",
5097                    operation.instruction
5098                ))
5099            })?;
5100            match target.native_plan(&state) {
5101                Some(plan) => Ok(structural_plan_depth(plan, false)),
5102                None => {
5103                    if let Some(failure) = target.unsupported_native_plan(&state) {
5104                        return Err(CompilerError::DeviceLoweringFailed(failure.clone()));
5105                    }
5106                    Err(CompilerError::InvariantViolation(format!(
5107                        "routed operation {} on {:?} was not prepared in the native plan catalog",
5108                        operation.instruction, state.ordered_qargs
5109                    )))
5110                }
5111            }
5112        }
5113        Instruction::ClassicalControl(ClassicalControlOp::If(op)) => {
5114            let then_depth = native_total_depth(op.then_body().operations(), target)?;
5115            let else_depth = op
5116                .else_body()
5117                .map(|body| native_total_depth(body.operations(), target))
5118                .transpose()?
5119                .unwrap_or(0);
5120            Ok(then_depth.max(else_depth))
5121        }
5122        Instruction::ClassicalControl(ClassicalControlOp::While(op)) => {
5123            native_total_depth(op.body().operations(), target)
5124        }
5125        Instruction::ClassicalControl(ClassicalControlOp::For(op)) => {
5126            let body_depth = native_total_depth(op.body().operations(), target)?;
5127            let iterations = op
5128                .static_iteration_count()
5129                .unwrap_or(1)
5130                .min(usize::MAX as u128) as usize;
5131            Ok(body_depth.saturating_mul(iterations))
5132        }
5133        Instruction::ClassicalControl(ClassicalControlOp::Switch(op)) => {
5134            let mut depth = 0;
5135            for case in op.cases() {
5136                depth = depth.max(native_total_depth(case.body().operations(), target)?);
5137            }
5138            if let Some(body) = op.default() {
5139                depth = depth.max(native_total_depth(body.operations(), target)?);
5140            }
5141            Ok(depth)
5142        }
5143        Instruction::ClassicalData(_) | Instruction::Directive(_) | Instruction::Delay => Ok(0),
5144        Instruction::UnitaryGate(_) | Instruction::CircuitGate(_) => {
5145            Err(CompilerError::InvariantViolation(format!(
5146                "unlowerable routed operation {} reached native depth scoring",
5147                operation.instruction
5148            )))
5149        }
5150        Instruction::ClassicalControl(ClassicalControlOp::Break | ClassicalControlOp::Continue) => {
5151            Ok(0)
5152        }
5153    }
5154}
5155
5156fn structural_plan_depth(plan: &StructuralNativePlan, two_qubit_only: bool) -> usize {
5157    let mut qubit_depths = BTreeMap::<PhysicalQubit, usize>::new();
5158    let mut max_depth = 0;
5159    for qargs in &plan.leaves {
5160        if qargs.is_empty() || (two_qubit_only && qargs.len() != 2) {
5161            continue;
5162        }
5163        let depth = qargs
5164            .iter()
5165            .map(|qubit| qubit_depths.get(qubit).copied().unwrap_or(0))
5166            .max()
5167            .unwrap_or(0)
5168            .saturating_add(1);
5169        for qubit in qargs {
5170            qubit_depths.insert(*qubit, depth);
5171        }
5172        max_depth = max_depth.max(depth);
5173    }
5174    max_depth
5175}
5176
5177fn two_qubit_operation_count(operations: &[Operation]) -> usize {
5178    operations
5179        .iter()
5180        .map(|operation| match &operation.instruction {
5181            Instruction::ClassicalControl(
5182                ClassicalControlOp::Break | ClassicalControlOp::Continue,
5183            ) => usize::from(operation.qubits.len() == 2),
5184            Instruction::ClassicalControl(control) => control
5185                .bodies()
5186                .map(|body| two_qubit_operation_count(body.operations()))
5187                .sum(),
5188            _ => usize::from(operation.qubits.len() == 2),
5189        })
5190        .sum()
5191}
5192
5193/// Estimates ASAP two-qubit depth for trial ranking.
5194///
5195/// This is not a full scheduler. Control-flow contributes the maximum local
5196/// branch or body depth, and parent operations are chained by used qubits.
5197fn two_qubit_depth(operations: &[Operation]) -> usize {
5198    let mut qubit_depths = BTreeMap::<Qubit, usize>::new();
5199    let mut max_depth = 0usize;
5200
5201    for operation in operations {
5202        let local_depth = operation_local_two_qubit_depth(operation);
5203        if local_depth == 0 {
5204            continue;
5205        }
5206
5207        let base = operation
5208            .qubits
5209            .iter()
5210            .map(|qubit| qubit_depths.get(qubit).copied().unwrap_or(0))
5211            .max()
5212            .unwrap_or(0);
5213        let depth = base + local_depth;
5214        for qubit in &operation.qubits {
5215            qubit_depths.insert(*qubit, depth);
5216        }
5217        max_depth = max_depth.max(depth);
5218    }
5219
5220    max_depth
5221}
5222
5223fn operation_local_two_qubit_depth(operation: &Operation) -> usize {
5224    match &operation.instruction {
5225        Instruction::Standard(StandardGate::SWAP) => 3,
5226        Instruction::ClassicalControl(ClassicalControlOp::If(op)) => {
5227            let then_depth = two_qubit_depth(op.then_body().operations());
5228            let else_depth = op
5229                .else_body()
5230                .map(|body| two_qubit_depth(body.operations()))
5231                .unwrap_or(0);
5232            then_depth.max(else_depth)
5233        }
5234        Instruction::ClassicalControl(ClassicalControlOp::While(op)) => {
5235            two_qubit_depth(op.body().operations())
5236        }
5237        Instruction::ClassicalControl(ClassicalControlOp::For(op)) => {
5238            let iterations = op
5239                .static_iteration_count()
5240                .unwrap_or(1)
5241                .min(usize::MAX as u128) as usize;
5242            two_qubit_depth(op.body().operations()).saturating_mul(iterations)
5243        }
5244        Instruction::ClassicalControl(ClassicalControlOp::Switch(op)) => op
5245            .cases()
5246            .iter()
5247            .map(|case| two_qubit_depth(case.body().operations()))
5248            .chain(
5249                op.default()
5250                    .into_iter()
5251                    .map(|body| two_qubit_depth(body.operations())),
5252            )
5253            .max()
5254            .unwrap_or(0),
5255        _ if operation.qubits.len() == 2 => 1,
5256        _ => 0,
5257    }
5258}
5259
5260fn operation_count(operations: &[Operation]) -> usize {
5261    operations
5262        .iter()
5263        .map(|operation| {
5264            1 + match &operation.instruction {
5265                Instruction::ClassicalControl(control) => control
5266                    .bodies()
5267                    .map(|body| operation_count(body.operations()))
5268                    .sum::<usize>(),
5269                _ => 0,
5270            }
5271        })
5272        .sum()
5273}
5274
5275#[cfg(test)]
5276#[path = "score_test.rs"]
5277mod score_test;