Skip to content

Commit 7dae02a

Browse files
authored
Merge branch 'main' into oxidize-commutative-cancellation
2 parents d919028 + 2ef371a commit 7dae02a

23 files changed

+933
-289
lines changed

Cargo.lock

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ license = "Apache-2.0"
1414
#
1515
# Each crate can add on specific features freely as it inherits.
1616
[workspace.dependencies]
17-
bytemuck = "1.17"
17+
bytemuck = "1.18"
1818
indexmap.version = "2.5.0"
1919
hashbrown.version = "0.14.5"
2020
num-bigint = "0.4"

crates/accelerate/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ num-traits = "0.2"
2020
num-complex.workspace = true
2121
rustworkx-core.workspace = true
2222
num-bigint.workspace = true
23-
faer = "0.19.2"
23+
faer = "0.19.3"
2424
itertools.workspace = true
2525
qiskit-circuit.workspace = true
2626
thiserror.workspace = true

crates/accelerate/src/check_map.rs

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2024
4+
//
5+
// This code is licensed under the Apache License, Version 2.0. You may
6+
// obtain a copy of this license in the LICENSE.txt file in the root directory
7+
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
//
9+
// Any modifications or derivative works of this code must retain this
10+
// copyright notice, and modified files need to carry a notice indicating
11+
// that they have been altered from the originals.
12+
13+
use hashbrown::HashSet;
14+
use pyo3::intern;
15+
use pyo3::prelude::*;
16+
use pyo3::wrap_pyfunction;
17+
18+
use qiskit_circuit::circuit_data::CircuitData;
19+
use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType};
20+
use qiskit_circuit::imports::CIRCUIT_TO_DAG;
21+
use qiskit_circuit::operations::{Operation, OperationRef};
22+
use qiskit_circuit::Qubit;
23+
24+
fn recurse<'py>(
25+
py: Python<'py>,
26+
dag: &'py DAGCircuit,
27+
edge_set: &'py HashSet<[u32; 2]>,
28+
wire_map: Option<&'py [Qubit]>,
29+
) -> PyResult<Option<(String, [u32; 2])>> {
30+
let check_qubits = |qubits: &[Qubit]| -> bool {
31+
match wire_map {
32+
Some(wire_map) => {
33+
let mapped_bits = [
34+
wire_map[qubits[0].0 as usize],
35+
wire_map[qubits[1].0 as usize],
36+
];
37+
edge_set.contains(&[mapped_bits[0].into(), mapped_bits[1].into()])
38+
}
39+
None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]),
40+
}
41+
};
42+
for node in dag.op_nodes(false) {
43+
if let NodeType::Operation(inst) = &dag.dag[node] {
44+
let qubits = dag.get_qargs(inst.qubits);
45+
if inst.op.control_flow() {
46+
if let OperationRef::Instruction(py_inst) = inst.op.view() {
47+
let raw_blocks = py_inst.instruction.getattr(py, "blocks")?;
48+
let circuit_to_dag = CIRCUIT_TO_DAG.get_bound(py);
49+
for raw_block in raw_blocks.bind(py).iter().unwrap() {
50+
let block_obj = raw_block?;
51+
let block = block_obj
52+
.getattr(intern!(py, "_data"))?
53+
.downcast::<CircuitData>()?
54+
.borrow();
55+
let new_dag: DAGCircuit =
56+
circuit_to_dag.call1((block_obj.clone(),))?.extract()?;
57+
let wire_map = (0..block.num_qubits())
58+
.map(|inner| {
59+
let outer = qubits[inner];
60+
match wire_map {
61+
Some(wire_map) => wire_map[outer.0 as usize],
62+
None => outer,
63+
}
64+
})
65+
.collect::<Vec<_>>();
66+
let res = recurse(py, &new_dag, edge_set, Some(&wire_map))?;
67+
if res.is_some() {
68+
return Ok(res);
69+
}
70+
}
71+
}
72+
} else if qubits.len() == 2
73+
&& (dag.calibrations_empty() || !dag.has_calibration_for_index(py, node)?)
74+
&& !check_qubits(qubits)
75+
{
76+
return Ok(Some((
77+
inst.op.name().to_string(),
78+
[qubits[0].0, qubits[1].0],
79+
)));
80+
}
81+
}
82+
}
83+
Ok(None)
84+
}
85+
86+
#[pyfunction]
87+
pub fn check_map(
88+
py: Python,
89+
dag: &DAGCircuit,
90+
edge_set: HashSet<[u32; 2]>,
91+
) -> PyResult<Option<(String, [u32; 2])>> {
92+
recurse(py, dag, &edge_set, None)
93+
}
94+
95+
pub fn check_map_mod(m: &Bound<PyModule>) -> PyResult<()> {
96+
m.add_wrapped(wrap_pyfunction!(check_map))?;
97+
Ok(())
98+
}
+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2024
4+
//
5+
// This code is licensed under the Apache License, Version 2.0. You may
6+
// obtain a copy of this license in the LICENSE.txt file in the root directory
7+
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
//
9+
// Any modifications or derivative works of this code must retain this
10+
// copyright notice, and modified files need to carry a notice indicating
11+
// that they have been altered from the originals.
12+
13+
use crate::nlayout::PhysicalQubit;
14+
use crate::target_transpiler::Target;
15+
use hashbrown::HashSet;
16+
use pyo3::prelude::*;
17+
use qiskit_circuit::imports;
18+
use qiskit_circuit::operations::OperationRef;
19+
use qiskit_circuit::{
20+
dag_circuit::{DAGCircuit, NodeType},
21+
operations::Operation,
22+
packed_instruction::PackedInstruction,
23+
Qubit,
24+
};
25+
use smallvec::smallvec;
26+
27+
/// Check if the two-qubit gates follow the right direction with respect to the coupling map.
28+
///
29+
/// Args:
30+
/// dag: the DAGCircuit to analyze
31+
///
32+
/// coupling_edges: set of edge pairs representing a directed coupling map, against which gate directionality is checked
33+
///
34+
/// Returns:
35+
/// true iff all two-qubit gates comply with the coupling constraints
36+
#[pyfunction]
37+
#[pyo3(name = "check_gate_direction_coupling")]
38+
fn py_check_with_coupling_map(
39+
py: Python,
40+
dag: &DAGCircuit,
41+
coupling_edges: HashSet<[Qubit; 2]>,
42+
) -> PyResult<bool> {
43+
let coupling_map_check =
44+
|_: &PackedInstruction, op_args: &[Qubit]| -> bool { coupling_edges.contains(op_args) };
45+
46+
check_gate_direction(py, dag, &coupling_map_check, None)
47+
}
48+
49+
/// Check if the two-qubit gates follow the right direction with respect to instructions supported in the given target.
50+
///
51+
/// Args:
52+
/// dag: the DAGCircuit to analyze
53+
///
54+
/// target: the Target against which gate directionality compliance is checked
55+
///
56+
/// Returns:
57+
/// true iff all two-qubit gates comply with the target's coupling constraints
58+
#[pyfunction]
59+
#[pyo3(name = "check_gate_direction_target")]
60+
fn py_check_with_target(py: Python, dag: &DAGCircuit, target: &Target) -> PyResult<bool> {
61+
let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool {
62+
let qargs = smallvec![
63+
PhysicalQubit::new(op_args[0].0),
64+
PhysicalQubit::new(op_args[1].0)
65+
];
66+
67+
target.instruction_supported(inst.op.name(), Some(&qargs))
68+
};
69+
70+
check_gate_direction(py, dag, &target_check, None)
71+
}
72+
73+
// The main routine for checking gate directionality.
74+
//
75+
// gate_complies: a function returning true iff the two-qubit gate direction complies with directionality constraints
76+
//
77+
// qubit_mapping: used for mapping the index of a given qubit within an instruction qargs vector to the corresponding qubit index of the
78+
// original DAGCircuit the pass was called with. This mapping is required since control flow blocks are represented by nested DAGCircuit
79+
// objects whose instruction qubit indices are relative to the parent DAGCircuit they reside in, thus when we recurse into nested DAGs, we need
80+
// to carry the mapping context relative to the original DAG.
81+
// When qubit_mapping is None, the identity mapping is assumed
82+
fn check_gate_direction<T>(
83+
py: Python,
84+
dag: &DAGCircuit,
85+
gate_complies: &T,
86+
qubit_mapping: Option<&[Qubit]>,
87+
) -> PyResult<bool>
88+
where
89+
T: Fn(&PackedInstruction, &[Qubit]) -> bool,
90+
{
91+
for node in dag.op_nodes(false) {
92+
let NodeType::Operation(packed_inst) = &dag.dag[node] else {
93+
panic!("PackedInstruction is expected");
94+
};
95+
96+
let inst_qargs = dag.get_qargs(packed_inst.qubits);
97+
98+
if let OperationRef::Instruction(py_inst) = packed_inst.op.view() {
99+
if py_inst.control_flow() {
100+
let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); // TODO: Take out of the recursion
101+
let py_inst = py_inst.instruction.bind(py);
102+
103+
for block in py_inst.getattr("blocks")?.iter()? {
104+
let inner_dag: DAGCircuit = circuit_to_dag.call1((block?,))?.extract()?;
105+
106+
let block_ok = if let Some(mapping) = qubit_mapping {
107+
let mapping = inst_qargs // Create a temp mapping for the recursive call
108+
.iter()
109+
.map(|q| mapping[q.0 as usize])
110+
.collect::<Vec<Qubit>>();
111+
112+
check_gate_direction(py, &inner_dag, gate_complies, Some(&mapping))?
113+
} else {
114+
check_gate_direction(py, &inner_dag, gate_complies, Some(inst_qargs))?
115+
};
116+
117+
if !block_ok {
118+
return Ok(false);
119+
}
120+
}
121+
continue;
122+
}
123+
}
124+
125+
if inst_qargs.len() == 2
126+
&& !match qubit_mapping {
127+
// Check gate direction based either on a given custom mapping or the identity mapping
128+
Some(mapping) => gate_complies(
129+
packed_inst,
130+
&[
131+
mapping[inst_qargs[0].0 as usize],
132+
mapping[inst_qargs[1].0 as usize],
133+
],
134+
),
135+
None => gate_complies(packed_inst, inst_qargs),
136+
}
137+
{
138+
return Ok(false);
139+
}
140+
}
141+
142+
Ok(true)
143+
}
144+
145+
#[pymodule]
146+
pub fn gate_direction(m: &Bound<PyModule>) -> PyResult<()> {
147+
m.add_wrapped(wrap_pyfunction!(py_check_with_coupling_map))?;
148+
m.add_wrapped(wrap_pyfunction!(py_check_with_target))?;
149+
Ok(())
150+
}

0 commit comments

Comments
 (0)