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

Reuse VF2 scoring views for all scoring #11115

Merged
merged 2 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions crates/accelerate/src/vf2_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use indexmap::IndexMap;

use numpy::PyReadonlyArray1;
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
Expand All @@ -22,21 +20,34 @@ use crate::nlayout::{NLayout, VirtualQubit};

const PARALLEL_THRESHOLD: usize = 50;

#[pyclass]
pub struct EdgeList {
pub edge_list: Vec<([VirtualQubit; 2], i32)>,
}

#[pymethods]
impl EdgeList {
#[new]
pub fn new(edge_list: Vec<([VirtualQubit; 2], i32)>) -> Self {
EdgeList { edge_list }
}
}

/// Score a given circuit with a layout applied
#[pyfunction]
#[pyo3(
text_signature = "(bit_list, edge_list, error_matrix, layout, strict_direction, run_in_parallel, /)"
)]
pub fn score_layout(
bit_list: PyReadonlyArray1<i32>,
edge_list: IndexMap<[VirtualQubit; 2], i32>,
edge_list: &EdgeList,
error_map: &ErrorMap,
layout: &NLayout,
strict_direction: bool,
run_in_parallel: bool,
) -> PyResult<f64> {
let bit_counts = bit_list.as_slice()?;
let edge_filter_map = |(index_arr, gate_count): (&[VirtualQubit; 2], &i32)| -> Option<f64> {
let edge_filter_map = |(index_arr, gate_count): &([VirtualQubit; 2], i32)| -> Option<f64> {
let mut error = error_map
.error_map
.get(&[index_arr[0].to_phys(layout), index_arr[1].to_phys(layout)]);
Expand Down Expand Up @@ -66,10 +77,18 @@ pub fn score_layout(
})
};

let mut fidelity: f64 = if edge_list.len() < PARALLEL_THRESHOLD || !run_in_parallel {
edge_list.iter().filter_map(edge_filter_map).product()
let mut fidelity: f64 = if edge_list.edge_list.len() < PARALLEL_THRESHOLD || !run_in_parallel {
edge_list
.edge_list
.iter()
.filter_map(edge_filter_map)
.product()
} else {
edge_list.par_iter().filter_map(edge_filter_map).product()
edge_list
.edge_list
.par_iter()
.filter_map(edge_filter_map)
.product()
};
fidelity *= if bit_list.len() < PARALLEL_THRESHOLD || !run_in_parallel {
bit_counts
Expand All @@ -90,5 +109,6 @@ pub fn score_layout(
#[pymodule]
pub fn vf2_layout(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(score_layout))?;
m.add_class::<EdgeList>()?;
Ok(())
}
4 changes: 4 additions & 0 deletions qiskit/transpiler/passes/layout/vf2_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ def run(self, dag):
self.property_set["VF2Layout_stop_reason"] = VF2LayoutStopReason.MORE_THAN_2Q
return
im_graph, im_graph_node_map, reverse_im_graph_node_map, free_nodes = result
scoring_edge_list = vf2_utils.build_edge_list(im_graph)
scoring_bit_list = vf2_utils.build_bit_list(im_graph, im_graph_node_map)
cm_graph, cm_nodes = vf2_utils.shuffle_coupling_graph(
self.coupling_map, self.seed, self.strict_direction
)
Expand Down Expand Up @@ -199,6 +201,8 @@ def mapping_to_layout(layout_mapping):
reverse_im_graph_node_map,
im_graph,
self.strict_direction,
edge_list=scoring_edge_list,
bit_list=scoring_bit_list,
)
# If the layout score is 0 we can't do any better and we'll just
# waste time finding additional mappings that will at best match
Expand Down
11 changes: 10 additions & 1 deletion qiskit/transpiler/passes/layout/vf2_post_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ def run(self, dag):
self.property_set["VF2PostLayout_stop_reason"] = VF2PostLayoutStopReason.MORE_THAN_2Q
return
im_graph, im_graph_node_map, reverse_im_graph_node_map, free_nodes = result
scoring_bit_list = vf2_utils.build_bit_list(im_graph, im_graph_node_map)
scoring_edge_list = vf2_utils.build_edge_list(im_graph)

if self.target is not None:
# If qargs is None then target is global and ideal so no
Expand Down Expand Up @@ -256,7 +258,10 @@ def run(self, dag):
if self.strict_direction:
initial_layout = Layout({bit: index for index, bit in enumerate(dag.qubits)})
chosen_layout_score = self._score_layout(
initial_layout, im_graph_node_map, reverse_im_graph_node_map, im_graph
initial_layout,
im_graph_node_map,
reverse_im_graph_node_map,
im_graph,
)
else:
initial_layout = {
Expand All @@ -271,6 +276,8 @@ def run(self, dag):
reverse_im_graph_node_map,
im_graph,
self.strict_direction,
edge_list=scoring_edge_list,
bit_list=scoring_bit_list,
)
# Circuit not in basis so we have nothing to compare against return here
except KeyError:
Expand Down Expand Up @@ -303,6 +310,8 @@ def run(self, dag):
reverse_im_graph_node_map,
im_graph,
self.strict_direction,
edge_list=scoring_edge_list,
bit_list=scoring_bit_list,
)
logger.debug("Trial %s has score %s", trials, layout_score)
if layout_score < chosen_layout_score:
Expand Down
39 changes: 27 additions & 12 deletions qiskit/transpiler/passes/layout/vf2_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,27 @@ def _visit(dag, weight, wire_map):
return im_graph, im_graph_node_map, reverse_im_graph_node_map, free_nodes


def build_edge_list(im_graph):
"""Generate an edge list for scoring."""
return vf2_layout.EdgeList(
[((edge[0], edge[1]), sum(edge[2].values())) for edge in im_graph.edge_index_map().values()]
)


def build_bit_list(im_graph, bit_map):
"""Generate a bit list for scoring."""
bit_list = np.zeros(len(im_graph), dtype=np.int32)
for node_index in bit_map.values():
try:
bit_list[node_index] = sum(im_graph[node_index].values())
# If node_index not in im_graph that means there was a standalone
# node we will score/sort separately outside the vf2 mapping, so we
# can skip the hole
except IndexError:
pass
return bit_list


def score_layout(
avg_error_map,
layout_mapping,
Expand All @@ -103,25 +124,19 @@ def score_layout(
im_graph,
strict_direction=False,
run_in_parallel=False,
edge_list=None,
bit_list=None,
):
"""Score a layout given an average error map."""
if layout_mapping:
size = max(max(layout_mapping), max(layout_mapping.values()))
else:
size = 0
nlayout = NLayout(layout_mapping, size + 1, size + 1)
bit_list = np.zeros(len(im_graph), dtype=np.int32)
for node_index in bit_map.values():
try:
bit_list[node_index] = sum(im_graph[node_index].values())
# If node_index not in im_graph that means there was a standalone
# node we will score/sort separately outside the vf2 mapping, so we
# can skip the hole
except IndexError:
pass
edge_list = {
(edge[0], edge[1]): sum(edge[2].values()) for edge in im_graph.edge_index_map().values()
}
if bit_list is None:
bit_list = build_bit_list(im_graph, bit_map)
if edge_list is None:
edge_list = build_edge_list(im_graph)
return vf2_layout.score_layout(
bit_list, edge_list, avg_error_map, nlayout, strict_direction, run_in_parallel
)
Expand Down