From 90e92a46643c72a21c5852299243213907453c21 Mon Sep 17 00:00:00 2001
From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
Date: Tue, 8 Oct 2024 20:24:18 +0300
Subject: [PATCH 01/32] Add inverse function to StandardGate in rust (#13168)
* add inverse of 1-qubit StatndardGate's
* add inverse of 2-qubit and 3-qubit StandardGate's
* return None if the inverse of a StandardGate is not a StandardGate
* add inverse to all impl Operation
* update inverse return type
* add test for inverse method
* replace Vec by SmallVec in Param
* update function name inverse to try_inverse
* fix test following review
* replace try_inverse by inverse
* move inverse from Operation to StandardGate
---
crates/circuit/src/operations.rs | 198 ++++++++++++++++++-
test/python/circuit/test_rust_equivalence.py | 16 ++
2 files changed, 213 insertions(+), 1 deletion(-)
diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs
index cea7398748de..1a9059b08a13 100644
--- a/crates/circuit/src/operations.rs
+++ b/crates/circuit/src/operations.rs
@@ -22,7 +22,7 @@ use crate::{gate_matrix, Qubit};
use ndarray::{aview2, Array2};
use num_complex::Complex64;
-use smallvec::smallvec;
+use smallvec::{smallvec, SmallVec};
use numpy::IntoPyArray;
use numpy::PyReadonlyArray2;
@@ -482,6 +482,198 @@ impl StandardGate {
pub fn num_ctrl_qubits(&self) -> u32 {
STANDARD_GATE_NUM_CTRL_QUBITS[*self as usize]
}
+
+ pub fn inverse(&self, params: &[Param]) -> Option<(StandardGate, SmallVec<[Param; 3]>)> {
+ match self {
+ Self::GlobalPhaseGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::GlobalPhaseGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::HGate => Some((Self::HGate, smallvec![])),
+ Self::IGate => Some((Self::IGate, smallvec![])),
+ Self::XGate => Some((Self::XGate, smallvec![])),
+ Self::YGate => Some((Self::YGate, smallvec![])),
+ Self::ZGate => Some((Self::ZGate, smallvec![])),
+ Self::PhaseGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::PhaseGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::RGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::RGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py), params[1].clone()],
+ )
+ })),
+ Self::RXGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::RXGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::RYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::RYGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::RZGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::RZGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::SGate => Some((Self::SdgGate, smallvec![])),
+ Self::SdgGate => Some((Self::SGate, smallvec![])),
+ Self::SXGate => Some((Self::SXdgGate, smallvec![])),
+ Self::SXdgGate => Some((Self::SXGate, smallvec![])),
+ Self::TGate => Some((Self::TdgGate, smallvec![])),
+ Self::TdgGate => Some((Self::TGate, smallvec![])),
+ Self::UGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::UGate,
+ smallvec![
+ multiply_param(¶ms[0], -1.0, py),
+ multiply_param(¶ms[2], -1.0, py),
+ multiply_param(¶ms[1], -1.0, py),
+ ],
+ )
+ })),
+ Self::U1Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::U1Gate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::U2Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::U2Gate,
+ smallvec![
+ add_param(&multiply_param(¶ms[1], -1.0, py), -PI, py),
+ add_param(&multiply_param(¶ms[0], -1.0, py), PI, py),
+ ],
+ )
+ })),
+ Self::U3Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::U3Gate,
+ smallvec![
+ multiply_param(¶ms[0], -1.0, py),
+ multiply_param(¶ms[2], -1.0, py),
+ multiply_param(¶ms[1], -1.0, py),
+ ],
+ )
+ })),
+ Self::CHGate => Some((Self::CHGate, smallvec![])),
+ Self::CXGate => Some((Self::CXGate, smallvec![])),
+ Self::CYGate => Some((Self::CYGate, smallvec![])),
+ Self::CZGate => Some((Self::CZGate, smallvec![])),
+ Self::DCXGate => None, // the inverse in not a StandardGate
+ Self::ECRGate => Some((Self::ECRGate, smallvec![])),
+ Self::SwapGate => Some((Self::SwapGate, smallvec![])),
+ Self::ISwapGate => None, // the inverse in not a StandardGate
+ Self::CPhaseGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::CPhaseGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::CRXGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::CRXGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::CRYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::CRYGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::CRZGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::CRZGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::CSGate => Some((Self::CSdgGate, smallvec![])),
+ Self::CSdgGate => Some((Self::CSGate, smallvec![])),
+ Self::CSXGate => None, // the inverse in not a StandardGate
+ Self::CUGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::CUGate,
+ smallvec![
+ multiply_param(¶ms[0], -1.0, py),
+ multiply_param(¶ms[2], -1.0, py),
+ multiply_param(¶ms[1], -1.0, py),
+ multiply_param(¶ms[3], -1.0, py),
+ ],
+ )
+ })),
+ Self::CU1Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::CU1Gate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::CU3Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::CU3Gate,
+ smallvec![
+ multiply_param(¶ms[0], -1.0, py),
+ multiply_param(¶ms[2], -1.0, py),
+ multiply_param(¶ms[1], -1.0, py),
+ ],
+ )
+ })),
+ Self::RXXGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::RXXGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::RYYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::RYYGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::RZZGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::RZZGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::RZXGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::RZXGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py)],
+ )
+ })),
+ Self::XXMinusYYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::XXMinusYYGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py), params[1].clone()],
+ )
+ })),
+ Self::XXPlusYYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) {
+ (
+ Self::XXPlusYYGate,
+ smallvec![multiply_param(¶ms[0], -1.0, py), params[1].clone()],
+ )
+ })),
+ Self::CCXGate => Some((Self::CCXGate, smallvec![])),
+ Self::CCZGate => Some((Self::CCZGate, smallvec![])),
+ Self::CSwapGate => Some((Self::CSwapGate, smallvec![])),
+ Self::RCCXGate => None, // the inverse in not a StandardGate
+ Self::C3XGate => Some((Self::C3XGate, smallvec![])),
+ Self::C3SXGate => None, // the inverse in not a StandardGate
+ Self::RC3XGate => None, // the inverse in not a StandardGate
+ }
+ }
}
#[pymethods]
@@ -504,6 +696,10 @@ impl StandardGate {
self.definition(¶ms)
}
+ pub fn _inverse(&self, params: Vec) -> Option<(StandardGate, SmallVec<[Param; 3]>)> {
+ self.inverse(¶ms)
+ }
+
#[getter]
pub fn get_num_qubits(&self) -> u32 {
self.num_qubits()
diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py
index db4f1441bea3..8e17efcfb098 100644
--- a/test/python/circuit/test_rust_equivalence.py
+++ b/test/python/circuit/test_rust_equivalence.py
@@ -146,6 +146,22 @@ def test_matrix(self):
rs_def = standard_gate._to_matrix(params)
np.testing.assert_allclose(rs_def, py_def)
+ def test_inverse(self):
+ """Test that the inverse is the same in rust space."""
+ for name, gate_class in self.standard_gates.items():
+ standard_gate = getattr(gate_class, "_standard_gate", None)
+ if standard_gate is None:
+ # gate is not in rust yet
+ continue
+
+ with self.subTest(name=name):
+ params = [0.1 * (i + 1) for i in range(standard_gate._num_params())]
+ py_def = gate_class.base_class(*params).inverse()
+ rs_def = standard_gate._inverse(params)
+ if rs_def is not None:
+ self.assertEqual(py_def.name, rs_def[0].name)
+ np.testing.assert_allclose(py_def.params, rs_def[1])
+
def test_name(self):
"""Test that the gate name properties match in rust space."""
for name, gate_class in self.standard_gates.items():
From 4dcb0a0b824eb4aeb56afe44a9b2b8b848fbaf05 Mon Sep 17 00:00:00 2001
From: Matthew Treinish
Date: Wed, 9 Oct 2024 16:54:24 -0400
Subject: [PATCH 02/32] Add .index() and new() method to Qubit and Clbit
(#13284)
* Add .index() and new() method to Qubit and Clbit
The Qubit and Clbit rust newtypes are a u32 that are used to track the
Qubits or Clbits in a circuit. The u32 value represents the index of the
Qubit or Clbit in the circuit and are often used as indices in Vecs or
other structures where the index must be a usize. Accordingly there are
a lot of times we need to convert a Qubit or Clbit object into a usize
and previously the only way to do that was use `.0 as usize` on a given
qubit object. To improve the ergonomics of that pattern this commit adds
two new methods for converting from and to a usize new() and index()
respectively. These are modeled after what petgraph does with it's
NodeIndex type which is a similar pattern (although NodeIndex is generic
over the inner type, although it defaults to u32).
* Use Qubit::new() and Clbit::new() where applicable
* Panic on overflow with new() constructor
This commit changes the behavior of the Qubit::new() and Clbit::new()
constructors so that they panic if a user provides a usize that's too
large for the u32 we store internally. Previously the way it was written
it would just wrap the value to u32::MAX.
Co-authored-by: Kevin Hartman
---------
Co-authored-by: Kevin Hartman
---
crates/accelerate/src/check_map.rs | 7 +-
crates/accelerate/src/commutation_checker.rs | 4 +-
crates/accelerate/src/elide_permutations.rs | 12 +-
.../src/euler_one_qubit_decomposer.rs | 12 +-
crates/accelerate/src/gate_direction.rs | 6 +-
crates/accelerate/src/gates_in_basis.rs | 12 +-
.../src/synthesis/clifford/bm_synthesis.rs | 36 +++---
.../synthesis/clifford/greedy_synthesis.rs | 43 ++++---
.../src/synthesis/clifford/utils.rs | 14 +--
.../src/synthesis/multi_controlled/mcmt.rs | 23 ++--
.../src/synthesis/permutation/mod.rs | 14 +--
crates/accelerate/src/unitary_compose.rs | 4 +-
crates/circuit/src/dag_circuit.rs | 110 +++++++++---------
crates/circuit/src/lib.rs | 92 +++++++++++++++
14 files changed, 230 insertions(+), 159 deletions(-)
diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs
index da0592093817..4d4702d18b49 100644
--- a/crates/accelerate/src/check_map.rs
+++ b/crates/accelerate/src/check_map.rs
@@ -30,10 +30,7 @@ fn recurse<'py>(
let check_qubits = |qubits: &[Qubit]| -> bool {
match wire_map {
Some(wire_map) => {
- let mapped_bits = [
- wire_map[qubits[0].0 as usize],
- wire_map[qubits[1].0 as usize],
- ];
+ let mapped_bits = [wire_map[qubits[0].index()], wire_map[qubits[1].index()]];
edge_set.contains(&[mapped_bits[0].into(), mapped_bits[1].into()])
}
None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]),
@@ -58,7 +55,7 @@ fn recurse<'py>(
.map(|inner| {
let outer = qubits[inner];
match wire_map {
- Some(wire_map) => wire_map[outer.0 as usize],
+ Some(wire_map) => wire_map[outer.index()],
None => outer,
}
})
diff --git a/crates/accelerate/src/commutation_checker.rs b/crates/accelerate/src/commutation_checker.rs
index b6e61fcf3f6d..f6c50ca89824 100644
--- a/crates/accelerate/src/commutation_checker.rs
+++ b/crates/accelerate/src/commutation_checker.rs
@@ -377,7 +377,7 @@ impl CommutationChecker {
first_qargs
.iter()
.enumerate()
- .map(|(i, q)| (q, Qubit(i as u32))),
+ .map(|(i, q)| (q, Qubit::new(i))),
);
let mut num_qubits = first_qargs.len() as u32;
for q in second_qargs {
@@ -574,7 +574,7 @@ fn get_relative_placement(
) -> SmallVec<[Option; 2]> {
let mut qubits_g2: HashMap<&Qubit, Qubit> = HashMap::with_capacity(second_qargs.len());
second_qargs.iter().enumerate().for_each(|(i_g1, q_g1)| {
- qubits_g2.insert_unique_unchecked(q_g1, Qubit(i_g1 as u32));
+ qubits_g2.insert_unique_unchecked(q_g1, Qubit::new(i_g1));
});
first_qargs
diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs
index 7d6326d5c496..81dc0ba24027 100644
--- a/crates/accelerate/src/elide_permutations.rs
+++ b/crates/accelerate/src/elide_permutations.rs
@@ -43,8 +43,8 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult