Skip to content

Commit

Permalink
Use rust gates for Optimize1QGatesDecomposition
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mtreinish committed Jun 24, 2024
1 parent 8b1f75f commit 4ac7782
Show file tree
Hide file tree
Showing 9 changed files with 470 additions and 141 deletions.
276 changes: 241 additions & 35 deletions crates/accelerate/src/euler_one_qubit_decomposer.rs

Large diffs are not rendered by default.

19 changes: 10 additions & 9 deletions crates/accelerate/src/two_qubit_decompose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ use rand_distr::StandardNormal;
use rand_pcg::Pcg64Mcg;

use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE};
use qiskit_circuit::operations::Operation;
use qiskit_circuit::SliceOrInt;

const PI2: f64 = PI / 2.0;
Expand Down Expand Up @@ -1097,7 +1098,7 @@ impl TwoQubitWeylDecomposition {
)
.unwrap();
for gate in c2r.gates {
gate_sequence.push((gate.0, gate.1, smallvec![0]))
gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![0]))
}
global_phase += c2r.global_phase;
let c2l = unitary_to_gate_sequence_inner(
Expand All @@ -1110,7 +1111,7 @@ impl TwoQubitWeylDecomposition {
)
.unwrap();
for gate in c2l.gates {
gate_sequence.push((gate.0, gate.1, smallvec![1]))
gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![1]))
}
global_phase += c2l.global_phase;
self.weyl_gate(
Expand All @@ -1129,7 +1130,7 @@ impl TwoQubitWeylDecomposition {
)
.unwrap();
for gate in c1r.gates {
gate_sequence.push((gate.0, gate.1, smallvec![0]))
gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![0]))
}
global_phase += c2r.global_phase;
let c1l = unitary_to_gate_sequence_inner(
Expand All @@ -1142,7 +1143,7 @@ impl TwoQubitWeylDecomposition {
)
.unwrap();
for gate in c1l.gates {
gate_sequence.push((gate.0, gate.1, smallvec![1]))
gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![1]))
}
Ok(TwoQubitGateSequence {
gates: gate_sequence,
Expand Down Expand Up @@ -1542,7 +1543,7 @@ impl TwoQubitBasisDecomposer {
if let Some(sequence) = sequence {
*global_phase += sequence.global_phase;
for gate in sequence.gates {
gates.push((gate.0, gate.1, smallvec![qubit]));
gates.push((gate.0.name().to_string(), gate.1, smallvec![qubit]));
}
}
}
Expand Down Expand Up @@ -1955,27 +1956,27 @@ impl TwoQubitBasisDecomposer {
for i in 0..best_nbasis as usize {
if let Some(euler_decomp) = &euler_decompositions[2 * i] {
for gate in &euler_decomp.gates {
gates.push((gate.0.clone(), gate.1.clone(), smallvec![0]));
gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![0]));
}
global_phase += euler_decomp.global_phase
}
if let Some(euler_decomp) = &euler_decompositions[2 * i + 1] {
for gate in &euler_decomp.gates {
gates.push((gate.0.clone(), gate.1.clone(), smallvec![1]));
gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![1]));
}
global_phase += euler_decomp.global_phase
}
gates.push((self.gate.clone(), smallvec![], smallvec![0, 1]));
}
if let Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize] {
for gate in &euler_decomp.gates {
gates.push((gate.0.clone(), gate.1.clone(), smallvec![0]));
gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![0]));
}
global_phase += euler_decomp.global_phase
}
if let Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize + 1] {
for gate in &euler_decomp.gates {
gates.push((gate.0.clone(), gate.1.clone(), smallvec![1]));
gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![1]));
}
global_phase += euler_decomp.global_phase
}
Expand Down
51 changes: 50 additions & 1 deletion crates/circuit/src/dag_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::circuit_instruction::{
use crate::operations::Operation;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PySequence, PyString, PyTuple};
use pyo3::{intern, PyObject, PyResult};
use pyo3::{intern, PyObject, PyResult, ToPyObject};

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

#[staticmethod]
fn from_instruction(
py: Python,
instruction: CircuitInstruction,
dag: Option<&Bound<PyAny>>,
) -> PyResult<PyObject> {
let qargs = instruction.qubits.clone_ref(py).into_bound(py);
let cargs = instruction.clbits.clone_ref(py).into_bound(py);

let sort_key = match dag {
Some(dag) => {
let cache = dag
.getattr(intern!(py, "_key_cache"))?
.downcast_into_exact::<PyDict>()?;
let cache_key = PyTuple::new_bound(py, [&qargs, &cargs]);
match cache.get_item(&cache_key)? {
Some(key) => key,
None => {
let indices: PyResult<Vec<_>> = qargs
.iter()
.chain(cargs.iter())
.map(|bit| {
dag.call_method1(intern!(py, "find_bit"), (bit,))?
.getattr(intern!(py, "index"))
})
.collect();
let index_strs: Vec<_> =
indices?.into_iter().map(|i| format!("{:04}", i)).collect();
let key = PyString::new_bound(py, index_strs.join(",").as_str());
cache.set_item(&cache_key, &key)?;
key.into_any()
}
}
}
None => qargs.str()?.into_any(),
};
let base = PyClassInitializer::from(DAGNode { _node_id: -1 });
let sub = base.add_subclass(DAGOpNode {
instruction,
sort_key: sort_key.unbind(),
});
Ok(Py::new(py, sub)?.to_object(py))
}

fn __reduce__(slf: PyRef<Self>, py: Python) -> PyResult<PyObject> {
let state = (slf.as_ref()._node_id, &slf.sort_key);
Ok((
Expand Down Expand Up @@ -229,6 +273,11 @@ impl DAGOpNode {
Ok(())
}

#[getter]
fn op_name(&self) -> &str {
self.instruction.operation.name()
}

/// Returns a representation of the DAGOpNode
fn __repr__(&self, py: Python) -> PyResult<String> {
Ok(format!(
Expand Down
11 changes: 11 additions & 0 deletions crates/circuit/src/gate_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,14 @@ pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] {
[c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)],
]
}

pub fn r_gate(theta: f64, phi: f64) -> [[Complex64; 2]; 2] {
let cos = (theta / 2.).cos();
let sin = (theta / 2.).sin();
let exp_m = c64(0., -phi).exp();
let exp_p = c64(0., phi).exp();
[
[c64(cos, 0.), c64(0., -1.) * exp_m * sin],
[c64(0., -1.) * exp_p * sin, c64(cos, 0.)],
]
}
63 changes: 58 additions & 5 deletions crates/circuit/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,18 @@ pub enum StandardGate {
RZXGate = 52,
}

impl ToPyObject for StandardGate {
fn to_object(&self, py: Python) -> PyObject {
self.into_py(py)
}
}

// TODO: replace all 34s (placeholders) with actual number
static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [
1, 1, 1, 2, 2, 2, 3, 1, 1, 1, // 0-9
2, 2, 1, 0, 1, 1, 1, 1, 1, 1, // 10-19
1, 1, 1, 2, 2, 2, 1, 1, 1, 34, // 20-29
34, 34, 34, 2, 2, 2, 2, 2, 3, 2, // 30-39
34, 34, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39
2, 2, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49
34, 34, 34, // 50-52
];
Expand All @@ -249,7 +255,7 @@ static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, // 0-9
0, 0, 0, 1, 0, 0, 1, 3, 0, 0, // 10-19
0, 0, 0, 0, 2, 2, 1, 2, 3, 34, // 20-29
34, 34, 34, 0, 1, 0, 0, 0, 0, 3, // 30-39
34, 34, 2, 0, 1, 0, 0, 0, 0, 3, // 30-39
1, 3, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49
34, 34, 34, // 50-52
];
Expand Down Expand Up @@ -514,7 +520,12 @@ impl Operation for StandardGate {
_ => None,
},
Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(),
Self::RGate => todo!(),
Self::RGate => match params {
[Param::Float(theta), Param::Float(phi)] => {
Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned())
}
_ => None,
},
Self::CHGate => todo!(),
Self::CPhaseGate => todo!(),
Self::CSGate => todo!(),
Expand Down Expand Up @@ -954,7 +965,23 @@ impl Operation for StandardGate {
)
}),
Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(),
Self::RGate => todo!(),
Self::RGate => Python::with_gil(|py| -> Option<CircuitData> {
let out_phi = subtract_param(&params[1], PI2, py);
let out_lam = add_param(&multiply_param(&params[1], -1., py), PI2, py);
Some(
CircuitData::from_standard_gates(
py,
1,
[(
Self::UGate,
smallvec![params[0].clone(), out_phi, out_lam],
smallvec![Qubit(0)],
)],
FLOAT_ZERO,
)
.expect("Unexpected Qiskit python bug"),
)
}),
Self::CHGate => todo!(),
Self::CPhaseGate => todo!(),
Self::CSGate => todo!(),
Expand Down Expand Up @@ -987,7 +1014,33 @@ fn multiply_param(param: &Param, mult: f64, py: Python) -> Param {
theta
.clone_ref(py)
.call_method1(py, intern!(py, "__rmul__"), (mult,))
.expect("Parameter expression for global phase failed"),
.expect("Parameter expression for multiplication failed"),
),
Param::Obj(_) => unreachable!(),
}
}

fn subtract_param(param: &Param, other: f64, py: Python) -> Param {
match param {
Param::Float(theta) => Param::Float(*theta - other),
Param::ParameterExpression(theta) => Param::ParameterExpression(
theta
.clone_ref(py)
.call_method1(py, intern!(py, "__sub__"), (other,))
.expect("Parameter expression for subtraction failed"),
),
Param::Obj(_) => unreachable!(),
}
}

fn add_param(param: &Param, other: f64, py: Python) -> Param {
match param {
Param::Float(theta) => Param::Float(*theta + other),
Param::ParameterExpression(theta) => Param::ParameterExpression(
theta
.clone_ref(py)
.call_method1(py, intern!(py, "__add__"), (other,))
.expect("Parameter expression for addition failed"),
),
Param::Obj(_) => unreachable!(),
}
Expand Down
3 changes: 3 additions & 0 deletions qiskit/circuit/library/standard_gates/r.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.parameterexpression import ParameterValueType
from qiskit._accelerate.circuit import StandardGate


class RGate(Gate):
Expand Down Expand Up @@ -49,6 +50,8 @@ class RGate(Gate):
\end{pmatrix}
"""

_standard_gate = StandardGate.RGate

def __init__(
self,
theta: ParameterValueType,
Expand Down
Loading

0 comments on commit 4ac7782

Please sign in to comment.