Skip to content

Commit dbc4506

Browse files
jakelishmanmtreinishkevinhartman
authored
Fix virtual/physical-qubit distinction in Sabre (#10712)
* Fix virtual/physical-qubit distinction in Sabre `SabreLayout` in particular previously had a very confused notion of virtual and physical qubits in Rust space; on each iteration, it rewrote the `SabreDAG` to "apply" a layout. This is not logically what `SabreDAG` represents, though; that defines the circuit purely in terms of virtual qubits, which by definition do not change as the mapping of them to physical qubits changes. This commit modifies that internal logic so that there is a clear distinction between the virtual DAG and the changing layout. This significantly simplies the logic within the Sabre layout Rust component. This commit is RNG-compatible with its parent. The Python entry points to the Rust components of both layout and routing are modified to return a final permutation, rather than a final layout, as this is what `TranspileLayout.final_layout` actually is. The application of the Sabre result to a DAG is also updated to reflect the correct virtual/physical relationship. With the roles of everything clarified, this now means that in the Sabre layout case, it automatically does the job of `ApplyLayout` _and_ the subsequent swap-mapping in one iteration, rather than rewriting the DAG once with `ApplyLayout` only to rewrite it immediately after with the swap-mapped form; the initial layout is after all only valid as far as the first inserted swap. The Sabre routing inputs are slightly tweaked, so the clone of the layout that the routing pass mutates to track its state as it progresses happens internally. _Technically_, there could have been situations where it was preferable for the caller in Rust-space to give the output object (if it didn't care about losing the initial layout), but in practice, we always cloned on entry. The cost of the layout clone is not high even in the worst case, and this makes the ownership, mutation and return-value interfaces clearer. * Fix out-of-date types Co-authored-by: Matthew Treinish <[email protected]> Co-authored-by: Kevin Hartman <[email protected]> * Improve efficiency of interfaces --------- Co-authored-by: Matthew Treinish <[email protected]> Co-authored-by: Kevin Hartman <[email protected]>
1 parent 1bce4bb commit dbc4506

File tree

5 files changed

+328
-368
lines changed

5 files changed

+328
-368
lines changed

crates/accelerate/src/sabre_layout.rs

+45-86
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
#![allow(clippy::too_many_arguments)]
1313

1414
use ndarray::prelude::*;
15-
use numpy::IntoPyArray;
16-
use numpy::PyReadonlyArray2;
15+
use numpy::{IntoPyArray, PyArray, PyReadonlyArray2};
1716
use pyo3::prelude::*;
1817
use pyo3::wrap_pyfunction;
1918
use pyo3::Python;
@@ -39,7 +38,7 @@ pub fn sabre_layout_and_routing(
3938
num_swap_trials: usize,
4039
num_layout_trials: usize,
4140
seed: Option<u64>,
42-
) -> ([NLayout; 2], (SwapMap, PyObject, NodeBlockResults)) {
41+
) -> (NLayout, PyObject, (SwapMap, PyObject, NodeBlockResults)) {
4342
let run_in_parallel = getenv_use_multiple_threads();
4443
let outer_rng = match seed {
4544
Some(seed) => Pcg64Mcg::seed_from_u64(seed),
@@ -69,7 +68,7 @@ pub fn sabre_layout_and_routing(
6968
),
7069
)
7170
})
72-
.min_by_key(|(index, (_, result))| {
71+
.min_by_key(|(index, (_, _, result))| {
7372
(
7473
result.map.map.values().map(|x| x.len()).sum::<usize>(),
7574
*index,
@@ -92,15 +91,16 @@ pub fn sabre_layout_and_routing(
9291
run_in_parallel,
9392
)
9493
})
95-
.min_by_key(|(_, result)| result.map.map.values().map(|x| x.len()).sum::<usize>())
94+
.min_by_key(|(_, _, result)| result.map.map.values().map(|x| x.len()).sum::<usize>())
9695
.unwrap()
9796
};
9897
(
9998
res.0,
99+
PyArray::from_vec(py, res.1).into(),
100100
(
101-
res.1.map,
102-
res.1.node_order.into_pyarray(py).into(),
103-
res.1.node_block_results,
101+
res.2.map,
102+
res.2.node_order.into_pyarray(py).into(),
103+
res.2.node_block_results,
104104
),
105105
)
106106
}
@@ -114,113 +114,72 @@ fn layout_trial(
114114
max_iterations: usize,
115115
num_swap_trials: usize,
116116
run_swap_in_parallel: bool,
117-
) -> ([NLayout; 2], SabreResult) {
118-
// Pick a random initial layout and fully populate ancillas in that layout too
117+
) -> (NLayout, Vec<usize>, SabreResult) {
119118
let num_physical_qubits = distance_matrix.shape()[0];
120119
let mut rng = Pcg64Mcg::seed_from_u64(seed);
121-
let mut physical_qubits: Vec<usize> = (0..num_physical_qubits).collect();
122-
physical_qubits.shuffle(&mut rng);
123-
let mut initial_layout = NLayout::from_logical_to_physical(physical_qubits);
124-
let new_dag_fn = |nodes| {
125-
// Because the current implementation of Sabre swap doesn't permute
126-
// the layout when placing control flow ops, there's no need to
127-
// recurse into blocks. We remove them here, but still map control
128-
// flow node IDs to an empty block list so Sabre treats these ops
129-
// as control flow nodes, but doesn't route their blocks.
130-
let node_blocks_empty = dag
120+
121+
// Pick a random initial layout including a full ancilla allocation.
122+
let mut initial_layout = {
123+
let mut physical_qubits: Vec<usize> = (0..num_physical_qubits).collect();
124+
physical_qubits.shuffle(&mut rng);
125+
NLayout::from_logical_to_physical(physical_qubits)
126+
};
127+
128+
// Sabre routing currently enforces that control-flow blocks return to their starting layout,
129+
// which means they don't actually affect any heuristics that affect our layout choice.
130+
let dag_no_control_forward = SabreDAG {
131+
num_qubits: dag.num_qubits,
132+
num_clbits: dag.num_clbits,
133+
dag: dag.dag.clone(),
134+
nodes: dag.nodes.clone(),
135+
first_layer: dag.first_layer.clone(),
136+
node_blocks: dag
131137
.node_blocks
132-
.iter()
133-
.map(|(node_index, _)| (*node_index, Vec::with_capacity(0)));
134-
SabreDAG::new(
135-
dag.num_qubits,
136-
dag.num_clbits,
137-
nodes,
138-
node_blocks_empty.collect(),
139-
)
140-
.unwrap()
138+
.keys()
139+
.map(|index| (*index, Vec::new()))
140+
.collect(),
141141
};
142+
let dag_no_control_reverse = SabreDAG::new(
143+
dag_no_control_forward.num_qubits,
144+
dag_no_control_forward.num_clbits,
145+
dag_no_control_forward.nodes.iter().rev().cloned().collect(),
146+
dag_no_control_forward.node_blocks.clone(),
147+
);
142148

143-
// Create forward and reverse dags (without node blocks).
144-
// Once we've settled on a layout, we recursively apply it to the original
145-
// DAG and its node blocks.
146-
let mut dag_forward: SabreDAG = new_dag_fn(dag.nodes.clone());
147-
let mut dag_reverse: SabreDAG = new_dag_fn(dag.nodes.iter().rev().cloned().collect());
148149
for _iter in 0..max_iterations {
149-
// forward and reverse
150-
for _direction in 0..2 {
151-
let layout_dag = apply_layout(&dag_forward, &initial_layout);
152-
let mut pass_final_layout = NLayout::generate_trivial_layout(num_physical_qubits);
153-
build_swap_map_inner(
150+
for dag in [&dag_no_control_forward, &dag_no_control_reverse] {
151+
let (_result, final_layout) = build_swap_map_inner(
154152
num_physical_qubits,
155-
&layout_dag,
153+
dag,
156154
neighbor_table,
157155
distance_matrix,
158156
heuristic,
159157
Some(seed),
160-
&mut pass_final_layout,
158+
&initial_layout,
161159
num_swap_trials,
162160
Some(run_swap_in_parallel),
163161
);
164-
let final_layout = compose_layout(&initial_layout, &pass_final_layout);
165162
initial_layout = final_layout;
166-
std::mem::swap(&mut dag_forward, &mut dag_reverse);
167163
}
168164
}
169165

170-
// Apply the layout to the original DAG.
171-
let layout_dag = apply_layout(dag, &initial_layout);
172-
173-
let mut final_layout = NLayout::generate_trivial_layout(num_physical_qubits);
174-
let sabre_result = build_swap_map_inner(
166+
let (sabre_result, final_layout) = build_swap_map_inner(
175167
num_physical_qubits,
176-
&layout_dag,
168+
dag,
177169
neighbor_table,
178170
distance_matrix,
179171
heuristic,
180172
Some(seed),
181-
&mut final_layout,
173+
&initial_layout,
182174
num_swap_trials,
183175
Some(run_swap_in_parallel),
184176
);
185-
([initial_layout, final_layout], sabre_result)
186-
}
187-
188-
fn apply_layout(dag: &SabreDAG, layout: &NLayout) -> SabreDAG {
189-
let layout_nodes = dag.nodes.iter().map(|(node_index, qargs, cargs)| {
190-
let new_qargs: Vec<usize> = qargs.iter().map(|n| layout.logic_to_phys[*n]).collect();
191-
(*node_index, new_qargs, cargs.clone())
192-
});
193-
let node_blocks = dag.node_blocks.iter().map(|(node_index, blocks)| {
194-
(
195-
*node_index,
196-
blocks.iter().map(|d| apply_layout(d, layout)).collect(),
197-
)
198-
});
199-
SabreDAG::new(
200-
dag.num_qubits,
201-
dag.num_clbits,
202-
layout_nodes.collect(),
203-
node_blocks.collect(),
204-
)
205-
.unwrap()
206-
}
207-
208-
fn compose_layout(initial_layout: &NLayout, final_layout: &NLayout) -> NLayout {
209-
let logic_to_phys = initial_layout
210-
.logic_to_phys
211-
.iter()
212-
.map(|n| final_layout.logic_to_phys[*n])
213-
.collect();
214-
let phys_to_logic = final_layout
177+
let final_permutation = initial_layout
215178
.phys_to_logic
216179
.iter()
217-
.map(|n| initial_layout.phys_to_logic[*n])
180+
.map(|initial| final_layout.logic_to_phys[*initial])
218181
.collect();
219-
220-
NLayout {
221-
logic_to_phys,
222-
phys_to_logic,
223-
}
182+
(initial_layout, final_permutation, sabre_result)
224183
}
225184

226185
#[pymodule]

0 commit comments

Comments
 (0)