1use 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;
55const ROUTING_PARALLELISM_THRESHOLDS: ParallelismThresholds =
58 ParallelismThresholds::new(256, 2_048, 32_768);
59
60#[derive(Debug, Clone)]
62pub struct SabreRoutingResult {
63 pub circuit: Circuit,
65 pub initial_layout: Layout,
67 pub final_layout: Layout,
69 pub swap_count: usize,
71 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#[derive(Debug, Clone, Default, PartialEq)]
89pub struct SabreRoutingDiagnostics {
90 pub trials_evaluated: usize,
92 pub selected_trial_index: usize,
94 pub fallback_count: usize,
96 pub control_flow_blocks_routed: usize,
98 pub two_qubit_depth: usize,
100 pub operation_count: usize,
102 pub native_two_qubit_count: usize,
104 pub native_two_qubit_depth: usize,
106 pub native_total_depth: usize,
108 pub native_operation_count: usize,
110 pub unknown_loop_count: usize,
112 pub requirement_signature_count: usize,
114 pub eager_pair_state_count: usize,
116 pub lazy_pair_l1_lookup_count: usize,
118 pub lazy_pair_l1_hit_count: usize,
120 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#[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 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
544pub 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 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 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 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, ¶meter_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 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 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 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
1017pub(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
1034pub 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
1078pub 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 GenericUnary,
1112 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
3491struct 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
3742fn 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
3812fn 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
3903fn 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#[derive(Debug, Clone)]
4021pub(crate) struct MovementComponentAssignment {
4022 pub(crate) components: Vec<Vec<PhysicalQubit>>,
4023 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
4047pub(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
4443pub(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
5193fn 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;