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