1use super::interaction_seed::{interaction_aware_layouts, interaction_layout_cost};
25use super::{
26 CircuitLayoutAnalysis, GreedyCandidateOutcome, LayoutDiagnostics, LayoutObjective,
27 LayoutResult, LayoutScore, PhysicalLayoutGraph, Vf2EdgeRequirement, Vf2LayoutConfig,
28 Vf2PreparedOutcome, analyze_circuit_for_layout, greedy_layout_candidate_prepared,
29 is_perfect_layout, try_vf2_perfect_layout_prepared,
30};
31use crate::circuit::Circuit;
32use crate::compile::device_planning::DevicePlanningSession;
33use crate::compile::parallelism::{ParallelismThresholds, parallel_chunk_size};
34use crate::compile::sabre::{
35 ComponentAssignmentSearch, InteractionReachability, PreparedRouteMetadata, RankedTrial,
36 RequirementReachabilityFailure, RoutingTarget, SabreConfig, SabreDag, TrialResult,
37 compare_ranked_trials, interaction_reachability_for_target_with_metadata,
38 movement_component_assignment, normalize_initial_layout_for_target,
39 refine_layout_with_metadata, route_ranked_trial_with_metadata,
40};
41use crate::compile::{CompilerError, SabreRoutingFailure};
42use crate::device::{Device, Layout, LogicalQubit, PhysicalQubit};
43use rand::SeedableRng;
44use rand::rngs::StdRng;
45use rand::seq::SliceRandom;
46use rayon::prelude::*;
47use std::collections::{BTreeMap, BTreeSet};
48use std::sync::Arc;
49
50const LAYOUT_PARALLELISM_THRESHOLDS: ParallelismThresholds =
54 ParallelismThresholds::new(2_048, 8_192, 32_768);
55const PARETO_ROUTE_FRONTIER_CAPACITY: usize = 64;
56const PARETO_EQUAL_QUALITY_CAPACITY: usize = 2;
57const PARETO_ROUTE_OUTCOME_BATCH_CAPACITY: usize = 64;
58
59#[derive(Debug, Clone)]
66pub struct PreparedSabreCircuit {
67 analysis: CircuitLayoutAnalysis,
68 routing_dag: SabreDag,
69 refinement_dag: SabreDag,
70 backward_refinement_dag: SabreDag,
71}
72
73#[derive(Debug, Clone)]
81pub struct PreparedSabreTarget {
82 physical: Arc<PhysicalLayoutGraph>,
83 routing: Arc<RoutingTarget>,
84 routing_metadata: PreparedRouteMetadata,
85 refinement_metadata: PreparedRouteMetadata,
86 backward_refinement_metadata: PreparedRouteMetadata,
87}
88
89impl PreparedSabreTarget {
90 pub fn physical(&self) -> &PhysicalLayoutGraph {
92 &self.physical
93 }
94
95 pub(crate) fn routing_target(&self) -> &RoutingTarget {
96 &self.routing
97 }
98}
99
100impl PreparedSabreCircuit {
101 pub fn analysis(&self) -> &CircuitLayoutAnalysis {
103 &self.analysis
104 }
105
106 pub fn logical_qubits(&self) -> &[LogicalQubit] {
108 &self.analysis.logical_qubits
109 }
110}
111
112pub fn prepare_sabre_circuit(circuit: &Circuit) -> Result<PreparedSabreCircuit, CompilerError> {
124 let analysis = analyze_circuit_for_layout(circuit)?;
125 let routing_dag = SabreDag::from_operations(circuit.operations())?;
126 let refinement_dag = SabreDag::refinement_workload(circuit.operations())?;
127 let backward_refinement_dag = refinement_dag.reverse_interactions();
128 Ok(PreparedSabreCircuit {
129 analysis,
130 routing_dag,
131 refinement_dag,
132 backward_refinement_dag,
133 })
134}
135
136pub fn prepare_sabre_topology_target(
142 prepared: &PreparedSabreCircuit,
143 device: &Device,
144) -> Result<PreparedSabreTarget, CompilerError> {
145 let physical = Arc::new(PhysicalLayoutGraph::from_device(device)?);
146 let routing = Arc::new(RoutingTarget::from_physical(&physical)?);
147 prepare_sabre_topology_target_with_prepared(prepared, physical, routing)
148}
149
150pub(crate) fn prepare_sabre_topology_target_with_prepared(
151 prepared: &PreparedSabreCircuit,
152 physical: Arc<PhysicalLayoutGraph>,
153 routing: Arc<RoutingTarget>,
154) -> Result<PreparedSabreTarget, CompilerError> {
155 finish_sabre_target_preparation(prepared, physical, routing)
156}
157
158pub fn prepare_sabre_device_target(
164 prepared: &PreparedSabreCircuit,
165 device: &Device,
166) -> Result<PreparedSabreTarget, CompilerError> {
167 let planning_session = DevicePlanningSession::new(device);
168 prepare_sabre_device_target_with_session(prepared, device, &planning_session)
169}
170
171pub(crate) fn prepare_sabre_device_target_with_session(
172 prepared: &PreparedSabreCircuit,
173 device: &Device,
174 planning_session: &DevicePlanningSession,
175) -> Result<PreparedSabreTarget, CompilerError> {
176 let physical = Arc::new(PhysicalLayoutGraph::from_device(device)?);
177 prepare_sabre_device_target_with_session_and_physical(
178 prepared,
179 device,
180 physical,
181 planning_session,
182 )
183}
184
185pub(crate) fn prepare_sabre_device_target_with_session_and_physical(
186 prepared: &PreparedSabreCircuit,
187 device: &Device,
188 physical: Arc<PhysicalLayoutGraph>,
189 planning_session: &DevicePlanningSession,
190) -> Result<PreparedSabreTarget, CompilerError> {
191 let routing = Arc::new(RoutingTarget::from_device_with_session(
192 device,
193 &physical,
194 &prepared.routing_dag,
195 planning_session,
196 )?);
197 finish_sabre_target_preparation(prepared, physical, routing)
198}
199
200fn finish_sabre_target_preparation(
201 prepared: &PreparedSabreCircuit,
202 physical: Arc<PhysicalLayoutGraph>,
203 routing: Arc<RoutingTarget>,
204) -> Result<PreparedSabreTarget, CompilerError> {
205 let routing_metadata = PreparedRouteMetadata::new(&prepared.routing_dag, &routing)?;
206 let refinement_metadata = PreparedRouteMetadata::new(&prepared.refinement_dag, &routing)?;
207 let backward_refinement_metadata =
208 PreparedRouteMetadata::new(&prepared.backward_refinement_dag, &routing)?;
209 Ok(PreparedSabreTarget {
210 physical,
211 routing,
212 routing_metadata,
213 refinement_metadata,
214 backward_refinement_metadata,
215 })
216}
217
218pub fn sabre_layout(
263 circuit: &Circuit,
264 device: &Device,
265 objective: &LayoutObjective,
266 config: &SabreConfig,
267) -> Result<LayoutResult, CompilerError> {
268 let prepared = prepare_sabre_circuit(circuit)?;
269 let target = prepare_sabre_topology_target(&prepared, device)?;
270 sabre_layout_prepared(&prepared, &target, objective, config)
271}
272
273pub fn sabre_layout_prepared(
315 prepared: &PreparedSabreCircuit,
316 prepared_target: &PreparedSabreTarget,
317 objective: &LayoutObjective,
318 config: &SabreConfig,
319) -> Result<LayoutResult, CompilerError> {
320 let selection =
321 sabre_route_selection_prepared(prepared, prepared_target, objective, config, false)?;
322 Ok(LayoutResult {
323 layout: selection.initial_layout,
324 score: Some(selection.score),
325 diagnostics: selection.diagnostics,
326 })
327}
328
329pub(crate) struct PreparedSabreRouteSelection {
330 pub(crate) initial_layout: Layout,
331 pub(crate) trial: TrialResult,
332 pub(crate) selected_trial_index: usize,
333 pub(crate) trials_evaluated: usize,
334 pub(crate) score: LayoutScore,
335 pub(crate) diagnostics: LayoutDiagnostics,
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
340pub(crate) struct SabreParetoCandidateId {
341 pub(crate) layout_index: usize,
342 pub(crate) route_index: usize,
343}
344
345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346struct SabreRouteQuality {
347 native_two_qubit_ops: usize,
348 native_two_qubit_depth: usize,
349 native_total_depth: usize,
350 native_total_ops: usize,
351}
352
353impl SabreRouteQuality {
354 fn dominates(self, other: Self) -> bool {
355 self.native_two_qubit_ops <= other.native_two_qubit_ops
356 && self.native_two_qubit_depth <= other.native_two_qubit_depth
357 && self.native_total_depth <= other.native_total_depth
358 && self.native_total_ops <= other.native_total_ops
359 && self != other
360 }
361
362 fn objective(self, index: usize) -> usize {
363 match index {
364 0 => self.native_two_qubit_ops,
365 1 => self.native_two_qubit_depth,
366 2 => self.native_total_depth,
367 3 => self.native_total_ops,
368 _ => unreachable!("SABRE route quality has four objectives"),
369 }
370 }
371
372 fn stable_route_key(self) -> (usize, usize, usize, usize) {
373 (
374 self.native_two_qubit_ops,
375 self.native_two_qubit_depth,
376 self.native_total_depth,
377 self.native_total_ops,
378 )
379 }
380}
381
382pub(crate) struct PreparedSabreParetoRouteSelection {
383 pub(crate) id: SabreParetoCandidateId,
384 pub(crate) selection: PreparedSabreRouteSelection,
385}
386
387#[derive(Clone)]
388struct ParetoRefinedCandidate {
389 index: usize,
390 layout: Layout,
391 score: LayoutScore,
392}
393
394#[derive(Clone)]
395struct ParetoRouteCandidate {
396 id: SabreParetoCandidateId,
397 layout: Layout,
398 score: LayoutScore,
399 trial: RankedTrial,
400 quality: SabreRouteQuality,
401 structure_fingerprint: u64,
402}
403
404pub(crate) struct PreparedSabreParetoSearch {
409 config: SabreConfig,
410 base_seed: u64,
411 refined: Vec<ParetoRefinedCandidate>,
412 frontier: Vec<ParetoRouteCandidate>,
413 emitted: BTreeSet<SabreParetoCandidateId>,
414 next_route_index: usize,
415 trials_evaluated: usize,
416 candidates_evaluated: usize,
417 candidate_notes: Vec<String>,
418}
419
420impl PreparedSabreParetoSearch {
421 pub(crate) fn new(
426 prepared: &PreparedSabreCircuit,
427 prepared_target: &PreparedSabreTarget,
428 objective: &LayoutObjective,
429 config: &SabreConfig,
430 supplied_layout: Option<&Layout>,
431 ) -> Result<Self, CompilerError> {
432 validate_layout_config(config)?;
433 let target = prepared_target.routing_target();
434 if prepared.logical_qubits().len() > target.physical_qubits.len() {
435 return Err(CompilerError::InvalidInput(format!(
436 "sabre layout requires at least as many usable physical qubits as logical qubits; got {} logical qubits and {} usable physical qubits",
437 prepared.logical_qubits().len(),
438 target.physical_qubits.len()
439 )));
440 }
441
442 let base_seed =
445 splitmix64(config.seed.unwrap_or_else(rand::random) ^ 0x7061_7265_746f_5f62);
446 let (layouts, mut candidate_notes) = if let Some(layout) = supplied_layout {
447 (
448 vec![normalize_initial_layout_for_target(
449 prepared.logical_qubits(),
450 target,
451 layout,
452 )?],
453 vec!["kept the caller-supplied initial layout fixed".to_string()],
454 )
455 } else {
456 let mut rng = StdRng::seed_from_u64(base_seed);
457 let generated =
458 initial_layout_candidates(prepared, prepared_target, objective, config, &mut rng)?;
459 (generated.layouts, generated.notes)
460 };
461 let candidates_evaluated = layouts.len();
462 if supplied_layout.is_none()
463 && dense_interaction_path_skips_refinement(
464 prepared.analysis(),
465 prepared_target.physical(),
466 )
467 {
468 candidate_notes.push(
469 "forced four-refinement exploratory profile for dense interactions".to_string(),
470 );
471 }
472
473 let refinement_iterations = if supplied_layout.is_none() {
474 config.refinement_iterations
475 } else {
476 0
477 };
478 let mut refined = Vec::with_capacity(layouts.len());
479 let mut missing_terminal = 0;
480 let mut movement_unreachable = 0;
481 let mut unsupported_native = 0;
482 for (index, layout) in layouts.into_iter().enumerate() {
483 match prepare_pareto_refined_candidate(
484 prepared,
485 prepared_target,
486 objective,
487 config,
488 refinement_iterations,
489 CandidateTrial {
490 index,
491 layout,
492 base_seed,
493 },
494 )? {
495 CandidateOutcome::Success(candidate) => refined.push(candidate),
496 CandidateOutcome::Infeasible(reason, _) => match reason {
497 CandidateInfeasibleReason::MissingTerminal => missing_terminal += 1,
498 CandidateInfeasibleReason::MovementUnreachable => movement_unreachable += 1,
499 CandidateInfeasibleReason::UnsupportedNative => unsupported_native += 1,
500 },
501 }
502 }
503 if refined.is_empty() {
504 return Err(CompilerError::SabreRoutingFailed(
505 SabreRoutingFailure::NoFeasibleLayoutCandidate {
506 evaluated: candidates_evaluated,
507 missing_terminal,
508 movement_unreachable,
509 unsupported_native,
510 },
511 ));
512 }
513
514 Ok(Self {
515 config: config.clone(),
516 base_seed,
517 refined,
518 frontier: Vec::new(),
519 emitted: BTreeSet::new(),
520 next_route_index: 0,
521 trials_evaluated: 0,
522 candidates_evaluated,
523 candidate_notes,
524 })
525 }
526
527 pub(crate) fn extend_to(
530 &mut self,
531 prepared: &PreparedSabreCircuit,
532 prepared_target: &PreparedSabreTarget,
533 route_limit: usize,
534 candidate_limit: usize,
535 ) -> Result<Vec<PreparedSabreParetoRouteSelection>, CompilerError> {
536 if route_limit <= self.next_route_index || route_limit > self.config.routing_trials {
537 return Err(CompilerError::InvalidInput(format!(
538 "incremental SABRE route limit must be in {}..={}, got {route_limit}",
539 self.next_route_index.saturating_add(1),
540 self.config.routing_trials
541 )));
542 }
543 let start = self.next_route_index;
544 let tasks = self
545 .refined
546 .iter()
547 .enumerate()
548 .flat_map(|(refined_index, _)| {
549 (start..route_limit).map(move |route_index| (refined_index, route_index))
550 })
551 .collect::<Vec<_>>();
552 for task_batch in tasks.chunks(PARETO_ROUTE_OUTCOME_BATCH_CAPACITY) {
553 let outcomes = task_batch
554 .into_par_iter()
555 .map(|&(refined_index, route_index)| {
556 route_pareto_candidate(
557 prepared,
558 prepared_target,
559 &self.config,
560 self.base_seed,
561 &self.refined[refined_index],
562 route_index,
563 )
564 })
565 .collect::<Result<Vec<_>, CompilerError>>()?;
566 self.trials_evaluated = self.trials_evaluated.saturating_add(outcomes.len());
567
568 let mut new_candidates = Vec::new();
569 for outcome in outcomes {
570 if let CandidateOutcome::Success(candidate) = outcome {
571 new_candidates.push(candidate);
572 }
573 }
574 self.merge_frontier(new_candidates);
575 }
576 self.next_route_index = route_limit;
577 let selected_ids = self.select_diverse_unseen(candidate_limit);
578 let target = prepared_target.routing_target();
579 selected_ids
580 .into_iter()
581 .map(|id| {
582 let candidate = self
583 .frontier
584 .iter()
585 .find(|candidate| candidate.id == id)
586 .expect("selected Pareto candidate remains on the frontier")
587 .clone();
588 let is_perfect = is_perfect_layout(
589 prepared.analysis(),
590 prepared_target.physical(),
591 &candidate.layout,
592 );
593 let mut notes = self.candidate_notes.clone();
594 notes.push(format!(
595 "selected exploratory Pareto route layout={} trial={} from 0..{route_limit}",
596 id.layout_index, id.route_index
597 ));
598 Ok(PreparedSabreParetoRouteSelection {
599 id,
600 selection: PreparedSabreRouteSelection {
601 initial_layout: candidate.layout,
602 trial: candidate.trial.finish(target)?,
603 selected_trial_index: id.route_index,
604 trials_evaluated: self.trials_evaluated,
605 score: candidate.score.clone(),
606 diagnostics: LayoutDiagnostics {
607 is_perfect,
608 candidates_evaluated: self.candidates_evaluated,
609 used_fidelity: candidate.score.used_fidelity,
610 notes,
611 },
612 },
613 })
614 })
615 .collect()
616 }
617
618 fn merge_frontier(&mut self, new_candidates: Vec<ParetoRouteCandidate>) {
619 self.frontier.extend(new_candidates);
620 self.frontier.sort_by_key(|candidate| candidate.id);
621 let mut frontier = Vec::<ParetoRouteCandidate>::new();
622 for candidate in self.frontier.drain(..) {
623 if frontier.iter().any(|incumbent| {
624 incumbent.quality.dominates(candidate.quality)
625 || incumbent.quality == candidate.quality
626 && (incumbent.structure_fingerprint == candidate.structure_fingerprint
627 || frontier
628 .iter()
629 .filter(|peer| peer.quality == candidate.quality)
630 .count()
631 >= PARETO_EQUAL_QUALITY_CAPACITY)
632 }) {
633 continue;
634 }
635 frontier.retain(|incumbent| !candidate.quality.dominates(incumbent.quality));
636 frontier.push(candidate);
637 }
638 if frontier.len() > PARETO_ROUTE_FRONTIER_CAPACITY {
639 let retained = select_diverse_candidate_ids(
640 frontier.iter().collect(),
641 PARETO_ROUTE_FRONTIER_CAPACITY,
642 )
643 .into_iter()
644 .collect::<BTreeSet<_>>();
645 frontier.retain(|candidate| retained.contains(&candidate.id));
646 }
647 self.frontier = frontier;
648 }
649
650 fn select_diverse_unseen(&mut self, limit: usize) -> Vec<SabreParetoCandidateId> {
651 if limit == 0 {
652 return Vec::new();
653 }
654 let unseen = self
655 .frontier
656 .iter()
657 .filter(|candidate| !self.emitted.contains(&candidate.id))
658 .collect::<Vec<_>>();
659 let selected = select_diverse_candidate_ids(unseen, limit);
660 self.emitted.extend(selected.iter().copied());
661 selected
662 }
663}
664
665fn select_diverse_candidate_ids(
666 mut candidates: Vec<&ParetoRouteCandidate>,
667 limit: usize,
668) -> Vec<SabreParetoCandidateId> {
669 let mut selected = Vec::new();
670 for objective in 0..4 {
671 let best = candidates
672 .iter()
673 .copied()
674 .filter(|candidate| !selected.contains(&candidate.id))
675 .min_by_key(|candidate| {
676 (
677 candidate.quality.objective(objective),
678 candidate.quality.stable_route_key(),
679 candidate.structure_fingerprint,
680 candidate.id,
681 )
682 });
683 if let Some(best) = best {
684 selected.push(best.id);
685 }
686 if selected.len() == limit {
687 return selected;
688 }
689 }
690 candidates.sort_by_key(|candidate| {
691 (
692 candidate.quality.stable_route_key(),
693 candidate.structure_fingerprint,
694 candidate.id,
695 )
696 });
697 for candidate in candidates {
698 if !selected.contains(&candidate.id) {
699 selected.push(candidate.id);
700 }
701 if selected.len() == limit {
702 break;
703 }
704 }
705 selected
706}
707
708#[allow(clippy::too_many_arguments)]
709fn prepare_pareto_refined_candidate(
710 prepared: &PreparedSabreCircuit,
711 prepared_target: &PreparedSabreTarget,
712 objective: &LayoutObjective,
713 config: &SabreConfig,
714 refinement_iterations: usize,
715 trial: CandidateTrial,
716) -> Result<CandidateOutcome<ParetoRefinedCandidate>, CompilerError> {
717 let target = prepared_target.routing_target();
718 match interaction_reachability_for_target_with_metadata(
719 &prepared.routing_dag,
720 target,
721 &prepared_target.routing_metadata,
722 &trial.layout,
723 )? {
724 InteractionReachability::Reachable => {}
725 InteractionReachability::UnreachableUnary {
726 cause: RequirementReachabilityFailure::NoExecutableTerminal,
727 ..
728 }
729 | InteractionReachability::UnreachablePair {
730 cause: RequirementReachabilityFailure::NoExecutableTerminal,
731 ..
732 } => {
733 return Ok(CandidateOutcome::Infeasible(
734 CandidateInfeasibleReason::MissingTerminal,
735 0,
736 ));
737 }
738 InteractionReachability::UnreachableUnary {
739 cause: RequirementReachabilityFailure::MovementDisconnected,
740 ..
741 }
742 | InteractionReachability::UnreachablePair {
743 cause: RequirementReachabilityFailure::MovementDisconnected,
744 ..
745 } => {
746 return Ok(CandidateOutcome::Infeasible(
747 CandidateInfeasibleReason::MovementUnreachable,
748 0,
749 ));
750 }
751 }
752
753 let mut refined = trial.layout;
754 let initial_signature = layout_mapping_signature(&refined, prepared.logical_qubits())?;
755 let mut seen_signatures = BTreeSet::from([initial_signature]);
756 for iteration in 0..refinement_iterations {
757 let forward_seed = derive_semantic_seed(
758 trial.base_seed,
759 11,
760 trial.index,
761 iteration,
762 layout_signature(&refined),
763 );
764 let forward = match refine_layout_with_metadata(
765 &prepared.refinement_dag,
766 target,
767 &prepared_target.refinement_metadata,
768 &refined,
769 &config.heuristic,
770 forward_seed,
771 ) {
772 Ok(layout) => layout,
773 Err(error) => return classify_candidate_error(error, 0),
774 };
775 let backward_seed = derive_semantic_seed(
776 trial.base_seed,
777 12,
778 trial.index,
779 iteration,
780 layout_signature(&forward),
781 );
782 let next = match refine_layout_with_metadata(
783 &prepared.backward_refinement_dag,
784 target,
785 &prepared_target.backward_refinement_metadata,
786 &forward,
787 &config.heuristic,
788 backward_seed,
789 ) {
790 Ok(layout) => layout,
791 Err(error) => return classify_candidate_error(error, 0),
792 };
793 let signature = layout_mapping_signature(&next, prepared.logical_qubits())?;
794 if !seen_signatures.insert(signature) {
795 break;
796 }
797 refined = next;
798 }
799 let score =
800 objective.score_layout(prepared.analysis(), prepared_target.physical(), &refined)?;
801 Ok(CandidateOutcome::Success(ParetoRefinedCandidate {
802 index: trial.index,
803 layout: refined,
804 score,
805 }))
806}
807
808fn route_pareto_candidate(
809 prepared: &PreparedSabreCircuit,
810 prepared_target: &PreparedSabreTarget,
811 config: &SabreConfig,
812 base_seed: u64,
813 refined: &ParetoRefinedCandidate,
814 route_index: usize,
815) -> Result<CandidateOutcome<ParetoRouteCandidate>, CompilerError> {
816 let target = prepared_target.routing_target();
817 let seed = derive_semantic_seed(
818 base_seed,
819 13,
820 refined.index,
821 route_index,
822 layout_signature(&refined.layout),
823 );
824 let mut trial = match route_ranked_trial_with_metadata(
825 &prepared.routing_dag,
826 target,
827 &prepared_target.routing_metadata,
828 &refined.layout,
829 &config.heuristic,
830 seed,
831 ) {
832 Ok(trial) => trial,
833 Err(error) => return classify_candidate_error(error, 1),
834 };
835 let quality = match trial.quality(target) {
836 Ok(quality) => quality,
837 Err(error) => return classify_candidate_error(error, 1),
838 };
839 let structure_fingerprint = trial.structure_fingerprint();
840 Ok(CandidateOutcome::Success(ParetoRouteCandidate {
841 id: SabreParetoCandidateId {
842 layout_index: refined.index,
843 route_index,
844 },
845 layout: refined.layout.clone(),
846 score: refined.score.clone(),
847 trial,
848 structure_fingerprint,
849 quality: SabreRouteQuality {
850 native_two_qubit_ops: quality.native_two_qubit_ops,
851 native_two_qubit_depth: quality.native_two_qubit_depth,
852 native_total_depth: quality.native_total_depth,
853 native_total_ops: quality.native_total_ops,
854 },
855 }))
856}
857
858pub(crate) fn sabre_route_selection_prepared(
859 prepared: &PreparedSabreCircuit,
860 prepared_target: &PreparedSabreTarget,
861 objective: &LayoutObjective,
862 config: &SabreConfig,
863 retain_provenance: bool,
864) -> Result<PreparedSabreRouteSelection, CompilerError> {
865 validate_layout_config(config)?;
866 let physical = &prepared_target.physical;
867 let target = &prepared_target.routing;
868 let analysis = &prepared.analysis;
869 let sabre = &prepared.routing_dag;
870 let forwards = &prepared.refinement_dag;
871 let backwards = &prepared.backward_refinement_dag;
872 let logical_qubits = analysis.logical_qubits.clone();
873
874 if logical_qubits.len() > target.physical_qubits.len() {
875 return Err(CompilerError::InvalidInput(format!(
876 "sabre layout requires at least as many usable physical qubits as logical qubits; got {} logical qubits and {} usable physical qubits",
877 logical_qubits.len(),
878 target.physical_qubits.len()
879 )));
880 }
881
882 let base_seed = config.seed.unwrap_or_else(rand::random);
883 let mut rng = StdRng::seed_from_u64(base_seed);
884 let initial_candidates =
885 initial_layout_candidates(prepared, prepared_target, objective, config, &mut rng)?;
886 let candidates = initial_candidates.layouts;
887 let mut candidate_notes = initial_candidates.notes;
888 let generated_candidates = candidates.len();
889 let refinement_iterations = if dense_interaction_path_skips_refinement(analysis, physical) {
890 candidate_notes.push(
891 "skipped bidirectional refinement because dense logical interactions already span a path topology"
892 .to_string(),
893 );
894 0
895 } else {
896 config.refinement_iterations
897 };
898 let perfect_candidate = (!target.uses_native_costs())
899 .then(|| {
900 candidates
901 .iter()
902 .position(|layout| is_perfect_layout(analysis, physical, layout))
903 })
904 .flatten();
905 let early_search = if let Some(index) = perfect_candidate {
906 let mut optimal_config = config.clone();
907 optimal_config.refinement_iterations = 0;
908 optimal_config.routing_trials = 1;
909 match evaluate_candidate(
910 sabre,
911 forwards,
912 backwards,
913 target,
914 prepared_target,
915 analysis,
916 physical,
917 objective,
918 &optimal_config,
919 0,
920 CandidateTrial {
921 index,
922 layout: candidates[index].clone(),
923 base_seed,
924 },
925 1,
926 )? {
927 CandidateOutcome::Success(evaluation) if evaluation.trial.swap_count() == 0 => {
928 candidate_notes.push(format!(
929 "selected a zero-SWAP topology-optimal candidate without evaluating the remaining {} candidates",
930 generated_candidates.saturating_sub(1)
931 ));
932 Some(CandidateSearch::from_outcome(CandidateOutcome::Success(
933 evaluation,
934 )))
935 }
936 CandidateOutcome::Success(_) | CandidateOutcome::Infeasible(_, _) => None,
937 }
938 } else {
939 None
940 };
941
942 let (search, candidates_evaluated) = if let Some(search) = early_search {
943 (search, 1)
944 } else {
945 let trials = candidates
946 .into_iter()
947 .enumerate()
948 .map(|(index, layout)| CandidateTrial {
949 index,
950 layout,
951 base_seed,
952 })
953 .collect::<Vec<_>>();
954 let work_units = trials
955 .len()
956 .saturating_mul(
957 refinement_iterations
958 .saturating_mul(2)
959 .saturating_add(config.routing_trials),
960 )
961 .saturating_mul(sabre.graph.node_count());
962 let evaluate = |search: CandidateSearch, trial: CandidateTrial| {
963 let outcome = evaluate_candidate(
964 sabre,
965 forwards,
966 backwards,
967 target,
968 prepared_target,
969 analysis,
970 physical,
971 objective,
972 config,
973 refinement_iterations,
974 trial,
975 config.routing_trials,
976 )?;
977 search.merge(CandidateSearch::from_outcome(outcome), target)
978 };
979 let search = if let Some(chunk_size) =
980 parallel_chunk_size(trials.len(), work_units, LAYOUT_PARALLELISM_THRESHOLDS)
981 {
982 trials
983 .into_par_iter()
984 .chunks(chunk_size)
985 .map(|chunk| {
986 chunk
987 .into_iter()
988 .try_fold(CandidateSearch::default(), &evaluate)
989 })
990 .try_reduce(CandidateSearch::default, |left, right| {
991 left.merge(right, target)
992 })?
993 } else {
994 trials
995 .into_iter()
996 .try_fold(CandidateSearch::default(), evaluate)?
997 };
998 (search, generated_candidates)
999 };
1000
1001 let best = search.best.ok_or_else(|| {
1002 CompilerError::SabreRoutingFailed(SabreRoutingFailure::NoFeasibleLayoutCandidate {
1003 evaluated: candidates_evaluated,
1004 missing_terminal: search.missing_terminal,
1005 movement_unreachable: search.movement_unreachable,
1006 unsupported_native: search.unsupported_native,
1007 })
1008 })?;
1009 let swap_count = best.trial.swap_count();
1010 let is_perfect = is_perfect_layout(analysis, physical, &best.layout);
1011
1012 let mut notes = candidate_notes;
1013 notes.push(format!(
1014 "selected SABRE refined layout with {swap_count} final-route swaps"
1015 ));
1016 notes.push(
1017 "winner selected by predicted native 2Q count/depth and total depth; layout score is diagnostic"
1018 .to_string(),
1019 );
1020 let trial = if retain_provenance {
1021 best.trial.finish(target)?
1022 } else {
1023 best.trial.finish_without_provenance(target)?
1024 };
1025 Ok(PreparedSabreRouteSelection {
1026 initial_layout: best.layout,
1027 trial,
1028 selected_trial_index: best.route_index,
1029 trials_evaluated: search.trials_evaluated,
1030 score: best.score.clone(),
1031 diagnostics: LayoutDiagnostics {
1032 is_perfect,
1033 candidates_evaluated,
1034 used_fidelity: best.score.used_fidelity,
1035 notes,
1036 },
1037 })
1038}
1039
1040#[allow(clippy::too_many_arguments)]
1041fn evaluate_candidate(
1042 sabre: &SabreDag,
1043 forwards: &SabreDag,
1044 backwards: &SabreDag,
1045 target: &RoutingTarget,
1046 prepared_target: &PreparedSabreTarget,
1047 analysis: &CircuitLayoutAnalysis,
1048 physical: &PhysicalLayoutGraph,
1049 objective: &LayoutObjective,
1050 config: &SabreConfig,
1051 refinement_iterations: usize,
1052 trial: CandidateTrial,
1053 route_trials: usize,
1054) -> Result<CandidateOutcome<CandidateEvaluation>, CompilerError> {
1055 match interaction_reachability_for_target_with_metadata(
1056 sabre,
1057 target,
1058 &prepared_target.routing_metadata,
1059 &trial.layout,
1060 )? {
1061 InteractionReachability::Reachable => {}
1062 InteractionReachability::UnreachableUnary {
1063 cause: RequirementReachabilityFailure::NoExecutableTerminal,
1064 ..
1065 }
1066 | InteractionReachability::UnreachablePair {
1067 cause: RequirementReachabilityFailure::NoExecutableTerminal,
1068 ..
1069 } => {
1070 return Ok(CandidateOutcome::Infeasible(
1071 CandidateInfeasibleReason::MissingTerminal,
1072 0,
1073 ));
1074 }
1075 InteractionReachability::UnreachableUnary {
1076 cause: RequirementReachabilityFailure::MovementDisconnected,
1077 ..
1078 }
1079 | InteractionReachability::UnreachablePair {
1080 cause: RequirementReachabilityFailure::MovementDisconnected,
1081 ..
1082 } => {
1083 return Ok(CandidateOutcome::Infeasible(
1084 CandidateInfeasibleReason::MovementUnreachable,
1085 0,
1086 ));
1087 }
1088 }
1089 let initial_signature = layout_mapping_signature(&trial.layout, &analysis.logical_qubits)?;
1090 let mut seen_signatures = BTreeSet::from([initial_signature]);
1091 let mut refined = trial.layout;
1092 for iteration in 0..refinement_iterations {
1093 let forward_seed = derive_semantic_seed(
1094 trial.base_seed,
1095 1,
1096 trial.index,
1097 iteration,
1098 layout_signature(&refined),
1099 );
1100 let forward = match refine_layout_with_metadata(
1101 forwards,
1102 target,
1103 &prepared_target.refinement_metadata,
1104 &refined,
1105 &config.heuristic,
1106 forward_seed,
1107 ) {
1108 Ok(layout) => layout,
1109 Err(error) => return classify_candidate_error(error, 0),
1110 };
1111 let backward_seed = derive_semantic_seed(
1112 trial.base_seed,
1113 2,
1114 trial.index,
1115 iteration,
1116 layout_signature(&forward),
1117 );
1118 let next_refined = match refine_layout_with_metadata(
1119 backwards,
1120 target,
1121 &prepared_target.backward_refinement_metadata,
1122 &forward,
1123 &config.heuristic,
1124 backward_seed,
1125 ) {
1126 Ok(layout) => layout,
1127 Err(error) => return classify_candidate_error(error, 0),
1128 };
1129 let mapping_signature = layout_mapping_signature(&next_refined, &analysis.logical_qubits)?;
1130 if !seen_signatures.insert(mapping_signature) {
1131 break;
1132 }
1133 refined = next_refined;
1134 }
1135
1136 let mut best = None::<(usize, RankedTrial)>;
1137 for route_index in 0..route_trials {
1138 let seed = derive_semantic_seed(
1139 trial.base_seed,
1140 3,
1141 trial.index,
1142 route_index,
1143 layout_signature(&refined),
1144 );
1145 let mut routed = match route_ranked_trial_with_metadata(
1146 sabre,
1147 target,
1148 &prepared_target.routing_metadata,
1149 &refined,
1150 &config.heuristic,
1151 seed,
1152 ) {
1153 Ok(routed) => routed,
1154 Err(error) => return classify_candidate_error(error, route_index + 1),
1155 };
1156 let replace = if let Some((best_index, current)) = best.as_mut() {
1157 compare_ranked_trials(
1158 &mut routed,
1159 (trial.index, route_index),
1160 current,
1161 (trial.index, *best_index),
1162 target,
1163 )?
1164 .is_lt()
1165 } else {
1166 true
1167 };
1168 if replace {
1169 best = Some((route_index, routed));
1170 }
1171 }
1172 let (route_index, routed) = best.expect("route_trials is greater than zero");
1173 let score = objective.score_layout(analysis, physical, &refined)?;
1174 Ok(CandidateOutcome::Success(CandidateEvaluation {
1175 index: trial.index,
1176 route_index,
1177 trial: routed,
1178 layout: refined,
1179 score,
1180 trials_evaluated: route_trials,
1181 }))
1182}
1183
1184pub(crate) fn dense_interaction_path_skips_refinement(
1185 analysis: &CircuitLayoutAnalysis,
1186 physical: &PhysicalLayoutGraph,
1187) -> bool {
1188 let logical_count = analysis.logical_qubits.len();
1189 if logical_count < 4 {
1190 return false;
1191 }
1192 let possible_interactions = logical_count.saturating_mul(logical_count.saturating_sub(1)) / 2;
1193 if analysis.interactions.len() != possible_interactions {
1194 return false;
1195 }
1196 let (min_weight, max_weight) = analysis
1197 .interactions
1198 .interactions()
1199 .iter()
1200 .fold((f64::INFINITY, 0.0_f64), |(min, max), interaction| {
1201 (min.min(interaction.weight), max.max(interaction.weight))
1202 });
1203 if !min_weight.is_finite() || min_weight <= 0.0 || max_weight > min_weight * 4.0 {
1204 return false;
1205 }
1206
1207 let physical_count = physical.physical_qubits().len();
1208 if physical_count < 2 {
1209 return false;
1210 }
1211 let mut degree = vec![0usize; physical_count];
1212 let mut edge_count = 0usize;
1213 for (left, right) in physical.undirected_edges_by_index() {
1214 degree[left] += 1;
1215 degree[right] += 1;
1216 edge_count += 1;
1217 }
1218 let is_connected =
1219 (1..physical_count).all(|index| physical.distance_by_index(0, index).is_some());
1220 is_connected
1221 && edge_count + 1 == physical_count
1222 && degree.iter().filter(|value| **value == 1).count() == 2
1223 && degree.iter().all(|value| matches!(*value, 1 | 2))
1224}
1225
1226#[derive(Default)]
1227struct CandidateSearch {
1228 best: Option<CandidateEvaluation>,
1229 missing_terminal: usize,
1230 movement_unreachable: usize,
1231 unsupported_native: usize,
1232 trials_evaluated: usize,
1233}
1234
1235impl CandidateSearch {
1236 fn from_outcome(outcome: CandidateOutcome<CandidateEvaluation>) -> Self {
1237 let mut search = Self::default();
1238 match outcome {
1239 CandidateOutcome::Success(evaluation) => {
1240 search.trials_evaluated = evaluation.trials_evaluated();
1241 search.best = Some(evaluation);
1242 }
1243 CandidateOutcome::Infeasible(
1244 CandidateInfeasibleReason::MissingTerminal,
1245 trials_evaluated,
1246 ) => {
1247 search.trials_evaluated = trials_evaluated;
1248 search.missing_terminal = 1;
1249 }
1250 CandidateOutcome::Infeasible(
1251 CandidateInfeasibleReason::MovementUnreachable,
1252 trials_evaluated,
1253 ) => {
1254 search.trials_evaluated = trials_evaluated;
1255 search.movement_unreachable = 1;
1256 }
1257 CandidateOutcome::Infeasible(
1258 CandidateInfeasibleReason::UnsupportedNative,
1259 trials_evaluated,
1260 ) => {
1261 search.trials_evaluated = trials_evaluated;
1262 search.unsupported_native = 1;
1263 }
1264 }
1265 search
1266 }
1267
1268 fn merge(mut self, mut other: Self, target: &RoutingTarget) -> Result<Self, CompilerError> {
1269 self.missing_terminal = self.missing_terminal.saturating_add(other.missing_terminal);
1270 self.movement_unreachable = self
1271 .movement_unreachable
1272 .saturating_add(other.movement_unreachable);
1273 self.unsupported_native = self
1274 .unsupported_native
1275 .saturating_add(other.unsupported_native);
1276 self.trials_evaluated = self.trials_evaluated.saturating_add(other.trials_evaluated);
1277 if let Some(mut candidate) = other.best.take() {
1278 let replace = if let Some(current) = self.best.as_mut() {
1279 candidate.compare(current, target)?.is_lt()
1280 } else {
1281 true
1282 };
1283 if replace {
1284 self.best = Some(candidate);
1285 }
1286 }
1287 Ok(self)
1288 }
1289}
1290
1291impl CandidateEvaluation {
1292 fn compare(
1293 &mut self,
1294 other: &mut Self,
1295 target: &RoutingTarget,
1296 ) -> Result<std::cmp::Ordering, CompilerError> {
1297 compare_ranked_trials(
1298 &mut self.trial,
1299 (self.index, self.route_index),
1300 &mut other.trial,
1301 (other.index, other.route_index),
1302 target,
1303 )
1304 }
1305
1306 fn trials_evaluated(&self) -> usize {
1307 self.trials_evaluated
1308 }
1309}
1310
1311struct CandidateTrial {
1312 index: usize,
1314 layout: Layout,
1316 base_seed: u64,
1318}
1319
1320fn layout_mapping_signature(
1321 layout: &Layout,
1322 logical_qubits: &[LogicalQubit],
1323) -> Result<Vec<u32>, CompilerError> {
1324 logical_qubits
1325 .iter()
1326 .map(|logical| {
1327 layout
1328 .get_physical(*logical)
1329 .map(PhysicalQubit::id)
1330 .ok_or_else(|| {
1331 CompilerError::InvariantViolation(format!(
1332 "sabre layout does not map logical qubit {logical}"
1333 ))
1334 })
1335 })
1336 .collect()
1337}
1338
1339fn layout_signature(layout: &Layout) -> u64 {
1340 layout
1341 .l2p_map()
1342 .iter()
1343 .fold(0x6a09_e667_f3bc_c909, |hash, (logical, physical)| {
1344 splitmix64(hash ^ (u64::from(logical.id()) << 32) ^ u64::from(physical.id()))
1345 })
1346}
1347
1348fn derive_semantic_seed(
1349 base: u64,
1350 domain: u64,
1351 candidate_index: usize,
1352 step_index: usize,
1353 layout_signature: u64,
1354) -> u64 {
1355 splitmix64(
1356 base ^ domain.wrapping_mul(0x9e37_79b9_7f4a_7c15)
1357 ^ (candidate_index as u64).wrapping_mul(0xbf58_476d_1ce4_e5b9)
1358 ^ (step_index as u64).wrapping_mul(0x94d0_49bb_1331_11eb)
1359 ^ layout_signature,
1360 )
1361}
1362
1363fn splitmix64(mut value: u64) -> u64 {
1364 value = value.wrapping_add(0x9e37_79b9_7f4a_7c15);
1365 value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
1366 value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
1367 value ^ (value >> 31)
1368}
1369
1370enum CandidateOutcome<T> {
1371 Success(T),
1372 Infeasible(CandidateInfeasibleReason, usize),
1373}
1374
1375enum CandidateInfeasibleReason {
1376 MissingTerminal,
1377 MovementUnreachable,
1378 UnsupportedNative,
1379}
1380
1381fn classify_candidate_error<T>(
1382 error: CompilerError,
1383 trials_evaluated: usize,
1384) -> Result<CandidateOutcome<T>, CompilerError> {
1385 match error {
1386 CompilerError::SabreRoutingFailed(
1387 SabreRoutingFailure::NoExecutableUnaryTerminal { .. }
1388 | SabreRoutingFailure::NoExecutablePairTerminal { .. },
1389 ) => Ok(CandidateOutcome::Infeasible(
1390 CandidateInfeasibleReason::MissingTerminal,
1391 trials_evaluated,
1392 )),
1393 CompilerError::SabreRoutingFailed(
1394 SabreRoutingFailure::UnreachableUnaryPlacement { .. }
1395 | SabreRoutingFailure::UnreachablePairPlacement { .. },
1396 ) => Ok(CandidateOutcome::Infeasible(
1397 CandidateInfeasibleReason::MovementUnreachable,
1398 trials_evaluated,
1399 )),
1400 CompilerError::DeviceLoweringFailed(_) => Ok(CandidateOutcome::Infeasible(
1401 CandidateInfeasibleReason::UnsupportedNative,
1402 trials_evaluated,
1403 )),
1404 fatal => Err(fatal),
1405 }
1406}
1407
1408struct CandidateEvaluation {
1409 index: usize,
1411 route_index: usize,
1413 trial: RankedTrial,
1415 layout: Layout,
1417 score: LayoutScore,
1419 trials_evaluated: usize,
1421}
1422
1423fn validate_layout_config(config: &SabreConfig) -> Result<(), CompilerError> {
1429 if config.layout_trials == 0 {
1430 return Err(CompilerError::InvalidInput(
1431 "sabre layout_trials must be greater than zero".to_string(),
1432 ));
1433 }
1434 if config.layout_assignment_budget == 0 {
1435 return Err(CompilerError::InvalidInput(
1436 "sabre layout_assignment_budget must be greater than zero".to_string(),
1437 ));
1438 }
1439 if let Some(vf2) = config.vf2_prepass {
1440 if vf2.candidate_limit == 0 {
1441 return Err(CompilerError::InvalidInput(
1442 "sabre vf2_prepass candidate_limit must be greater than zero".to_string(),
1443 ));
1444 }
1445 if vf2.call_limit == 0 {
1446 return Err(CompilerError::InvalidInput(
1447 "sabre vf2_prepass call_limit must be greater than zero".to_string(),
1448 ));
1449 }
1450 }
1451 config.validate()
1452}
1453
1454struct InitialLayoutCandidates {
1455 layouts: Vec<Layout>,
1456 notes: Vec<String>,
1457}
1458
1459fn initial_layout_candidates(
1467 prepared: &PreparedSabreCircuit,
1468 prepared_target: &PreparedSabreTarget,
1469 objective: &LayoutObjective,
1470 config: &SabreConfig,
1471 rng: &mut StdRng,
1472) -> Result<InitialLayoutCandidates, CompilerError> {
1473 let analysis = &prepared.analysis;
1474 let sabre = &prepared.routing_dag;
1475 let physical = &prepared_target.physical;
1476 let target = &prepared_target.routing;
1477 let mut candidates = Vec::new();
1478 let mut notes = Vec::new();
1479 let logical_qubits = &analysis.logical_qubits;
1480 let assignment = match movement_component_assignment(
1481 sabre,
1482 target,
1483 logical_qubits,
1484 config.layout_assignment_budget,
1485 )? {
1486 ComponentAssignmentSearch::Found(assignment) => assignment,
1487 ComponentAssignmentSearch::ProvenInfeasible => {
1488 return Err(CompilerError::SabreRoutingFailed(
1489 SabreRoutingFailure::MovementAssignmentInfeasible,
1490 ));
1491 }
1492 ComponentAssignmentSearch::BudgetExhausted { expansions } => {
1493 return Err(CompilerError::SabreRoutingFailed(
1494 SabreRoutingFailure::MovementAssignmentBudgetExhausted {
1495 budget: config.layout_assignment_budget,
1496 expansions,
1497 },
1498 ));
1499 }
1500 };
1501
1502 candidates.push(movement_component_layout(
1505 logical_qubits,
1506 &assignment.components,
1507 &assignment.logical_components,
1508 target,
1509 false,
1510 None,
1511 )?);
1512 candidates.push(movement_component_layout(
1513 logical_qubits,
1514 &assignment.components,
1515 &assignment.logical_components,
1516 target,
1517 true,
1518 None,
1519 )?);
1520
1521 let structured_budget = config.layout_trials.min(6);
1522 let structured = interaction_aware_layouts(analysis, physical, structured_budget)?;
1523 for candidate in structured {
1524 if interaction_reachability_for_target_with_metadata(
1525 sabre,
1526 target,
1527 &prepared_target.routing_metadata,
1528 &candidate,
1529 )? == InteractionReachability::Reachable
1530 {
1531 candidates.push(candidate);
1532 }
1533 }
1534
1535 match greedy_layout_candidate_prepared(analysis, physical, objective)? {
1536 GreedyCandidateOutcome::Found(greedy) => {
1537 let greedy =
1538 normalize_initial_layout_for_target(logical_qubits, target, &greedy.layout)?;
1539 if interaction_reachability_for_target_with_metadata(
1540 sabre,
1541 target,
1542 &prepared_target.routing_metadata,
1543 &greedy,
1544 )?
1545 == InteractionReachability::Reachable
1546 {
1547 candidates.push(greedy);
1548 }
1549 }
1550 GreedyCandidateOutcome::Disconnected { left, right } => notes.push(format!(
1551 "SABRE greedy prepass candidate mapped an interaction across disconnected physical qubits {left} and {right}"
1552 )),
1553 }
1554
1555 if let Some(vf2_prepass) = config.vf2_prepass
1556 && vf2_is_promising(analysis, physical)
1557 {
1558 let vf2_config = Vf2LayoutConfig {
1559 candidate_limit: vf2_prepass.candidate_limit,
1560 call_limit: Some(vf2_prepass.call_limit),
1561 edge_requirement: Vf2EdgeRequirement::PositiveInteractions,
1562 };
1563 match try_vf2_perfect_layout_prepared(analysis, physical, objective, &vf2_config)? {
1564 Vf2PreparedOutcome::Found(vf2) => {
1565 let vf2 = normalize_initial_layout_for_target(logical_qubits, target, &vf2.layout)?;
1566 if interaction_reachability_for_target_with_metadata(
1567 sabre,
1568 target,
1569 &prepared_target.routing_metadata,
1570 &vf2,
1571 )? == InteractionReachability::Reachable
1572 {
1573 candidates.push(vf2);
1574 }
1575 }
1576 Vf2PreparedOutcome::NoCandidate => {
1577 notes.push("SABRE VF2 prepass found no perfect layout candidate".to_string());
1578 }
1579 Vf2PreparedOutcome::BudgetExhausted => notes.push(format!(
1580 "SABRE VF2 prepass exhausted its call budget of {}",
1581 vf2_prepass.call_limit
1582 )),
1583 }
1584 } else if config.vf2_prepass.is_some() {
1585 notes.push(
1586 "SABRE skipped VF2 because cheap graph bounds indicate an oversized or impossible exact embedding"
1587 .to_string(),
1588 );
1589 }
1590
1591 for _ in 0..config.layout_trials {
1594 candidates.push(movement_component_layout(
1595 logical_qubits,
1596 &assignment.components,
1597 &assignment.logical_components,
1598 target,
1599 false,
1600 Some(rng),
1601 )?);
1602 }
1603
1604 let candidates = deduplicate_layouts(candidates, logical_qubits)?;
1605 Ok(InitialLayoutCandidates {
1606 layouts: prune_layout_candidates(
1607 candidates,
1608 analysis,
1609 physical,
1610 logical_qubits,
1611 config.layout_trials,
1612 )?,
1613 notes,
1614 })
1615}
1616
1617fn vf2_is_promising(analysis: &CircuitLayoutAnalysis, physical: &PhysicalLayoutGraph) -> bool {
1618 let interactions = analysis
1619 .interactions
1620 .interactions()
1621 .iter()
1622 .filter(|interaction| interaction.weight > 0.0)
1623 .collect::<Vec<_>>();
1624 if interactions.is_empty() {
1625 return false;
1626 }
1627 let mut logical_degree = BTreeMap::<LogicalQubit, usize>::new();
1628 for interaction in &interactions {
1629 *logical_degree.entry(interaction.left).or_default() += 1;
1630 *logical_degree.entry(interaction.right).or_default() += 1;
1631 }
1632 let mut physical_degree = vec![0usize; physical.physical_qubits().len()];
1633 let physical_edges = physical
1634 .undirected_edges_by_index()
1635 .inspect(|(left, right)| {
1636 physical_degree[*left] += 1;
1637 physical_degree[*right] += 1;
1638 })
1639 .count();
1640 logical_degree.len() <= 40
1641 && interactions.len() <= physical_edges
1642 && logical_degree.values().copied().max().unwrap_or(0)
1643 <= physical_degree.into_iter().max().unwrap_or(0)
1644}
1645
1646fn movement_component_layout(
1647 logical_qubits: &[LogicalQubit],
1648 physical_components: &[Vec<PhysicalQubit>],
1649 logical_components: &[usize],
1650 target: &RoutingTarget,
1651 reverse: bool,
1652 mut rng: Option<&mut StdRng>,
1653) -> Result<Layout, CompilerError> {
1654 let mut mapping = BTreeMap::new();
1655 let mut used = BTreeSet::new();
1656 for (physical_index, physical_component) in physical_components.iter().enumerate() {
1657 let mut logical = logical_qubits
1658 .iter()
1659 .enumerate()
1660 .filter(|(index, _)| logical_components[*index] == physical_index)
1661 .map(|(_, logical)| *logical)
1662 .collect::<Vec<_>>();
1663 let mut physical = physical_component.clone();
1664 if let Some(rng) = rng.as_deref_mut() {
1665 logical.shuffle(rng);
1666 physical.shuffle(rng);
1667 } else if reverse {
1668 physical.reverse();
1669 }
1670 for (logical, physical) in logical.into_iter().zip(physical) {
1671 mapping.insert(logical, physical);
1672 used.insert(physical);
1673 }
1674 }
1675
1676 let mut idle = logical_qubits
1677 .iter()
1678 .copied()
1679 .filter(|logical| !mapping.contains_key(logical))
1680 .collect::<Vec<_>>();
1681 let mut remaining = target
1682 .physical_qubits
1683 .iter()
1684 .copied()
1685 .filter(|physical| !used.contains(physical))
1686 .collect::<Vec<_>>();
1687 if let Some(rng) = rng {
1688 idle.shuffle(rng);
1689 remaining.shuffle(rng);
1690 } else if reverse {
1691 remaining.reverse();
1692 }
1693 mapping.extend(idle.into_iter().zip(remaining));
1694
1695 Layout::new(
1696 logical_qubits.to_vec(),
1697 target.physical_qubits.clone(),
1698 Some(mapping),
1699 )
1700 .map_err(|error| {
1701 CompilerError::InvariantViolation(format!(
1702 "sabre layout failed to construct an initial candidate: {error}"
1703 ))
1704 })
1705}
1706
1707fn deduplicate_layouts(
1712 candidates: Vec<Layout>,
1713 logical_qubits: &[LogicalQubit],
1714) -> Result<Vec<Layout>, CompilerError> {
1715 let mut seen = BTreeSet::new();
1716 let mut unique = Vec::new();
1717 for layout in candidates {
1718 let signature = logical_qubits
1721 .iter()
1722 .map(|logical| {
1723 layout
1724 .get_physical(*logical)
1725 .map(PhysicalQubit::id)
1726 .ok_or_else(|| {
1727 CompilerError::InvariantViolation(format!(
1728 "sabre layout candidate does not map logical qubit {logical}"
1729 ))
1730 })
1731 })
1732 .collect::<Result<Vec<_>, CompilerError>>()?;
1733 if seen.insert(signature) {
1734 unique.push(layout);
1735 }
1736 }
1737 Ok(unique)
1738}
1739
1740fn prune_layout_candidates(
1741 candidates: Vec<Layout>,
1742 analysis: &CircuitLayoutAnalysis,
1743 physical: &PhysicalLayoutGraph,
1744 logical_qubits: &[LogicalQubit],
1745 limit: usize,
1746) -> Result<Vec<Layout>, CompilerError> {
1747 if candidates.len() <= limit {
1748 return Ok(candidates);
1749 }
1750 let mut scored = candidates
1751 .into_iter()
1752 .map(|layout| {
1753 let cost = interaction_layout_cost(analysis, physical, &layout)?;
1754 let mapping = logical_qubits
1755 .iter()
1756 .map(|logical| {
1757 layout
1758 .get_physical(*logical)
1759 .map(PhysicalQubit::id)
1760 .ok_or_else(|| {
1761 CompilerError::InvariantViolation(format!(
1762 "sabre layout candidate does not map logical qubit {logical}"
1763 ))
1764 })
1765 })
1766 .collect::<Result<Vec<_>, _>>()?;
1767 let mut subset = mapping.clone();
1768 subset.sort_unstable();
1769 Ok((cost, subset, mapping, layout))
1770 })
1771 .collect::<Result<Vec<_>, CompilerError>>()?;
1772 scored.sort_by(|left, right| {
1773 left.0
1774 .total_cmp(&right.0)
1775 .then_with(|| left.1.cmp(&right.1))
1776 .then_with(|| left.2.cmp(&right.2))
1777 });
1778
1779 let mut subset_counts = BTreeMap::<Vec<u32>, usize>::new();
1780 let mut selected = Vec::with_capacity(limit);
1781 let mut deferred = Vec::new();
1782 for candidate in scored {
1783 let count = subset_counts.entry(candidate.1.clone()).or_default();
1784 if *count < 3 && selected.len() < limit {
1785 *count += 1;
1786 selected.push(candidate.3);
1787 } else {
1788 deferred.push(candidate.3);
1789 }
1790 }
1791 selected.extend(
1792 deferred
1793 .into_iter()
1794 .take(limit.saturating_sub(selected.len())),
1795 );
1796 Ok(selected)
1797}