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

Port CRX/Y/Z gates to Rust #12648

Merged
merged 11 commits into from
Jun 28, 2024
37 changes: 37 additions & 0 deletions crates/circuit/src/gate_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,43 @@ pub fn rz_gate(theta: f64) -> GateArray1Q {
[[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]]
}

#[inline]
pub fn crx_gate(theta: f64) -> GateArray2Q {
let half_theta = theta / 2.;
let cos = c64(half_theta.cos(), 0.);
let isin = c64(0., half_theta.sin());
[
[C_ONE, C_ZERO, C_ZERO, C_ZERO],
[C_ZERO, cos, C_ZERO, -isin],
[C_ZERO, C_ZERO, C_ONE, C_ZERO],
[C_ZERO, -isin, C_ZERO, cos],
]
}

#[inline]
pub fn cry_gate(theta: f64) -> GateArray2Q {
let half_theta = theta / 2.;
let cos = c64(half_theta.cos(), 0.);
let sin = c64(half_theta.sin(), 0.);
[
[C_ONE, C_ZERO, C_ZERO, C_ZERO],
[C_ZERO, cos, C_ZERO, -sin],
[C_ZERO, C_ZERO, C_ONE, C_ZERO],
[C_ZERO, sin, C_ZERO, cos],
]
}

#[inline]
pub fn crz_gate(theta: f64) -> GateArray2Q {
let i_half_theta = c64(0., theta / 2.);
[
[C_ONE, C_ZERO, C_ZERO, C_ZERO],
[C_ZERO, (-i_half_theta).exp(), C_ZERO, C_ZERO],
[C_ZERO, C_ZERO, C_ONE, C_ZERO],
[C_ZERO, C_ZERO, C_ZERO, i_half_theta.exp()],
]
}

pub static H_GATE: GateArray1Q = [
[c64(FRAC_1_SQRT_2, 0.), c64(FRAC_1_SQRT_2, 0.)],
[c64(FRAC_1_SQRT_2, 0.), c64(-FRAC_1_SQRT_2, 0.)],
Expand Down
6 changes: 3 additions & 3 deletions crates/circuit/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,11 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [
// U3Gate = 28
["qiskit.circuit.library.standard_gates.u3", "U3Gate"],
// CRXGate = 29
["placeholder", "placeholder"],
["qiskit.circuit.library.standard_gates.rx", "CRXGate"],
// CRYGate = 30
["placeholder", "placeholder"],
["qiskit.circuit.library.standard_gates.ry", "CRYGate"],
// CRZGate = 31
["placeholder", "placeholder"],
["qiskit.circuit.library.standard_gates.rz", "CRZGate"],
// RGate 32
["qiskit.circuit.library.standard_gates.r", "RGate"],
// CHGate = 33
Expand Down
110 changes: 104 additions & 6 deletions crates/circuit/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,8 @@ pub enum StandardGate {
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, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39
1, 1, 1, 2, 2, 2, 1, 1, 1, 2, // 20-29
2, 2, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39
2, 2, 34, 34, 34, 2, 34, 34, 34, 34, // 40-49
34, 34, 34, // 50-52
];
Expand All @@ -245,8 +245,8 @@ static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [
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, 2, 0, 1, 0, 0, 0, 0, 3, // 30-39
0, 0, 0, 0, 2, 2, 1, 2, 3, 1, // 20-29
1, 1, 2, 0, 1, 0, 0, 0, 0, 3, // 30-39
1, 3, 34, 34, 34, 0, 34, 34, 34, 34, // 40-49
34, 34, 34, // 50-52
];
Expand Down Expand Up @@ -422,6 +422,18 @@ impl Operation for StandardGate {
[Param::Float(theta)] => Some(aview2(&gate_matrix::rz_gate(*theta)).to_owned()),
_ => None,
},
Self::CRXGate => match params {
[Param::Float(theta)] => Some(aview2(&gate_matrix::crx_gate(*theta)).to_owned()),
_ => None,
},
Self::CRYGate => match params {
[Param::Float(theta)] => Some(aview2(&gate_matrix::cry_gate(*theta)).to_owned()),
_ => None,
},
Self::CRZGate => match params {
[Param::Float(theta)] => Some(aview2(&gate_matrix::crz_gate(*theta)).to_owned()),
_ => None,
},
Self::ECRGate => match params {
[] => Some(aview2(&gate_matrix::ECR_GATE).to_owned()),
_ => None,
Expand Down Expand Up @@ -510,7 +522,6 @@ impl Operation for StandardGate {
}
_ => None,
},
Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(),
Self::RGate => match params {
[Param::Float(theta), Param::Float(phi)] => {
Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned())
Expand Down Expand Up @@ -673,6 +684,94 @@ impl Operation for StandardGate {
.expect("Unexpected Qiskit python bug"),
)
}),
Self::CRXGate => Python::with_gil(|py| -> Option<CircuitData> {
let theta = &params[0];
Some(
CircuitData::from_standard_gates(
py,
2,
[
(
Self::PhaseGate,
smallvec![Param::Float(PI / 2.)],
smallvec![Qubit(1)],
),
(Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
(
Self::UGate,
smallvec![
multiply_param(theta, -0.5, py),
Param::Float(0.0),
Param::Float(0.0)
],
smallvec![Qubit(1)],
),
(Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
(
Self::UGate,
smallvec![
multiply_param(theta, 0.5, py),
Param::Float(-PI / 2.),
Param::Float(0.0)
],
smallvec![Qubit(1)],
),
],
Param::Float(0.0),
)
.expect("Unexpected Qiskit Python bug!"),
)
}),
Self::CRYGate => Python::with_gil(|py| -> Option<CircuitData> {
let theta = &params[0];
Some(
CircuitData::from_standard_gates(
py,
2,
[
(
Self::RYGate,
smallvec![multiply_param(theta, 0.5, py)],
smallvec![Qubit(1)],
),
(Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
(
Self::RYGate,
smallvec![multiply_param(theta, -0.5, py)],
smallvec![Qubit(1)],
),
(Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
],
Param::Float(0.0),
)
.expect("Unexpected Qiskit Python bug!"),
)
}),
Self::CRZGate => Python::with_gil(|py| -> Option<CircuitData> {
let theta = &params[0];
Some(
CircuitData::from_standard_gates(
py,
2,
[
(
Self::RZGate,
smallvec![multiply_param(theta, 0.5, py)],
smallvec![Qubit(1)],
),
(Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
(
Self::RZGate,
smallvec![multiply_param(theta, -0.5, py)],
smallvec![Qubit(1)],
),
(Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
],
Param::Float(0.0),
)
.expect("Unexpected Qiskit Python bug!"),
)
}),
Self::ECRGate => todo!("Add when we have RZX"),
Self::SwapGate => Python::with_gil(|py| -> Option<CircuitData> {
Some(
Expand Down Expand Up @@ -962,7 +1061,6 @@ impl Operation for StandardGate {
.expect("Unexpected Qiskit python bug"),
)
}),
Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(),
Self::RGate => Python::with_gil(|py| -> Option<CircuitData> {
let theta_expr = clone_param(&params[0], py);
let phi_expr1 = add_param(&params[1], -PI / 2., py);
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/library/standard_gates/rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ class CRXGate(ControlledGate):
\end{pmatrix}
"""

_standard_gate = StandardGate.CRXGate

def __init__(
self,
theta: ParameterValueType,
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/library/standard_gates/ry.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ class CRYGate(ControlledGate):
\end{pmatrix}
"""

_standard_gate = StandardGate.CRYGate

def __init__(
self,
theta: ParameterValueType,
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/library/standard_gates/rz.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ class CRZGate(ControlledGate):
phase difference.
"""

_standard_gate = StandardGate.CRZGate

def __init__(
self,
theta: ParameterValueType,
Expand Down
18 changes: 18 additions & 0 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4774,6 +4774,12 @@ def crx(
"""
from .library.standard_gates.rx import CRXGate

# if the control state is |1> use the fast Rust version of the gate
if ctrl_state is None or ctrl_state in ["1", 1]:
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm I get that you added the ["1", 1] cases because it's the state set by default when ctrl_state is None, but I wonder if we would be overseeing some edge case by adding this?? (If there is indeed no difference, I think we should do it in all controlled gate methods).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If ctrl_state is None it is directly set to 1 in the constructor of any controlled gate, e.g.

>>> CRXGate(theta=0.5, ctrl_state=None).ctrl_state 
1

so I think there should be no functional difference 🙂

But I agree we should generally cover all |1> state definitions we can use the Rust implementation more generally.

return self._append_standard_gate(
StandardGate.CRXGate, [theta], [control_qubit, target_qubit], None, label=label
)

return self.append(
CRXGate(theta, label=label, ctrl_state=ctrl_state),
[control_qubit, target_qubit],
Expand Down Expand Up @@ -4843,6 +4849,12 @@ def cry(
"""
from .library.standard_gates.ry import CRYGate

# if the control state is |1> use the fast Rust version of the gate
if ctrl_state is None or ctrl_state in ["1", 1]:
return self._append_standard_gate(
StandardGate.CRYGate, [theta], [control_qubit, target_qubit], None, label=label
)

return self.append(
CRYGate(theta, label=label, ctrl_state=ctrl_state),
[control_qubit, target_qubit],
Expand Down Expand Up @@ -4909,6 +4921,12 @@ def crz(
"""
from .library.standard_gates.rz import CRZGate

# if the control state is |1> use the fast Rust version of the gate
if ctrl_state is None or ctrl_state in ["1", 1]:
return self._append_standard_gate(
StandardGate.CRZGate, [theta], [control_qubit, target_qubit], None, label=label
)

return self.append(
CRZGate(theta, label=label, ctrl_state=ctrl_state),
[control_qubit, target_qubit],
Expand Down
Loading