Skip to content

Commit 4ac7782

Browse files
committed
Use rust gates for Optimize1QGatesDecomposition
This commit moves to using rust gates for the Optimize1QGatesDecomposition transpiler pass. It takes in a sequence of runs (which are a list of DAGOpNodes) from the python side of the transpiler pass which are generated from DAGCircuit.collect_1q_runs() (which in the future should be moved to rust after Qiskit#12550 merges). The rust portion of the pass now iterates over each run, performs the matrix multiplication to compute the unitary of the run, then synthesizes that unitary, computes the estimated error of the circuit synthesis and returns a tuple of the circuit sequence in terms of rust StandardGate enums. The python portion of the code then takes those sequences and does inplace substitution of each run with the sequence returned from rust. Once Qiskit#12550 merges we should be able to move the input collect_1q_runs() call and perform the output node substitions in rust making the full pass execute in the rust domain without any python interaction. Additionally, the OneQubitEulerDecomposer class is updated to use rust for circuit generation instead of doing this python side. The internal changes done to use rust gates in the transpiler pass meant we were half way to this already by emitting rust StandardGates instead of python gate objects. The dag handling is still done in Python however until Qiskit#12550 merges. This also includes an implementation of the r gate, I temporarily added this to unblock this effort as it was the only gate missing needed to complete this. We can rebase this if a standalone implementation of the gate merges before this.
1 parent 8b1f75f commit 4ac7782

File tree

9 files changed

+470
-141
lines changed

9 files changed

+470
-141
lines changed

crates/accelerate/src/euler_one_qubit_decomposer.rs

+241-35
Large diffs are not rendered by default.

crates/accelerate/src/two_qubit_decompose.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ use rand_distr::StandardNormal;
5252
use rand_pcg::Pcg64Mcg;
5353

5454
use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE};
55+
use qiskit_circuit::operations::Operation;
5556
use qiskit_circuit::SliceOrInt;
5657

5758
const PI2: f64 = PI / 2.0;
@@ -1097,7 +1098,7 @@ impl TwoQubitWeylDecomposition {
10971098
)
10981099
.unwrap();
10991100
for gate in c2r.gates {
1100-
gate_sequence.push((gate.0, gate.1, smallvec![0]))
1101+
gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![0]))
11011102
}
11021103
global_phase += c2r.global_phase;
11031104
let c2l = unitary_to_gate_sequence_inner(
@@ -1110,7 +1111,7 @@ impl TwoQubitWeylDecomposition {
11101111
)
11111112
.unwrap();
11121113
for gate in c2l.gates {
1113-
gate_sequence.push((gate.0, gate.1, smallvec![1]))
1114+
gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![1]))
11141115
}
11151116
global_phase += c2l.global_phase;
11161117
self.weyl_gate(
@@ -1129,7 +1130,7 @@ impl TwoQubitWeylDecomposition {
11291130
)
11301131
.unwrap();
11311132
for gate in c1r.gates {
1132-
gate_sequence.push((gate.0, gate.1, smallvec![0]))
1133+
gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![0]))
11331134
}
11341135
global_phase += c2r.global_phase;
11351136
let c1l = unitary_to_gate_sequence_inner(
@@ -1142,7 +1143,7 @@ impl TwoQubitWeylDecomposition {
11421143
)
11431144
.unwrap();
11441145
for gate in c1l.gates {
1145-
gate_sequence.push((gate.0, gate.1, smallvec![1]))
1146+
gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![1]))
11461147
}
11471148
Ok(TwoQubitGateSequence {
11481149
gates: gate_sequence,
@@ -1542,7 +1543,7 @@ impl TwoQubitBasisDecomposer {
15421543
if let Some(sequence) = sequence {
15431544
*global_phase += sequence.global_phase;
15441545
for gate in sequence.gates {
1545-
gates.push((gate.0, gate.1, smallvec![qubit]));
1546+
gates.push((gate.0.name().to_string(), gate.1, smallvec![qubit]));
15461547
}
15471548
}
15481549
}
@@ -1955,27 +1956,27 @@ impl TwoQubitBasisDecomposer {
19551956
for i in 0..best_nbasis as usize {
19561957
if let Some(euler_decomp) = &euler_decompositions[2 * i] {
19571958
for gate in &euler_decomp.gates {
1958-
gates.push((gate.0.clone(), gate.1.clone(), smallvec![0]));
1959+
gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![0]));
19591960
}
19601961
global_phase += euler_decomp.global_phase
19611962
}
19621963
if let Some(euler_decomp) = &euler_decompositions[2 * i + 1] {
19631964
for gate in &euler_decomp.gates {
1964-
gates.push((gate.0.clone(), gate.1.clone(), smallvec![1]));
1965+
gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![1]));
19651966
}
19661967
global_phase += euler_decomp.global_phase
19671968
}
19681969
gates.push((self.gate.clone(), smallvec![], smallvec![0, 1]));
19691970
}
19701971
if let Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize] {
19711972
for gate in &euler_decomp.gates {
1972-
gates.push((gate.0.clone(), gate.1.clone(), smallvec![0]));
1973+
gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![0]));
19731974
}
19741975
global_phase += euler_decomp.global_phase
19751976
}
19761977
if let Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize + 1] {
19771978
for gate in &euler_decomp.gates {
1978-
gates.push((gate.0.clone(), gate.1.clone(), smallvec![1]));
1979+
gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![1]));
19791980
}
19801981
global_phase += euler_decomp.global_phase
19811982
}

crates/circuit/src/dag_node.rs

+50-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::circuit_instruction::{
1717
use crate::operations::Operation;
1818
use pyo3::prelude::*;
1919
use pyo3::types::{PyDict, PyList, PySequence, PyString, PyTuple};
20-
use pyo3::{intern, PyObject, PyResult};
20+
use pyo3::{intern, PyObject, PyResult, ToPyObject};
2121

2222
/// Parent class for DAGOpNode, DAGInNode, and DAGOutNode.
2323
#[pyclass(module = "qiskit._accelerate.circuit", subclass)]
@@ -144,6 +144,50 @@ impl DAGOpNode {
144144
))
145145
}
146146

147+
#[staticmethod]
148+
fn from_instruction(
149+
py: Python,
150+
instruction: CircuitInstruction,
151+
dag: Option<&Bound<PyAny>>,
152+
) -> PyResult<PyObject> {
153+
let qargs = instruction.qubits.clone_ref(py).into_bound(py);
154+
let cargs = instruction.clbits.clone_ref(py).into_bound(py);
155+
156+
let sort_key = match dag {
157+
Some(dag) => {
158+
let cache = dag
159+
.getattr(intern!(py, "_key_cache"))?
160+
.downcast_into_exact::<PyDict>()?;
161+
let cache_key = PyTuple::new_bound(py, [&qargs, &cargs]);
162+
match cache.get_item(&cache_key)? {
163+
Some(key) => key,
164+
None => {
165+
let indices: PyResult<Vec<_>> = qargs
166+
.iter()
167+
.chain(cargs.iter())
168+
.map(|bit| {
169+
dag.call_method1(intern!(py, "find_bit"), (bit,))?
170+
.getattr(intern!(py, "index"))
171+
})
172+
.collect();
173+
let index_strs: Vec<_> =
174+
indices?.into_iter().map(|i| format!("{:04}", i)).collect();
175+
let key = PyString::new_bound(py, index_strs.join(",").as_str());
176+
cache.set_item(&cache_key, &key)?;
177+
key.into_any()
178+
}
179+
}
180+
}
181+
None => qargs.str()?.into_any(),
182+
};
183+
let base = PyClassInitializer::from(DAGNode { _node_id: -1 });
184+
let sub = base.add_subclass(DAGOpNode {
185+
instruction,
186+
sort_key: sort_key.unbind(),
187+
});
188+
Ok(Py::new(py, sub)?.to_object(py))
189+
}
190+
147191
fn __reduce__(slf: PyRef<Self>, py: Python) -> PyResult<PyObject> {
148192
let state = (slf.as_ref()._node_id, &slf.sort_key);
149193
Ok((
@@ -229,6 +273,11 @@ impl DAGOpNode {
229273
Ok(())
230274
}
231275

276+
#[getter]
277+
fn op_name(&self) -> &str {
278+
self.instruction.operation.name()
279+
}
280+
232281
/// Returns a representation of the DAGOpNode
233282
fn __repr__(&self, py: Python) -> PyResult<String> {
234283
Ok(format!(

crates/circuit/src/gate_matrix.rs

+11
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,14 @@ pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] {
324324
[c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)],
325325
]
326326
}
327+
328+
pub fn r_gate(theta: f64, phi: f64) -> [[Complex64; 2]; 2] {
329+
let cos = (theta / 2.).cos();
330+
let sin = (theta / 2.).sin();
331+
let exp_m = c64(0., -phi).exp();
332+
let exp_p = c64(0., phi).exp();
333+
[
334+
[c64(cos, 0.), c64(0., -1.) * exp_m * sin],
335+
[c64(0., -1.) * exp_p * sin, c64(cos, 0.)],
336+
]
337+
}

crates/circuit/src/operations.rs

+58-5
Original file line numberDiff line numberDiff line change
@@ -234,12 +234,18 @@ pub enum StandardGate {
234234
RZXGate = 52,
235235
}
236236

237+
impl ToPyObject for StandardGate {
238+
fn to_object(&self, py: Python) -> PyObject {
239+
self.into_py(py)
240+
}
241+
}
242+
237243
// TODO: replace all 34s (placeholders) with actual number
238244
static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [
239245
1, 1, 1, 2, 2, 2, 3, 1, 1, 1, // 0-9
240246
2, 2, 1, 0, 1, 1, 1, 1, 1, 1, // 10-19
241247
1, 1, 1, 2, 2, 2, 1, 1, 1, 34, // 20-29
242-
34, 34, 34, 2, 2, 2, 2, 2, 3, 2, // 30-39
248+
34, 34, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39
243249
2, 2, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49
244250
34, 34, 34, // 50-52
245251
];
@@ -249,7 +255,7 @@ static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [
249255
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, // 0-9
250256
0, 0, 0, 1, 0, 0, 1, 3, 0, 0, // 10-19
251257
0, 0, 0, 0, 2, 2, 1, 2, 3, 34, // 20-29
252-
34, 34, 34, 0, 1, 0, 0, 0, 0, 3, // 30-39
258+
34, 34, 2, 0, 1, 0, 0, 0, 0, 3, // 30-39
253259
1, 3, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49
254260
34, 34, 34, // 50-52
255261
];
@@ -514,7 +520,12 @@ impl Operation for StandardGate {
514520
_ => None,
515521
},
516522
Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(),
517-
Self::RGate => todo!(),
523+
Self::RGate => match params {
524+
[Param::Float(theta), Param::Float(phi)] => {
525+
Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned())
526+
}
527+
_ => None,
528+
},
518529
Self::CHGate => todo!(),
519530
Self::CPhaseGate => todo!(),
520531
Self::CSGate => todo!(),
@@ -954,7 +965,23 @@ impl Operation for StandardGate {
954965
)
955966
}),
956967
Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(),
957-
Self::RGate => todo!(),
968+
Self::RGate => Python::with_gil(|py| -> Option<CircuitData> {
969+
let out_phi = subtract_param(&params[1], PI2, py);
970+
let out_lam = add_param(&multiply_param(&params[1], -1., py), PI2, py);
971+
Some(
972+
CircuitData::from_standard_gates(
973+
py,
974+
1,
975+
[(
976+
Self::UGate,
977+
smallvec![params[0].clone(), out_phi, out_lam],
978+
smallvec![Qubit(0)],
979+
)],
980+
FLOAT_ZERO,
981+
)
982+
.expect("Unexpected Qiskit python bug"),
983+
)
984+
}),
958985
Self::CHGate => todo!(),
959986
Self::CPhaseGate => todo!(),
960987
Self::CSGate => todo!(),
@@ -987,7 +1014,33 @@ fn multiply_param(param: &Param, mult: f64, py: Python) -> Param {
9871014
theta
9881015
.clone_ref(py)
9891016
.call_method1(py, intern!(py, "__rmul__"), (mult,))
990-
.expect("Parameter expression for global phase failed"),
1017+
.expect("Parameter expression for multiplication failed"),
1018+
),
1019+
Param::Obj(_) => unreachable!(),
1020+
}
1021+
}
1022+
1023+
fn subtract_param(param: &Param, other: f64, py: Python) -> Param {
1024+
match param {
1025+
Param::Float(theta) => Param::Float(*theta - other),
1026+
Param::ParameterExpression(theta) => Param::ParameterExpression(
1027+
theta
1028+
.clone_ref(py)
1029+
.call_method1(py, intern!(py, "__sub__"), (other,))
1030+
.expect("Parameter expression for subtraction failed"),
1031+
),
1032+
Param::Obj(_) => unreachable!(),
1033+
}
1034+
}
1035+
1036+
fn add_param(param: &Param, other: f64, py: Python) -> Param {
1037+
match param {
1038+
Param::Float(theta) => Param::Float(*theta + other),
1039+
Param::ParameterExpression(theta) => Param::ParameterExpression(
1040+
theta
1041+
.clone_ref(py)
1042+
.call_method1(py, intern!(py, "__add__"), (other,))
1043+
.expect("Parameter expression for addition failed"),
9911044
),
9921045
Param::Obj(_) => unreachable!(),
9931046
}

qiskit/circuit/library/standard_gates/r.py

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from qiskit.circuit.gate import Gate
2121
from qiskit.circuit.quantumregister import QuantumRegister
2222
from qiskit.circuit.parameterexpression import ParameterValueType
23+
from qiskit._accelerate.circuit import StandardGate
2324

2425

2526
class RGate(Gate):
@@ -49,6 +50,8 @@ class RGate(Gate):
4950
\end{pmatrix}
5051
"""
5152

53+
_standard_gate = StandardGate.RGate
54+
5255
def __init__(
5356
self,
5457
theta: ParameterValueType,

0 commit comments

Comments
 (0)