Skip to content

Commit

Permalink
Add support for running the TwoQubitControlledUDecomposer
Browse files Browse the repository at this point in the history
Since Qiskit#13139 merged we have another two qubit decomposer available to
run in rust, the TwoQubitControlledUDecomposer. This commit updates the
new TwoQubitPeepholeOptimization to call this decomposer if the target
supports appropriate 2q gates.
  • Loading branch information
mtreinish committed Dec 1, 2024
1 parent decee9a commit 746758f
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 29 deletions.
44 changes: 22 additions & 22 deletions crates/accelerate/src/two_qubit_decompose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2436,23 +2436,23 @@ pub enum RXXEquivalent {
}

impl RXXEquivalent {
fn matrix(&self, py: Python, param: f64) -> PyResult<Array2<Complex64>> {
fn matrix(&self, param: f64) -> PyResult<Array2<Complex64>> {
match self {
Self::Standard(gate) => Ok(gate.matrix(&[Param::Float(param)]).unwrap()),
Self::CustomPython(gate_cls) => {
Self::CustomPython(gate_cls) => Python::with_gil(|py: Python| {
let gate_obj = gate_cls.bind(py).call1((param,))?;
let raw_matrix = gate_obj
.call_method0(intern!(py, "to_matrix"))?
.extract::<PyReadonlyArray2<Complex64>>()?;
Ok(raw_matrix.as_array().to_owned())
}
}),
}
}
}

#[pyclass(module = "qiskit._accelerate.two_qubit_decompose", subclass)]
pub struct TwoQubitControlledUDecomposer {
rxx_equivalent_gate: RXXEquivalent,
pub rxx_equivalent_gate: RXXEquivalent,
#[pyo3(get)]
scale: f64,
}
Expand All @@ -2467,7 +2467,6 @@ impl TwoQubitControlledUDecomposer {
/// invert 2q gate sequence
fn invert_2q_gate(
&self,
py: Python,
gate: (Option<StandardGate>, SmallVec<[f64; 3]>, SmallVec<[u8; 2]>),
) -> PyResult<InverseReturn> {
let (gate, params, qubits) = gate;
Expand Down Expand Up @@ -2504,7 +2503,7 @@ impl TwoQubitControlledUDecomposer {
.collect::<SmallVec<_>>();
Ok((Some(inv_gate.0), inv_gate_params, qubits))
}
RXXEquivalent::CustomPython(gate_cls) => {
RXXEquivalent::CustomPython(gate_cls) => Python::with_gil(|py: Python| {
let gate_obj = gate_cls.bind(py).call1(PyTuple::new_bound(py, params))?;
let raw_inverse = gate_obj.call_method0(intern!(py, "inverse"))?;
let inverse: OperationFromPython = raw_inverse.extract()?;
Expand All @@ -2525,7 +2524,7 @@ impl TwoQubitControlledUDecomposer {
"rxx gate inverse is not valid for this decomposer",
))
}
}
}),
}
}
}
Expand All @@ -2538,14 +2537,14 @@ impl TwoQubitControlledUDecomposer {
/// Circuit: Circuit equivalent to an RXXGate.
/// Raises:
/// QiskitError: If the circuit is not equivalent to an RXXGate.
fn to_rxx_gate(&self, py: Python, angle: f64) -> PyResult<TwoQubitGateSequence> {
fn to_rxx_gate(&self, angle: f64) -> PyResult<TwoQubitGateSequence> {
// The user-provided RXXGate equivalent gate may be locally equivalent to the RXXGate
// but with some scaling in the rotation angle. For example, RXXGate(angle) has Weyl
// parameters (angle, 0, 0) for angle in [0, pi/2] but the user provided gate, i.e.
// :code:`self.rxx_equivalent_gate(angle)` might produce the Weyl parameters
// (scale * angle, 0, 0) where scale != 1. This is the case for the CPhaseGate.

let mat = self.rxx_equivalent_gate.matrix(py, self.scale * angle)?;
let mat = self.rxx_equivalent_gate.matrix(self.scale * angle)?;
let decomposer_inv =
TwoQubitWeylDecomposition::new_inner(mat.view(), Some(DEFAULT_FIDELITY), None)?;

Expand Down Expand Up @@ -2611,18 +2610,17 @@ impl TwoQubitControlledUDecomposer {
/// Appends U_d(a, b, c) to the circuit.
fn weyl_gate(
&self,
py: Python,
circ: &mut TwoQubitGateSequence,
target_decomposed: TwoQubitWeylDecomposition,
atol: f64,
) -> PyResult<()> {
let circ_a = self.to_rxx_gate(py, -2.0 * target_decomposed.a)?;
let circ_a = self.to_rxx_gate(-2.0 * target_decomposed.a)?;
circ.gates.extend(circ_a.gates);
let mut global_phase = circ_a.global_phase;

// translate the RYYGate(b) into a circuit based on the desired Ctrl-U gate.
if (target_decomposed.b).abs() > atol {
let circ_b = self.to_rxx_gate(py, -2.0 * target_decomposed.b)?;
let circ_b = self.to_rxx_gate(-2.0 * target_decomposed.b)?;
global_phase += circ_b.global_phase;
circ.gates
.push((Some(StandardGate::SdgGate), smallvec![], smallvec![0]));
Expand All @@ -2644,7 +2642,7 @@ impl TwoQubitControlledUDecomposer {
// circuit if c < 0.
let mut gamma = -2.0 * target_decomposed.c;
if gamma <= 0.0 {
let circ_c = self.to_rxx_gate(py, gamma)?;
let circ_c = self.to_rxx_gate(gamma)?;
global_phase += circ_c.global_phase;
circ.gates
.push((Some(StandardGate::HGate), smallvec![], smallvec![0]));
Expand All @@ -2658,15 +2656,15 @@ impl TwoQubitControlledUDecomposer {
} else {
// invert the circuit above
gamma *= -1.0;
let circ_c = self.to_rxx_gate(py, gamma)?;
let circ_c = self.to_rxx_gate(gamma)?;
global_phase -= circ_c.global_phase;
circ.gates
.push((Some(StandardGate::HGate), smallvec![], smallvec![0]));
circ.gates
.push((Some(StandardGate::HGate), smallvec![], smallvec![1]));
for gate in circ_c.gates.into_iter().rev() {
let (inv_gate_name, inv_gate_params, inv_gate_qubits) =
self.invert_2q_gate(py, gate)?;
self.invert_2q_gate(gate)?;
circ.gates
.push((inv_gate_name, inv_gate_params, inv_gate_qubits));
}
Expand All @@ -2683,9 +2681,8 @@ impl TwoQubitControlledUDecomposer {

/// Returns the Weyl decomposition in circuit form.
/// Note: atol is passed to OneQubitEulerDecomposer.
fn call_inner(
pub fn call_inner(
&self,
py: Python,
unitary: ArrayView2<Complex64>,
atol: f64,
) -> PyResult<TwoQubitGateSequence> {
Expand Down Expand Up @@ -2729,7 +2726,7 @@ impl TwoQubitControlledUDecomposer {
gates,
global_phase,
};
self.weyl_gate(py, &mut gates1, target_decomposed, atol)?;
self.weyl_gate(&mut gates1, target_decomposed, atol)?;
global_phase += gates1.global_phase;

if let Some(unitary_c1r) = unitary_c1r {
Expand Down Expand Up @@ -2760,7 +2757,7 @@ impl TwoQubitControlledUDecomposer {
/// QiskitError: If the gate is not locally equivalent to an :class:`.RXXGate`.
#[new]
#[pyo3(signature=(rxx_equivalent_gate))]
pub fn new(py: Python, rxx_equivalent_gate: RXXEquivalent) -> PyResult<Self> {
pub fn new(rxx_equivalent_gate: RXXEquivalent) -> PyResult<Self> {
let atol = DEFAULT_ATOL;
let test_angles = [0.2, 0.3, PI2];

Expand All @@ -2776,14 +2773,17 @@ impl TwoQubitControlledUDecomposer {
}
}
RXXEquivalent::CustomPython(gate_cls) => {
if gate_cls.bind(py).call1((test_angle,)).ok().is_none() {
let takes_param = Python::with_gil(|py: Python| {
gate_cls.bind(py).call1((test_angle,)).ok().is_none()
});
if takes_param {
return Err(QiskitError::new_err(
"Equivalent gate needs to take exactly 1 angle parameter.",
));
}
}
};
let mat = rxx_equivalent_gate.matrix(py, test_angle)?;
let mat = rxx_equivalent_gate.matrix(test_angle)?;
let decomp =
TwoQubitWeylDecomposition::new_inner(mat.view(), Some(DEFAULT_FIDELITY), None)?;
let mat_rxx = StandardGate::RXXGate
Expand Down Expand Up @@ -2834,7 +2834,7 @@ impl TwoQubitControlledUDecomposer {
unitary: PyReadonlyArray2<Complex64>,
atol: f64,
) -> PyResult<CircuitData> {
let sequence = self.call_inner(py, unitary.as_array(), atol)?;
let sequence = self.call_inner(unitary.as_array(), atol)?;
match &self.rxx_equivalent_gate {
RXXEquivalent::Standard(rxx_gate) => CircuitData::from_standard_gates(
py,
Expand Down
45 changes: 38 additions & 7 deletions crates/accelerate/src/two_qubit_peephole.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,20 @@ use crate::euler_one_qubit_decomposer::{
};
use crate::nlayout::PhysicalQubit;
use crate::target_transpiler::Target;
use crate::two_qubit_decompose::{TwoQubitBasisDecomposer, TwoQubitGateSequence};
use crate::two_qubit_decompose::{
RXXEquivalent, TwoQubitBasisDecomposer, TwoQubitControlledUDecomposer, TwoQubitGateSequence,
};

enum TwoQubitDecomposer {
Basis(TwoQubitBasisDecomposer),
ControlledU(TwoQubitControlledUDecomposer),
}

fn get_decomposers_from_target(
target: &Target,
qubits: &[Qubit],
fidelity: f64,
) -> PyResult<Vec<TwoQubitBasisDecomposer>> {
) -> PyResult<Vec<TwoQubitDecomposer>> {
let physical_qubits = smallvec![PhysicalQubit(qubits[0].0), PhysicalQubit(qubits[1].0)];
let gate_names = match target.operation_names_for_qargs(Some(&physical_qubits)) {
Ok(names) => names,
Expand Down Expand Up @@ -94,7 +101,7 @@ fn get_decomposers_from_target(

let euler_bases: Vec<EulerBasis> = target_basis_set.get_bases().collect();

available_kak_gate
let decomposers: PyResult<Vec<TwoQubitDecomposer>> = available_kak_gate
.iter()
.filter_map(|(two_qubit_name, two_qubit_gate, params)| {
let matrix = two_qubit_gate.matrix(params);
Expand All @@ -107,11 +114,26 @@ fn get_decomposers_from_target(
*euler_basis,
None,
)
.map(TwoQubitDecomposer::Basis)
})
})
})
.flatten()
.collect()
.collect();
let mut decomposers = decomposers?;
for gate in [
StandardGate::RXXGate,
StandardGate::RZZGate,
StandardGate::RYYGate,
StandardGate::RZXGate,
] {
if gate_names.contains(gate.name()) {
decomposers.push(TwoQubitDecomposer::ControlledU(
TwoQubitControlledUDecomposer::new(RXXEquivalent::Standard(gate))?,
));
}
}
Ok(decomposers)
}

#[inline]
Expand Down Expand Up @@ -229,13 +251,22 @@ pub(crate) fn two_qubit_unitary_peephole_optimize(

let sequence = decomposers
.iter()
.map(|decomposer| {
(
.map(|decomposer| match decomposer {
TwoQubitDecomposer::Basis(decomposer) => (
decomposer
.call_inner(matrix.view(), None, true, None)
.unwrap(),
decomposer.gate_name().to_string(),
)
),
TwoQubitDecomposer::ControlledU(decomposer) => (
decomposer.call_inner(matrix.view(), 1e-12).unwrap(),
match decomposer.rxx_equivalent_gate {
RXXEquivalent::Standard(gate) => gate.name().to_string(),
RXXEquivalent::CustomPython(_) => {
unreachable!("Decomposer only uses standard gates")
}
},
),
})
.enumerate()
.min_by(order_sequence)
Expand Down

0 comments on commit 746758f

Please sign in to comment.