Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Rust representation for XXMinusYYGate and XXPlusYYGate #12606

Merged
merged 2 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions crates/circuit/src/gate_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,15 @@ pub static ISWAP_GATE: [[Complex64; 4]; 4] = [
];

pub static S_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., 1.)]];

pub static SDG_GATE: [[Complex64; 2]; 2] =
[[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., -1.)]];

pub static T_GATE: [[Complex64; 2]; 2] = [
[c64(1., 0.), c64(0., 0.)],
[c64(0., 0.), c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)],
];

pub static TDG_GATE: [[Complex64; 2]; 2] = [
[c64(1., 0.), c64(0., 0.)],
[c64(0., 0.), c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)],
Expand Down Expand Up @@ -246,3 +248,47 @@ pub fn u_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] {
[c64(0., phi).exp() * sin, c64(0., phi + lam).exp() * cos],
]
}

#[inline]
pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] {
let cos = (theta / 2.).cos();
let sin = (theta / 2.).sin();
[
[
c64(cos, 0.),
c64(0., 0.),
c64(0., 0.),
c64(0., -sin) * c64(0., -beta).exp(),
],
[c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)],
[c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)],
[
c64(0., -sin) * c64(0., beta).exp(),
c64(0., 0.),
c64(0., 0.),
c64(cos, 0.),
],
]
}

#[inline]
pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] {
let cos = (theta / 2.).cos();
let sin = (theta / 2.).sin();
[
[c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
[
c64(0., 0.),
c64(cos, 0.),
c64(0., -sin) * c64(0., -beta).exp(),
c64(0., 0.),
],
[
c64(0., 0.),
c64(0., -sin) * c64(0., beta).exp(),
c64(cos, 0.),
c64(0., 0.),
],
[c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)],
]
}
14 changes: 12 additions & 2 deletions crates/circuit/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,23 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [
// SdgGate = 19
["qiskit.circuit.library.standard_gates.s", "SdgGate"],
// TGate = 20
["qiskit.circuit.library.standard_gates.s", "TGate"],
["qiskit.circuit.library.standard_gates.t", "TGate"],
// TdgGate = 21
["qiskit.circuit.library.standard_gates.s", "TdgGate"],
["qiskit.circuit.library.standard_gates.t", "TdgGate"],
Comment on lines -127 to +129
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slightly concerned that this didn't cause us test failures when the first PR merged; we're presumably missing coverage, because that import would have failed.

(Also, there's really no need for us to be importing all these from the specific modules; we could just import them from qiskit.circuit.library or at most qiskit.circuit.library.standard_gates.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure how we could test these. From Rust space, as we'd need the Python GIL, and from Python space, we always have a Python object to refer to, right?

What I guess we could do is expose get_std_gate_class to Python and test it there, but it doesn't sound like the greatest idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The easiest test we should write for this is to call the QuantumCircuit gate method for each gate which will only create a rust representation then just do QuantumCircuit.data[0].operation and compare it against the expected one. That should give us full path coverage of this conversion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, looking through the code this never gets used because we're always adding a tgate by class to a circuit before needing to instantiate a python object from rust so we have the mapping already. I expect via the equivalence library on import. This is only used if we're in a call path where we end up with a StandardGate::TGate created in rust before it's ever added to a circuit via the python object to add a fallback import path. I wrote up the test case as I suggested above locally and it passes without this change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed up a test for this here: #12623

// SXdgGate = 22
["qiskit.circuit.library.standard_gates.sx", "SXdgGate"],
// iSWAPGate = 23
["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"],
//XXMinusYYGate = 24
[
"qiskit.circuit.library.standard_gates.xx_minus_yy",
"XXMinusYYGate",
],
//XXPlusYYGate = 25
[
"qiskit.circuit.library.standard_gates.xx_plus_yy",
"XXPlusYYGate",
],
];

/// A mapping from the enum variant in crate::operations::StandardGate to the python object for the
Expand Down
167 changes: 130 additions & 37 deletions crates/circuit/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,16 @@ pub enum StandardGate {
TdgGate = 21,
SXdgGate = 22,
ISwapGate = 23,
XXMinusYYGate = 24,
XXPlusYYGate = 25,
}

static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [
1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
];

static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2,
];

static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [
Expand Down Expand Up @@ -238,6 +240,8 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [
"tdg",
"sxdg",
"iswap",
"xx_minus_yy",
"xx_plus_yy",
];

#[pymethods]
Expand Down Expand Up @@ -287,7 +291,7 @@ impl StandardGate {
// Remove this when std::mem::variant_count() is stabilized (see
// https://github.com/rust-lang/rust/issues/73662 )

pub const STANDARD_GATE_SIZE: usize = 24;
pub const STANDARD_GATE_SIZE: usize = 26;

impl Operation for StandardGate {
fn name(&self) -> &str {
Expand Down Expand Up @@ -416,6 +420,18 @@ impl Operation for StandardGate {
[] => Some(aview2(&gate_matrix::ISWAP_GATE).to_owned()),
_ => None,
},
Self::XXMinusYYGate => match params {
[Param::Float(theta), Param::Float(beta)] => {
Some(aview2(&gate_matrix::xx_minus_yy_gate(*theta, *beta)).to_owned())
}
_ => None,
},
Self::XXPlusYYGate => match params {
[Param::Float(theta), Param::Float(beta)] => {
Some(aview2(&gate_matrix::xx_plus_yy_gate(*theta, *beta)).to_owned())
}
_ => None,
},
}
}

Expand Down Expand Up @@ -502,6 +518,7 @@ impl Operation for StandardGate {
}),
Self::CXGate => None,
Self::CCXGate => Python::with_gil(|py| -> Option<CircuitData> {
let q0 = smallvec![Qubit(0)];
let q1 = smallvec![Qubit(1)];
let q2 = smallvec![Qubit(2)];
let q0_1 = smallvec![Qubit(0), Qubit(1)];
Expand All @@ -524,7 +541,7 @@ impl Operation for StandardGate {
(Self::TGate, smallvec![], q2.clone()),
(Self::HGate, smallvec![], q2),
(Self::CXGate, smallvec![], q0_1.clone()),
(Self::TGate, smallvec![], smallvec![Qubit(0)]),
(Self::TGate, smallvec![], q0),
(Self::TdgGate, smallvec![], q1),
(Self::CXGate, smallvec![], q0_1),
],
Expand All @@ -536,39 +553,20 @@ impl Operation for StandardGate {
Self::RXGate => todo!("Add when we have R"),
Self::RYGate => todo!("Add when we have R"),
Self::RZGate => Python::with_gil(|py| -> Option<CircuitData> {
match &params[0] {
Param::Float(theta) => Some(
CircuitData::from_standard_gates(
py,
1,
[(
Self::PhaseGate,
smallvec![Param::Float(*theta)],
smallvec![Qubit(0)],
)],
Param::Float(-0.5 * theta),
)
.expect("Unexpected Qiskit python bug"),
),
Param::ParameterExpression(theta) => Some(
CircuitData::from_standard_gates(
py,
1,
[(
Self::PhaseGate,
smallvec![Param::ParameterExpression(theta.clone_ref(py))],
smallvec![Qubit(0)],
)],
Param::ParameterExpression(
theta
.call_method1(py, intern!(py, "__rmul__"), (-0.5,))
.expect("Parameter expression for global phase failed"),
),
)
.expect("Unexpected Qiskit python bug"),
),
Param::Obj(_) => unreachable!(),
}
let theta = &params[0];
Some(
CircuitData::from_standard_gates(
py,
1,
[(
Self::PhaseGate,
smallvec![theta.clone()],
smallvec![Qubit(0)],
)],
multiply_param(theta, -0.5, py),
)
.expect("Unexpected Qiskit python bug"),
)
}),
Self::ECRGate => todo!("Add when we have RZX"),
Self::SwapGate => Python::with_gil(|py| -> Option<CircuitData> {
Expand Down Expand Up @@ -732,6 +730,88 @@ impl Operation for StandardGate {
.expect("Unexpected Qiskit python bug"),
)
}),
Self::XXMinusYYGate => Python::with_gil(|py| -> Option<CircuitData> {
let q0 = smallvec![Qubit(0)];
let q1 = smallvec![Qubit(1)];
let q0_1 = smallvec![Qubit(0), Qubit(1)];
let theta = &params[0];
let beta = &params[1];
Some(
CircuitData::from_standard_gates(
py,
2,
[
(
Self::RZGate,
smallvec![multiply_param(beta, -1.0, py)],
q1.clone(),
),
(Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()),
(Self::SXGate, smallvec![], q0.clone()),
(Self::RZGate, smallvec![Param::Float(PI2)], q0.clone()),
(Self::SGate, smallvec![], q1.clone()),
(Self::CXGate, smallvec![], q0_1.clone()),
(
Self::RYGate,
smallvec![multiply_param(theta, 0.5, py)],
q0.clone(),
),
(
Self::RYGate,
smallvec![multiply_param(theta, -0.5, py)],
q1.clone(),
),
(Self::CXGate, smallvec![], q0_1),
(Self::SdgGate, smallvec![], q1.clone()),
(Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()),
(Self::SXdgGate, smallvec![], q0.clone()),
(Self::RZGate, smallvec![Param::Float(PI2)], q0),
(Self::RZGate, smallvec![beta.clone()], q1),
],
FLOAT_ZERO,
)
.expect("Unexpected Qiskit python bug"),
)
}),
Self::XXPlusYYGate => Python::with_gil(|py| -> Option<CircuitData> {
let q0 = smallvec![Qubit(0)];
let q1 = smallvec![Qubit(1)];
let q1_0 = smallvec![Qubit(1), Qubit(0)];
let theta = &params[0];
let beta = &params[1];
Some(
CircuitData::from_standard_gates(
py,
2,
[
(Self::RZGate, smallvec![beta.clone()], q0.clone()),
(Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()),
(Self::SXGate, smallvec![], q1.clone()),
(Self::RZGate, smallvec![Param::Float(PI2)], q1.clone()),
(Self::SGate, smallvec![], q0.clone()),
(Self::CXGate, smallvec![], q1_0.clone()),
(
Self::RYGate,
smallvec![multiply_param(theta, -0.5, py)],
q1.clone(),
),
(
Self::RYGate,
smallvec![multiply_param(theta, -0.5, py)],
q0.clone(),
),
(Self::CXGate, smallvec![], q1_0),
(Self::SdgGate, smallvec![], q0.clone()),
(Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()),
(Self::SXdgGate, smallvec![], q1.clone()),
(Self::RZGate, smallvec![Param::Float(PI2)], q1),
(Self::RZGate, smallvec![multiply_param(beta, -1.0, py)], q0),
],
FLOAT_ZERO,
)
.expect("Unexpected Qiskit python bug"),
)
}),
}
}

Expand All @@ -742,6 +822,19 @@ impl Operation for StandardGate {

const FLOAT_ZERO: Param = Param::Float(0.0);

fn multiply_param(param: &Param, mult: f64, py: Python) -> Param {
match param {
Param::Float(theta) => Param::Float(*theta * mult),
Param::ParameterExpression(theta) => Param::ParameterExpression(
theta
.clone_ref(py)
.call_method1(py, intern!(py, "__rmul__"), (mult,))
.expect("Parameter expression for global phase failed"),
),
Param::Obj(_) => unreachable!(),
}
}

/// This class is used to wrap a Python side Instruction that is not in the standard library
#[derive(Clone, Debug)]
#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
Expand Down
3 changes: 3 additions & 0 deletions qiskit/circuit/library/standard_gates/xx_minus_yy.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from qiskit.circuit.parameterexpression import ParameterValueType
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit._accelerate.circuit import StandardGate


class XXMinusYYGate(Gate):
Expand Down Expand Up @@ -91,6 +92,8 @@ class XXMinusYYGate(Gate):
\end{pmatrix}
"""

_standard_gate = StandardGate.XXMinusYYGate

def __init__(
self,
theta: ParameterValueType,
Expand Down
3 changes: 3 additions & 0 deletions qiskit/circuit/library/standard_gates/xx_plus_yy.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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 XXPlusYYGate(Gate):
Expand Down Expand Up @@ -87,6 +88,8 @@ class XXPlusYYGate(Gate):
\end{pmatrix}
"""

_standard_gate = StandardGate.XXPlusYYGate

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