Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Sabre by default for optimization levels 1 and 2 #8552

Merged
merged 12 commits into from
Sep 29, 2022
Merged
20 changes: 11 additions & 9 deletions qiskit/transpiler/passes/routing/sabre_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,7 @@ def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, t
if coupling_map is not None:
self._neighbor_table = NeighborTable(retworkx.adjacency_matrix(self.coupling_map.graph))

if heuristic == "basic":
self.heuristic = Heuristic.Basic
elif heuristic == "lookahead":
self.heuristic = Heuristic.Lookahead
elif heuristic == "decay":
self.heuristic = Heuristic.Decay
else:
raise TranspilerError("Heuristic %s not recognized." % heuristic)
self.heuristic = heuristic

if seed is None:
ii32 = np.iinfo(np.int32)
Expand Down Expand Up @@ -191,6 +184,15 @@ def run(self, dag):
if len(dag.qubits) > self.coupling_map.size():
raise TranspilerError("More virtual qubits exist than physical.")

if self.heuristic == "basic":
heuristic = Heuristic.Basic
elif self.heuristic == "lookahead":
heuristic = Heuristic.Lookahead
elif self.heuristic == "decay":
heuristic = Heuristic.Decay
else:
raise TranspilerError("Heuristic %s not recognized." % self.heuristic)

self.dist_matrix = self.coupling_map.distance_matrix

# Preserve input DAG's name, regs, wire_map, etc. but replace the graph.
Expand Down Expand Up @@ -229,7 +231,7 @@ def run(self, dag):
sabre_dag,
self._neighbor_table,
self.dist_matrix,
self.heuristic,
heuristic,
self.seed,
layout,
self.trials,
Expand Down
2 changes: 1 addition & 1 deletion qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
)
if optimization_level == 1:
routing_pass = SabreSwap(
coupling_map, heuristic="lookahead", seed=seed_transpiler, trials=5
coupling_map, heuristic="decay", seed=seed_transpiler, trials=5
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this here, because the additional cost for the more thorough heuristic is basically zero over lookahead and in theory should provide better results. This way makes the optimization levels all run the same heuristic and just differ with the number of trials.

)
return common.generate_routing_passmanager(
routing_pass,
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
inst_map = pass_manager_config.inst_map
coupling_map = pass_manager_config.coupling_map
initial_layout = pass_manager_config.initial_layout
layout_method = pass_manager_config.layout_method or "dense"
init_method = pass_manager_config.init_method
routing_method = pass_manager_config.routing_method or "stochastic"
layout_method = pass_manager_config.layout_method or "sabre"
routing_method = pass_manager_config.routing_method or "sabre"
translation_method = pass_manager_config.translation_method or "translator"
optimization_method = pass_manager_config.optimization_method
scheduling_method = pass_manager_config.scheduling_method
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
coupling_map = pass_manager_config.coupling_map
initial_layout = pass_manager_config.initial_layout
init_method = pass_manager_config.init_method
layout_method = pass_manager_config.layout_method or "dense"
routing_method = pass_manager_config.routing_method or "stochastic"
layout_method = pass_manager_config.layout_method or "sabre"
routing_method = pass_manager_config.routing_method or "sabre"
translation_method = pass_manager_config.translation_method or "translator"
optimization_method = pass_manager_config.optimization_method
scheduling_method = pass_manager_config.scheduling_method
Expand Down
17 changes: 17 additions & 0 deletions releasenotes/notes/sabres-for-everyone-3148ccf2064ccb0d.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
upgrade:
- |
The preset pass managers for levels 1 and 2, which will be used when
``optimization_level=1`` or ``optimization_level=2`` with
:func:`~.transpile` or :func:`~.generate_preset_pass_manager` and output
from :func:`~.level_1_pass_manager` and :func:`~.level_2_pass_manager`,
will now use :class:`~.SabreLayout` and :func:`~SabreSwap` by default
instead of the previous defaults :class:`~.DenseLayout` and
:class:`~.StochasticSwap`. This change was made to improve the output
quality of the transpiler, the :class:`~.SabreLayout` and
:func:`~SabreSwap` combination typically results in fewer
:class:`~.SwapGate` objects being inserted into the output circuit.
If you would like to use the previous default passes you can set
``layout_method='dense'`` and ``routing_method='stochastic'`` on
:func:`~.transpile` or :func:`~.generate_preset_pass_manager to
leverage :class:`~.DenseLayout` and :class:`~.StochasticSwap` respectively.
72 changes: 41 additions & 31 deletions src/sabre_swap/neighbor_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,38 +36,48 @@ pub struct NeighborTable {
#[pymethods]
impl NeighborTable {
#[new]
pub fn new(adjacency_matrix: PyReadonlyArray2<f64>) -> Self {
let adj_mat = adjacency_matrix.as_array();
pub fn new(adjacency_matrix: Option<PyReadonlyArray2<f64>>) -> Self {
let run_in_parallel = getenv_use_multiple_threads();
let build_neighbors = |row: ArrayView1<f64>| -> Vec<usize> {
row.iter()
.enumerate()
.filter_map(
|(row_index, value)| {
if *value == 0. {
None
} else {
Some(row_index)
}
},
)
.collect()
};
if run_in_parallel {
NeighborTable {
neighbors: adj_mat
.axis_iter(Axis(0))
.into_par_iter()
.map(|row| build_neighbors(row))
.collect(),
}
} else {
NeighborTable {
neighbors: adj_mat
.axis_iter(Axis(0))
.map(|row| build_neighbors(row))
.collect(),
let neighbors = match adjacency_matrix {
Some(adjacency_matrix) => {
let adj_mat = adjacency_matrix.as_array();
let build_neighbors = |row: ArrayView1<f64>| -> Vec<usize> {
row.iter()
.enumerate()
.filter_map(
|(row_index, value)| {
if *value == 0. {
None
} else {
Some(row_index)
}
},
)
.collect()
};
if run_in_parallel {
adj_mat
.axis_iter(Axis(0))
.into_par_iter()
.map(|row| build_neighbors(row))
.collect()
} else {
adj_mat
.axis_iter(Axis(0))
.map(|row| build_neighbors(row))
.collect()
}
}
}
None => Vec::new(),
};
NeighborTable { neighbors }
}

fn __getstate__(&self) -> Vec<Vec<usize>> {
self.neighbors.clone()
}

fn __setstate__(&mut self, state: Vec<Vec<usize>>) {
self.neighbors = state
}
}
2 changes: 1 addition & 1 deletion test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ def test_move_measurements(self):
circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm"))

lay = [0, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6]
out = transpile(circ, initial_layout=lay, coupling_map=cmap)
out = transpile(circ, initial_layout=lay, coupling_map=cmap, routing_method="stochastic")
out_dag = circuit_to_dag(out)
meas_nodes = out_dag.named_nodes("measure")
for meas_node in meas_nodes:
Expand Down
27 changes: 2 additions & 25 deletions test/python/transpiler/test_preset_passmanagers.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,29 +701,6 @@ def test_layout_tokyo_fully_connected_cx(self, level):
19: ancilla[14],
}

dense_layout = {
11: qr[0],
6: qr[1],
5: qr[2],
10: qr[3],
15: qr[4],
0: ancilla[0],
1: ancilla[1],
2: ancilla[2],
3: ancilla[3],
4: ancilla[4],
7: ancilla[5],
8: ancilla[6],
9: ancilla[7],
12: ancilla[8],
13: ancilla[9],
14: ancilla[10],
16: ancilla[11],
17: ancilla[12],
18: ancilla[13],
19: ancilla[14],
}

sabre_layout = {
11: qr[0],
17: qr[1],
Expand All @@ -748,8 +725,8 @@ def test_layout_tokyo_fully_connected_cx(self, level):
}

expected_layout_level0 = trivial_layout
expected_layout_level1 = dense_layout
expected_layout_level2 = dense_layout
expected_layout_level1 = sabre_layout
expected_layout_level2 = sabre_layout
expected_layout_level3 = sabre_layout

expected_layouts = [
Expand Down