From 2a064bde769497b7a21d7395aee91e0dd81d76a3 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 25 Jun 2024 21:28:11 -0400 Subject: [PATCH 1/6] Implement RGate in Rust --- crates/circuit/src/gate_matrix.rs | 13 ++++++ crates/circuit/src/imports.rs | 2 +- crates/circuit/src/operations.rs | 47 ++++++++++++++++++-- qiskit/circuit/library/standard_gates/r.py | 3 ++ qiskit/circuit/quantumcircuit.py | 4 +- test/python/circuit/test_rust_equivalence.py | 2 +- 6 files changed, 62 insertions(+), 9 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 2e5f55d6ddcb..3ef2f5a115ae 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -24,6 +24,19 @@ const fn c64(re: f64, im: f64) -> Complex64 { pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(1., 0.)]]; +#[inline] +pub fn r_gate(theta: f64, phi: f64) -> [[Complex64; 2]; 2] { + let half_theta = theta / 2.; + let cost = c64(half_theta.cos(), 0.); + let sint = half_theta.sin(); + let cosphi = phi.cos(); + let sinphi = phi.sin(); + [ + [cost, c64(-sint * sinphi, -sint * cosphi)], + [c64(sint * sinphi, -sint * cosphi), cost], + ] +} + #[inline] pub fn rx_gate(theta: f64) -> [[Complex64; 2]; 2] { let half_theta = theta / 2.; diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 92700f3274e7..6650bfc7b7b9 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -157,7 +157,7 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // CRZGate = 31 ["placeholder", "placeholder"], // RGate 32 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.r", "RGate"], // CHGate = 33 ["qiskit.circuit.library.standard_gates.h", "CHGate"], // CPhaseGate = 34 diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index af7dabc86216..5c2855f6cc67 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -239,7 +239,7 @@ 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 ]; @@ -249,7 +249,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 ]; @@ -514,7 +514,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!(), @@ -954,7 +959,41 @@ impl Operation for StandardGate { ) }), Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), - Self::RGate => todo!(), + Self::RGate => Python::with_gil(|py| -> Option { + let theta_expr = match ¶ms[0] { + Param::Float(theta) => Param::Float(*theta), + Param::ParameterExpression(theta) => { + Param::ParameterExpression(theta.clone_ref(py)) + } + Param::Obj(_) => unreachable!(), + }; + let (phi_expr1, phi_expr2) = match ¶ms[0] { + Param::Float(phi) => (Param::Float(*phi - 1.0), Param::Float(-*phi + 1.0)), + Param::ParameterExpression(phi) => { + let phiexpr1 = phi + .call_method1(py, intern!(py, "__add__"), ((-PI / 2.0),)) + .expect("Unexpected Qiskit python bug"); + let phiexpr2 = phiexpr1 + .call_method1(py, intern!(py, "__rmul__"), (-1.0,)) + .expect("Unexpected Qiskit python bug"); + ( + Param::ParameterExpression(phiexpr1), + Param::ParameterExpression(phiexpr2), + ) + } + Param::Obj(_) => unreachable!(), + }; + let defparams = smallvec![theta_expr, phi_expr1, phi_expr2]; + Some( + CircuitData::from_standard_gates( + py, + 1, + [(Self::UGate, defparams, smallvec![Qubit(0)])], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CHGate => todo!(), Self::CPhaseGate => todo!(), Self::CSGate => todo!(), diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py index 9d4905e27866..22c30e24bf6a 100644 --- a/qiskit/circuit/library/standard_gates/r.py +++ b/qiskit/circuit/library/standard_gates/r.py @@ -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): @@ -49,6 +50,8 @@ class RGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.RGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index ee52e3308a94..658831544244 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -4649,9 +4649,7 @@ def r( Returns: A handle to the instructions created. """ - from .library.standard_gates.r import RGate - - return self.append(RGate(theta, phi), [qubit], [], copy=False) + return self._append_standard_gate(StandardGate.RGate, [theta, phi], qargs=[qubit]) def rv( self, diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index 8d6d159c0b64..f265712840e6 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -21,7 +21,7 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping -SKIP_LIST = {"rx", "ry", "ecr"} +SKIP_LIST = {"rx", "ry", "ecr", "r"} CUSTOM_MAPPING = {"x", "rz"} From a034be5ba8d6599d228b39e7f4bacca8ef0608af Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 26 Jun 2024 10:49:06 -0400 Subject: [PATCH 2/6] Update crates/circuit/src/operations.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/operations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 5c2855f6cc67..96b33a723aaf 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -967,7 +967,7 @@ impl Operation for StandardGate { } Param::Obj(_) => unreachable!(), }; - let (phi_expr1, phi_expr2) = match ¶ms[0] { + let (phi_expr1, phi_expr2) = match ¶ms[1] { Param::Float(phi) => (Param::Float(*phi - 1.0), Param::Float(-*phi + 1.0)), Param::ParameterExpression(phi) => { let phiexpr1 = phi From 7eef0f3d8292f12ebbdcb04b1fb3834e5141ea1d Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 26 Jun 2024 17:42:56 -0400 Subject: [PATCH 3/6] Fix error in decomposition of RGate There is an error in the expression for decomposition of the R gate in the port to Rust. This fixes the error and re-enables the skipped test that failed because of the incorrect expression. --- crates/circuit/src/operations.rs | 2 +- test/python/circuit/test_rust_equivalence.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 96b33a723aaf..ddb37d00d5e9 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -968,7 +968,7 @@ impl Operation for StandardGate { Param::Obj(_) => unreachable!(), }; let (phi_expr1, phi_expr2) = match ¶ms[1] { - Param::Float(phi) => (Param::Float(*phi - 1.0), Param::Float(-*phi + 1.0)), + Param::Float(phi) => (Param::Float(*phi - PI2), Param::Float(-*phi + PI2)), Param::ParameterExpression(phi) => { let phiexpr1 = phi .call_method1(py, intern!(py, "__add__"), ((-PI / 2.0),)) diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index f265712840e6..8d6d159c0b64 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -21,7 +21,7 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping -SKIP_LIST = {"rx", "ry", "ecr", "r"} +SKIP_LIST = {"rx", "ry", "ecr"} CUSTOM_MAPPING = {"x", "rz"} From 49c8dcd69f8c31dd0dd48be4425e9af6b3357dd4 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 26 Jun 2024 17:44:49 -0400 Subject: [PATCH 4/6] Factor cloning the Param enum in Rust To clone the enum, each variant must be handled separately. This is currently used once, but can be used each time a `Param` is cloned. In case more work needs to be done within match arms, one might choose not to use this function, but rather clone in each of these arms. --- crates/circuit/src/operations.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index ddb37d00d5e9..a26500801c65 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -960,13 +960,7 @@ impl Operation for StandardGate { }), Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), Self::RGate => Python::with_gil(|py| -> Option { - let theta_expr = match ¶ms[0] { - Param::Float(theta) => Param::Float(*theta), - Param::ParameterExpression(theta) => { - Param::ParameterExpression(theta.clone_ref(py)) - } - Param::Obj(_) => unreachable!(), - }; + let theta_expr = clone_param(¶ms[0], py); let (phi_expr1, phi_expr2) = match ¶ms[1] { Param::Float(phi) => (Param::Float(*phi - PI2), Param::Float(-*phi + PI2)), Param::ParameterExpression(phi) => { @@ -1019,6 +1013,18 @@ impl Operation for StandardGate { const FLOAT_ZERO: Param = Param::Float(0.0); +// Return explictly requested copy of `param`, handling +// each variant separately. +fn clone_param(param: &Param, py: Python) -> Param { + match param { + Param::Float(theta) => Param::Float(*theta), + Param::ParameterExpression(theta) => { + Param::ParameterExpression(theta.clone_ref(py)) + } + Param::Obj(_) => unreachable!(), + } +} + fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { match param { Param::Float(theta) => Param::Float(*theta * mult), From 043133f6994bd8f32bb1edb9139d59add9531693 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 26 Jun 2024 18:16:56 -0400 Subject: [PATCH 5/6] Run cargo fmt --- crates/circuit/src/operations.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index a26500801c65..6a7b422c104f 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -1018,9 +1018,7 @@ const FLOAT_ZERO: Param = Param::Float(0.0); fn clone_param(param: &Param, py: Python) -> Param { match param { Param::Float(theta) => Param::Float(*theta), - Param::ParameterExpression(theta) => { - Param::ParameterExpression(theta.clone_ref(py)) - } + Param::ParameterExpression(theta) => Param::ParameterExpression(theta.clone_ref(py)), Param::Obj(_) => unreachable!(), } } From 07fec379319090b0b065b2589b63ddc75b7430c4 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 26 Jun 2024 19:00:24 -0400 Subject: [PATCH 6/6] Implement and use addition for enum Param This handles `Float` and `ParameterExpression` variants uniformly. --- crates/circuit/src/operations.rs | 33 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 6a7b422c104f..a88469f65c6a 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -961,22 +961,8 @@ impl Operation for StandardGate { Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), Self::RGate => Python::with_gil(|py| -> Option { let theta_expr = clone_param(¶ms[0], py); - let (phi_expr1, phi_expr2) = match ¶ms[1] { - Param::Float(phi) => (Param::Float(*phi - PI2), Param::Float(-*phi + PI2)), - Param::ParameterExpression(phi) => { - let phiexpr1 = phi - .call_method1(py, intern!(py, "__add__"), ((-PI / 2.0),)) - .expect("Unexpected Qiskit python bug"); - let phiexpr2 = phiexpr1 - .call_method1(py, intern!(py, "__rmul__"), (-1.0,)) - .expect("Unexpected Qiskit python bug"); - ( - Param::ParameterExpression(phiexpr1), - Param::ParameterExpression(phiexpr2), - ) - } - Param::Obj(_) => unreachable!(), - }; + let phi_expr1 = add_param(¶ms[1], -PI2, py); + let phi_expr2 = multiply_param(&phi_expr1, -1.0, py); let defparams = smallvec![theta_expr, phi_expr1, phi_expr2]; Some( CircuitData::from_standard_gates( @@ -1030,7 +1016,20 @@ 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("Multiplication of Parameter expression by float failed."), + ), + Param::Obj(_) => unreachable!(), + } +} + +fn add_param(param: &Param, summand: f64, py: Python) -> Param { + match param { + Param::Float(theta) => Param::Float(*theta + summand), + Param::ParameterExpression(theta) => Param::ParameterExpression( + theta + .clone_ref(py) + .call_method1(py, intern!(py, "__add__"), (summand,)) + .expect("Sum of Parameter expression and float failed."), ), Param::Obj(_) => unreachable!(), }