From 2fab2007e3d7384e9e55a10340952c4974ece03c Mon Sep 17 00:00:00 2001 From: Eli Arbel <46826214+eliarbel@users.noreply.github.com> Date: Wed, 26 Jun 2024 18:15:10 +0300 Subject: [PATCH 01/17] Add Rust representation for DCXGate (#12644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updating tables * Adding remaining code * Appending the Rust representation directly * Fix fmt --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/gate_matrix.rs | 7 ++++++ crates/circuit/src/imports.rs | 2 +- crates/circuit/src/operations.rs | 25 ++++++++++++++++---- qiskit/circuit/library/standard_gates/dcx.py | 3 +++ qiskit/circuit/quantumcircuit.py | 4 +--- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 2e5f55d6ddcb..80fecfb597c6 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -226,6 +226,13 @@ pub static TDG_GATE: [[Complex64; 2]; 2] = [ [c64(0., 0.), c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], ]; +pub static DCX_GATE: [[Complex64; 4]; 4] = [ + [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], + [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], + [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], + [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], +]; + #[inline] pub fn global_phase_gate(theta: f64) -> [[Complex64; 1]; 1] { [[c64(0., theta).exp()]] diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 92700f3274e7..632f5b0f5737 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -183,7 +183,7 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // C4XGate = 44 ["placeholder", "placeholder"], // DCXGate = 45 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.dcx", "DCXGate"], // CCZGate = 46 ["placeholder", "placeholder"], // RCCXGate = 47 diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index af7dabc86216..d9626b5c7371 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -240,7 +240,7 @@ static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ 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 - 2, 2, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49 + 2, 2, 34, 34, 34, 2, 34, 34, 34, 34, // 40-49 34, 34, 34, // 50-52 ]; @@ -250,7 +250,7 @@ static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ 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 - 1, 3, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49 + 1, 3, 34, 34, 34, 0, 34, 34, 34, 34, // 40-49 34, 34, 34, // 50-52 ]; @@ -523,7 +523,10 @@ impl Operation for StandardGate { Self::CSwapGate => todo!(), Self::CUGate | Self::CU1Gate | Self::CU3Gate => todo!(), Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), - Self::DCXGate => todo!(), + Self::DCXGate => match params { + [] => Some(aview2(&gate_matrix::DCX_GATE).to_owned()), + _ => None, + }, Self::CCZGate => todo!(), Self::RCCXGate | Self::RC3XGate => todo!(), Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), @@ -965,7 +968,21 @@ impl Operation for StandardGate { Self::CU1Gate => todo!(), Self::CU3Gate => todo!(), Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), - Self::DCXGate => todo!(), + Self::DCXGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CCZGate => todo!(), Self::RCCXGate | Self::RC3XGate => todo!(), Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), diff --git a/qiskit/circuit/library/standard_gates/dcx.py b/qiskit/circuit/library/standard_gates/dcx.py index 6455bea2779e..d83f2e2f9c7f 100644 --- a/qiskit/circuit/library/standard_gates/dcx.py +++ b/qiskit/circuit/library/standard_gates/dcx.py @@ -15,6 +15,7 @@ from qiskit.circuit.singleton import SingletonGate, stdlib_singleton_key from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array +from qiskit._accelerate.circuit import StandardGate @with_gate_array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 1, 0, 0], [0, 0, 1, 0]]) @@ -48,6 +49,8 @@ class DCXGate(SingletonGate): \end{pmatrix} """ + _standard_gate = StandardGate.DCXGate + def __init__(self, label=None, *, duration=None, unit="dt"): """Create new DCX gate.""" super().__init__("dcx", 2, [], label=label, duration=duration, unit=unit) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 8b3ff7bf1979..08bac04c9e6f 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -5328,9 +5328,7 @@ def dcx(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - from .library.standard_gates.dcx import DCXGate - - return self.append(DCXGate(), [qubit1, qubit2], [], copy=False) + return self._append_standard_gate(op=StandardGate.DCXGate, qargs=[qubit1, qubit2]) def ccx( self, From 6447941885254066371b674334e68ee153e5b329 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 27 Jun 2024 05:08:24 -0400 Subject: [PATCH 02/17] Implement RGate in Rust (#12662) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement RGate in Rust * Update crates/circuit/src/operations.rs Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * 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. * 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. * Run cargo fmt * Implement and use addition for enum Param This handles `Float` and `ParameterExpression` variants uniformly. --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/gate_matrix.rs | 13 ++++++ crates/circuit/src/imports.rs | 2 +- crates/circuit/src/operations.rs | 52 +++++++++++++++++++--- qiskit/circuit/library/standard_gates/r.py | 3 ++ qiskit/circuit/quantumcircuit.py | 4 +- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 80fecfb597c6..2a3fcdf88289 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 632f5b0f5737..bf06685ba53b 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 d9626b5c7371..e0e93726735d 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, 2, 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, 0, 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!(), @@ -957,7 +962,21 @@ impl Operation for StandardGate { ) }), Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), - Self::RGate => todo!(), + Self::RGate => Python::with_gil(|py| -> Option { + let theta_expr = clone_param(¶ms[0], py); + 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( + py, + 1, + [(Self::UGate, defparams, smallvec![Qubit(0)])], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CHGate => todo!(), Self::CPhaseGate => todo!(), Self::CSGate => todo!(), @@ -997,6 +1016,16 @@ 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), @@ -1004,7 +1033,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!(), } 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 08bac04c9e6f..de7a2934a464 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, From 76af5b475b9b2a57f9eeabd3c0b793b037464630 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:58:52 +0200 Subject: [PATCH 03/17] Enable the new efficient MCX decompose (#12628) * enable the new efficient MCX decompose * fix tests * revert explicit * apply review comments * update test_circuit_qasm.py * update test_decompose.py * revert C3X C4X names * fix qasm2 exporter tests use regex to fetch the mcx_ name * fix lint and add reno --------- Co-authored-by: Julien Gacon --- qiskit/circuit/quantumcircuit.py | 4 ++-- .../notes/fix-mcx-performance-de86bcc9f969b81e.yaml | 6 ++++++ test/python/circuit/test_circuit_qasm.py | 12 +++++++----- test/python/circuit/test_controlled_gate.py | 4 ++-- test/python/qasm2/test_export.py | 13 +++++++------ test/python/transpiler/test_decompose.py | 4 ++-- 6 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/fix-mcx-performance-de86bcc9f969b81e.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index de7a2934a464..485591a8a3bb 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -5402,12 +5402,12 @@ def mcx( ValueError: if the given mode is not known, or if too few ancilla qubits are passed. AttributeError: if no ancilla qubits are passed, but some are needed. """ - from .library.standard_gates.x import MCXGrayCode, MCXRecursive, MCXVChain + from .library.standard_gates.x import MCXGate, MCXRecursive, MCXVChain num_ctrl_qubits = len(control_qubits) available_implementations = { - "noancilla": MCXGrayCode(num_ctrl_qubits, ctrl_state=ctrl_state), + "noancilla": MCXGate(num_ctrl_qubits, ctrl_state=ctrl_state), "recursion": MCXRecursive(num_ctrl_qubits, ctrl_state=ctrl_state), "v-chain": MCXVChain(num_ctrl_qubits, False, ctrl_state=ctrl_state), "v-chain-dirty": MCXVChain(num_ctrl_qubits, dirty_ancillas=True, ctrl_state=ctrl_state), diff --git a/releasenotes/notes/fix-mcx-performance-de86bcc9f969b81e.yaml b/releasenotes/notes/fix-mcx-performance-de86bcc9f969b81e.yaml new file mode 100644 index 000000000000..8cee3356ac4c --- /dev/null +++ b/releasenotes/notes/fix-mcx-performance-de86bcc9f969b81e.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Improve the decomposition of the gate generated by :meth:`.QuantumCircuit.mcx` + without using ancilla qubits, so that the number of :class:`.CXGate` will grow + quadratically in the number of qubits and not exponentially. diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index c1ece0230d39..13882281cff1 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -394,12 +394,14 @@ def test_circuit_qasm_with_mcx_gate(self): # qasm output doesn't support parameterized gate yet. # param0 for "gate mcuq(param0) is not used inside the definition - expected_qasm = """OPENQASM 2.0; + pattern = r"""OPENQASM 2.0; include "qelib1.inc"; -gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; } -qreg q[4]; -mcx q[0],q[1],q[2],q[3];""" - self.assertEqual(dumps(qc), expected_qasm) +gate mcx q0,q1,q2,q3 { h q3; p\(pi/8\) q0; p\(pi/8\) q1; p\(pi/8\) q2; p\(pi/8\) q3; cx q0,q1; p\(-pi/8\) q1; cx q0,q1; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; p\(pi/8\) q2; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; h q3; } +gate (?Pmcx_[0-9]*) q0,q1,q2,q3 { mcx q0,q1,q2,q3; } +qreg q\[4\]; +(?P=mcx_id) q\[0\],q\[1\],q\[2\],q\[3\];""" + expected_qasm = re.compile(pattern, re.MULTILINE) + self.assertRegex(dumps(qc), expected_qasm) def test_circuit_qasm_with_mcx_gate_variants(self): """Test circuit qasm() method with MCXGrayCode, MCXRecursive, MCXVChain""" diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 8ba70ee852ca..f26ab987f4fd 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -764,9 +764,9 @@ def test_small_mcx_gates_yield_cx_count(self, num_ctrl_qubits): @data(1, 2, 3, 4) def test_mcxgraycode_gates_yield_explicit_gates(self, num_ctrl_qubits): - """Test creating an mcx gate calls MCXGrayCode and yeilds explicit definition.""" + """Test an MCXGrayCode yields explicit definition.""" qc = QuantumCircuit(num_ctrl_qubits + 1) - qc.mcx(list(range(num_ctrl_qubits)), [num_ctrl_qubits]) + qc.append(MCXGrayCode(num_ctrl_qubits), list(range(qc.num_qubits)), []) explicit = {1: CXGate, 2: CCXGate, 3: C3XGate, 4: C4XGate} self.assertEqual(type(qc[0].operation), explicit[num_ctrl_qubits]) diff --git a/test/python/qasm2/test_export.py b/test/python/qasm2/test_export.py index a0a3ade6ce86..85172ec3ce8f 100644 --- a/test/python/qasm2/test_export.py +++ b/test/python/qasm2/test_export.py @@ -387,13 +387,14 @@ def test_mcx_gate(self): # qasm output doesn't support parameterized gate yet. # param0 for "gate mcuq(param0) is not used inside the definition - expected_qasm = """\ -OPENQASM 2.0; + pattern = r"""OPENQASM 2.0; include "qelib1.inc"; -gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; } -qreg q[4]; -mcx q[0],q[1],q[2],q[3];""" - self.assertEqual(qasm2.dumps(qc), expected_qasm) +gate mcx q0,q1,q2,q3 { h q3; p\(pi/8\) q0; p\(pi/8\) q1; p\(pi/8\) q2; p\(pi/8\) q3; cx q0,q1; p\(-pi/8\) q1; cx q0,q1; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; p\(pi/8\) q2; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; h q3; } +gate (?Pmcx_[0-9]*) q0,q1,q2,q3 { mcx q0,q1,q2,q3; } +qreg q\[4\]; +(?P=mcx_id) q\[0\],q\[1\],q\[2\],q\[3\];""" + expected_qasm = re.compile(pattern, re.MULTILINE) + self.assertRegex(qasm2.dumps(qc), expected_qasm) def test_mcx_gate_variants(self): n = 5 diff --git a/test/python/transpiler/test_decompose.py b/test/python/transpiler/test_decompose.py index 91ebede9fa86..7b364f3ac10f 100644 --- a/test/python/transpiler/test_decompose.py +++ b/test/python/transpiler/test_decompose.py @@ -216,7 +216,7 @@ def test_decompose_only_given_label(self): def test_decompose_only_given_name(self): """Test decomposition parameters so that only given name is decomposed.""" - decom_circ = self.complex_circuit.decompose(["mcx"]) + decom_circ = self.complex_circuit.decompose(["mcx"], reps=2) dag = circuit_to_dag(decom_circ) self.assertEqual(len(dag.op_nodes()), 13) @@ -236,7 +236,7 @@ def test_decompose_only_given_name(self): def test_decompose_mixture_of_names_and_labels(self): """Test decomposition parameters so that mixture of names and labels is decomposed""" - decom_circ = self.complex_circuit.decompose(["mcx", "gate2"]) + decom_circ = self.complex_circuit.decompose(["mcx", "gate2"], reps=2) dag = circuit_to_dag(decom_circ) self.assertEqual(len(dag.op_nodes()), 15) From ea5a54b9da8db91a3e67812856ce419bef923822 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 27 Jun 2024 15:38:36 -0400 Subject: [PATCH 04/17] Add constant abbreviations for some values and types. (#12651) This PR introduces some abbreviations for repetitive Rust code. Motivations are reducing clutter, improving readability, and perhaps modest support for rapid development. * Use the definition of `const fn 64` that was introduced in #12459 uniformly in all crates. * Define some complex constants `C_ONE`, `C_ZERO`, `IM`, etc. * Introduce type definitions for arrays representing gates. For example: `GateArray1Q = [[Complex64; 2]; 2];` --- .../accelerate/src/convert_2q_block_matrix.rs | 5 +- .../src/euler_one_qubit_decomposer.rs | 11 +- crates/accelerate/src/isometry.rs | 7 +- crates/accelerate/src/pauli_exp_val.rs | 5 +- crates/accelerate/src/sampled_exp_val.rs | 3 +- crates/accelerate/src/sparse_pauli_op.rs | 29 +- crates/accelerate/src/two_qubit_decompose.rs | 211 +++++---------- crates/accelerate/src/uc_gate.rs | 14 +- crates/circuit/src/gate_matrix.rs | 254 +++++++----------- crates/circuit/src/lib.rs | 1 + crates/circuit/src/operations.rs | 37 +-- crates/circuit/src/util.rs | 48 ++++ 12 files changed, 262 insertions(+), 363 deletions(-) create mode 100644 crates/circuit/src/util.rs diff --git a/crates/accelerate/src/convert_2q_block_matrix.rs b/crates/accelerate/src/convert_2q_block_matrix.rs index e311c129b11b..9c179397d641 100644 --- a/crates/accelerate/src/convert_2q_block_matrix.rs +++ b/crates/accelerate/src/convert_2q_block_matrix.rs @@ -20,10 +20,7 @@ use numpy::ndarray::{aview2, Array2, ArrayView2}; use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2}; use smallvec::SmallVec; -static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = [ - [Complex64::new(1., 0.), Complex64::new(0., 0.)], - [Complex64::new(0., 0.), Complex64::new(1., 0.)], -]; +use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY; /// Return the matrix Operator resulting from a block of Instructions. #[pyfunction] diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 1fd5fd7834ff..9f10f76de467 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -31,6 +31,7 @@ use ndarray::prelude::*; use numpy::PyReadonlyArray2; use pyo3::pybacked::PyBackedStr; +use qiskit_circuit::util::c64; use qiskit_circuit::SliceOrInt; pub const ANGLE_ZERO_EPSILON: f64 = 1e-12; @@ -855,16 +856,16 @@ pub fn params_xyx(unitary: PyReadonlyArray2) -> [f64; 4] { fn params_xzx_inner(umat: ArrayView2) -> [f64; 4] { let det = det_one_qubit(umat); - let phase = (Complex64::new(0., -1.) * det.ln()).re / 2.; + let phase = det.ln().im / 2.; let sqrt_det = det.sqrt(); let mat_zyz = arr2(&[ [ - Complex64::new((umat[[0, 0]] / sqrt_det).re, (umat[[1, 0]] / sqrt_det).im), - Complex64::new((umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im), + c64((umat[[0, 0]] / sqrt_det).re, (umat[[1, 0]] / sqrt_det).im), + c64((umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im), ], [ - Complex64::new(-(umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im), - Complex64::new((umat[[0, 0]] / sqrt_det).re, -(umat[[1, 0]] / sqrt_det).im), + c64(-(umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im), + c64((umat[[0, 0]] / sqrt_det).re, -(umat[[1, 0]] / sqrt_det).im), ], ]); let [theta, phi, lam, phase_zxz] = params_zxz_inner(mat_zyz.view()); diff --git a/crates/accelerate/src/isometry.rs b/crates/accelerate/src/isometry.rs index a3a8be38dae2..ceaba2946b3a 100644 --- a/crates/accelerate/src/isometry.rs +++ b/crates/accelerate/src/isometry.rs @@ -24,6 +24,7 @@ use ndarray::prelude::*; use numpy::{IntoPyArray, PyReadonlyArray1, PyReadonlyArray2}; use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY; +use qiskit_circuit::util::C_ZERO; /// Find special unitary matrix that maps [c0,c1] to [r,0] or [0,r] if basis_state=0 or /// basis_state=1 respectively @@ -315,11 +316,7 @@ pub fn merge_ucgate_and_diag( .enumerate() .map(|(i, raw_gate)| { let gate = raw_gate.as_array(); - let res = aview2(&[ - [diag[2 * i], Complex64::new(0., 0.)], - [Complex64::new(0., 0.), diag[2 * i + 1]], - ]) - .dot(&gate); + let res = aview2(&[[diag[2 * i], C_ZERO], [C_ZERO, diag[2 * i + 1]]]).dot(&gate); res.into_pyarray_bound(py).into() }) .collect() diff --git a/crates/accelerate/src/pauli_exp_val.rs b/crates/accelerate/src/pauli_exp_val.rs index 52a2fc07f81d..8ee4b019b3e0 100644 --- a/crates/accelerate/src/pauli_exp_val.rs +++ b/crates/accelerate/src/pauli_exp_val.rs @@ -19,6 +19,7 @@ use pyo3::wrap_pyfunction; use rayon::prelude::*; use crate::getenv_use_multiple_threads; +use qiskit_circuit::util::c64; const PARALLEL_THRESHOLD: usize = 19; @@ -88,7 +89,7 @@ pub fn expval_pauli_with_x( let index_0 = ((i << 1) & mask_u) | (i & mask_l); let index_1 = index_0 ^ x_mask; let val_0 = (phase - * Complex64::new( + * c64( data_arr[index_1].re * data_arr[index_0].re + data_arr[index_1].im * data_arr[index_0].im, data_arr[index_1].im * data_arr[index_0].re @@ -96,7 +97,7 @@ pub fn expval_pauli_with_x( )) .re; let val_1 = (phase - * Complex64::new( + * c64( data_arr[index_0].re * data_arr[index_1].re + data_arr[index_0].im * data_arr[index_1].im, data_arr[index_0].im * data_arr[index_1].re diff --git a/crates/accelerate/src/sampled_exp_val.rs b/crates/accelerate/src/sampled_exp_val.rs index b51ca3c98f0e..0b8836a94165 100644 --- a/crates/accelerate/src/sampled_exp_val.rs +++ b/crates/accelerate/src/sampled_exp_val.rs @@ -18,6 +18,7 @@ use pyo3::prelude::*; use pyo3::wrap_pyfunction; use crate::pauli_exp_val::fast_sum; +use qiskit_circuit::util::c64; const OPER_TABLE_SIZE: usize = (b'Z' as usize) + 1; const fn generate_oper_table() -> [[f64; 2]; OPER_TABLE_SIZE] { @@ -81,7 +82,7 @@ pub fn sampled_expval_complex( let out: Complex64 = oper_strs .into_iter() .enumerate() - .map(|(idx, string)| coeff_arr[idx] * Complex64::new(bitstring_expval(&dist, string), 0.)) + .map(|(idx, string)| coeff_arr[idx] * c64(bitstring_expval(&dist, string), 0.)) .sum(); Ok(out.re) } diff --git a/crates/accelerate/src/sparse_pauli_op.rs b/crates/accelerate/src/sparse_pauli_op.rs index e0c80f716161..8a51d8ee781c 100644 --- a/crates/accelerate/src/sparse_pauli_op.rs +++ b/crates/accelerate/src/sparse_pauli_op.rs @@ -23,6 +23,7 @@ use hashbrown::HashMap; use ndarray::{s, Array1, Array2, ArrayView1, ArrayView2, Axis}; use num_complex::Complex64; use num_traits::Zero; +use qiskit_circuit::util::{c64, C_ONE, C_ZERO}; use rayon::prelude::*; use crate::rayon_ext::*; @@ -257,9 +258,9 @@ impl<'py> ZXPaulisView<'py> { let ys = (xs & zs).count_ones(); match (phase as u32 + ys) % 4 { 0 => coeff, - 1 => Complex64::new(coeff.im, -coeff.re), - 2 => Complex64::new(-coeff.re, -coeff.im), - 3 => Complex64::new(-coeff.im, coeff.re), + 1 => c64(coeff.im, -coeff.re), + 2 => c64(-coeff.re, -coeff.im), + 3 => c64(-coeff.im, coeff.re), _ => unreachable!(), } }) @@ -311,10 +312,10 @@ impl MatrixCompressedPaulis { .zip(self.z_like.drain(..)) .zip(self.coeffs.drain(..)) { - *hash_table.entry(key).or_insert(Complex64::new(0.0, 0.0)) += coeff; + *hash_table.entry(key).or_insert(C_ZERO) += coeff; } for ((x, z), coeff) in hash_table { - if coeff == Complex64::new(0.0, 0.0) { + if coeff.is_zero() { continue; } self.x_like.push(x); @@ -347,7 +348,7 @@ pub fn decompose_dense( let mut coeffs = vec![]; if num_qubits > 0 { decompose_dense_inner( - Complex64::new(1.0, 0.0), + C_ONE, num_qubits, &[], operator.as_array(), @@ -532,7 +533,7 @@ fn to_matrix_dense_inner(paulis: &MatrixCompressedPaulis, parallel: bool) -> Vec // Doing the initialization here means that when we're in parallel contexts, we do the // zeroing across the whole threadpool. This also seems to give a speed-up in serial // contexts, but I don't understand that. ---Jake - row.fill(Complex64::new(0.0, 0.0)); + row.fill(C_ZERO); for ((&x_like, &z_like), &coeff) in paulis .x_like .iter() @@ -667,7 +668,7 @@ macro_rules! impl_to_matrix_sparse { ((i_row as $uint_ty) ^ (paulis.x_like[a] as $uint_ty)) .cmp(&((i_row as $uint_ty) ^ (paulis.x_like[b] as $uint_ty))) }); - let mut running = Complex64::new(0.0, 0.0); + let mut running = C_ZERO; let mut prev_index = i_row ^ (paulis.x_like[order[0]] as usize); for (x_like, z_like, coeff) in order .iter() @@ -748,7 +749,7 @@ macro_rules! impl_to_matrix_sparse { (i_row as $uint_ty ^ paulis.x_like[a] as $uint_ty) .cmp(&(i_row as $uint_ty ^ paulis.x_like[b] as $uint_ty)) }); - let mut running = Complex64::new(0.0, 0.0); + let mut running = C_ZERO; let mut prev_index = i_row ^ (paulis.x_like[order[0]] as usize); for (x_like, z_like, coeff) in order .iter() @@ -844,11 +845,11 @@ mod tests { // Deliberately using multiples of small powers of two so the floating-point addition // of them is associative. coeffs: vec![ - Complex64::new(0.25, 0.5), - Complex64::new(0.125, 0.25), - Complex64::new(0.375, 0.125), - Complex64::new(-0.375, 0.0625), - Complex64::new(-0.5, -0.25), + c64(0.25, 0.5), + c64(0.125, 0.25), + c64(0.375, 0.125), + c64(-0.375, 0.0625), + c64(-0.5, -0.25), ], } } diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index e8c572b04039..8637cb03c735 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -52,67 +52,28 @@ use rand_distr::StandardNormal; use rand_pcg::Pcg64Mcg; use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE}; +use qiskit_circuit::util::{c64, GateArray1Q, GateArray2Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM}; use qiskit_circuit::SliceOrInt; -const PI2: f64 = PI / 2.0; -const PI4: f64 = PI / 4.0; +const PI2: f64 = PI / 2.; +const PI4: f64 = PI / 4.; const PI32: f64 = 3.0 * PI2; const TWO_PI: f64 = 2.0 * PI; const C1: c64 = c64 { re: 1.0, im: 0.0 }; -static B_NON_NORMALIZED: [[Complex64; 4]; 4] = [ - [ - Complex64::new(1.0, 0.), - Complex64::new(0., 1.), - Complex64::new(0., 0.), - Complex64::new(0., 0.), - ], - [ - Complex64::new(0., 0.), - Complex64::new(0., 0.), - Complex64::new(0., 1.), - Complex64::new(1.0, 0.0), - ], - [ - Complex64::new(0., 0.), - Complex64::new(0., 0.), - Complex64::new(0., 1.), - Complex64::new(-1., 0.), - ], - [ - Complex64::new(1., 0.), - Complex64::new(0., -1.), - Complex64::new(0., 0.), - Complex64::new(0., 0.), - ], +static B_NON_NORMALIZED: GateArray2Q = [ + [C_ONE, IM, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, IM, C_ONE], + [C_ZERO, C_ZERO, IM, C_M_ONE], + [C_ONE, M_IM, C_ZERO, C_ZERO], ]; -static B_NON_NORMALIZED_DAGGER: [[Complex64; 4]; 4] = [ - [ - Complex64::new(0.5, 0.), - Complex64::new(0., 0.), - Complex64::new(0., 0.), - Complex64::new(0.5, 0.0), - ], - [ - Complex64::new(0., -0.5), - Complex64::new(0., 0.), - Complex64::new(0., 0.), - Complex64::new(0., 0.5), - ], - [ - Complex64::new(0., 0.), - Complex64::new(0., -0.5), - Complex64::new(0., -0.5), - Complex64::new(0., 0.), - ], - [ - Complex64::new(0., 0.), - Complex64::new(0.5, 0.), - Complex64::new(-0.5, 0.), - Complex64::new(0., 0.), - ], +static B_NON_NORMALIZED_DAGGER: GateArray2Q = [ + [c64(0.5, 0.), C_ZERO, C_ZERO, c64(0.5, 0.)], + [c64(0., -0.5), C_ZERO, C_ZERO, c64(0., 0.5)], + [C_ZERO, c64(0., -0.5), c64(0., -0.5), C_ZERO], + [C_ZERO, c64(0.5, 0.), c64(-0.5, 0.), C_ZERO], ]; enum MagicBasisTransform { @@ -318,29 +279,26 @@ fn closest_partial_swap(a: f64, b: f64, c: f64) -> f64 { fn rx_matrix(theta: f64) -> Array2 { let half_theta = theta / 2.; - let cos = Complex64::new(half_theta.cos(), 0.); - let isin = Complex64::new(0., -half_theta.sin()); + let cos = c64(half_theta.cos(), 0.); + let isin = c64(0., -half_theta.sin()); array![[cos, isin], [isin, cos]] } fn ry_matrix(theta: f64) -> Array2 { let half_theta = theta / 2.; - let cos = Complex64::new(half_theta.cos(), 0.); - let sin = Complex64::new(half_theta.sin(), 0.); + let cos = c64(half_theta.cos(), 0.); + let sin = c64(half_theta.sin(), 0.); array![[cos, -sin], [sin, cos]] } fn rz_matrix(theta: f64) -> Array2 { - let ilam2 = Complex64::new(0., 0.5 * theta); - array![ - [(-ilam2).exp(), Complex64::new(0., 0.)], - [Complex64::new(0., 0.), ilam2.exp()] - ] + let ilam2 = c64(0., 0.5 * theta); + array![[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] } fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2 { let identity = aview2(&ONE_QUBIT_IDENTITY); - let phase = Complex64::new(0., global_phase).exp(); + let phase = c64(0., global_phase).exp(); let mut matrix = Array2::from_diag(&arr1(&[phase, phase, phase, phase])); sequence .iter() @@ -375,7 +333,6 @@ fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2< } const DEFAULT_FIDELITY: f64 = 1.0 - 1.0e-9; -const C1_IM: Complex64 = Complex64::new(0.0, 1.0); #[derive(Clone, Debug, Copy)] #[pyclass(module = "qiskit._accelerate.two_qubit_decompose")] @@ -500,18 +457,9 @@ impl TwoQubitWeylDecomposition { } } -static IPZ: [[Complex64; 2]; 2] = [ - [C1_IM, Complex64::new(0., 0.)], - [Complex64::new(0., 0.), Complex64::new(0., -1.)], -]; -static IPY: [[Complex64; 2]; 2] = [ - [Complex64::new(0., 0.), Complex64::new(1., 0.)], - [Complex64::new(-1., 0.), Complex64::new(0., 0.)], -]; -static IPX: [[Complex64; 2]; 2] = [ - [Complex64::new(0., 0.), C1_IM], - [C1_IM, Complex64::new(0., 0.)], -]; +static IPZ: GateArray1Q = [[IM, C_ZERO], [C_ZERO, M_IM]]; +static IPY: GateArray1Q = [[C_ZERO, C_ONE], [C_M_ONE, C_ZERO]]; +static IPX: GateArray1Q = [[C_ZERO, IM], [IM, C_ZERO]]; #[pymethods] impl TwoQubitWeylDecomposition { @@ -671,7 +619,7 @@ impl TwoQubitWeylDecomposition { temp.diag_mut() .iter_mut() .enumerate() - .for_each(|(index, x)| *x = (C1_IM * d[index]).exp()); + .for_each(|(index, x)| *x = (IM * d[index]).exp()); let k1 = magic_basis_transform(u_p.dot(&p).dot(&temp).view(), MagicBasisTransform::Into); let k2 = magic_basis_transform(p.t(), MagicBasisTransform::Into); @@ -737,7 +685,7 @@ impl TwoQubitWeylDecomposition { let is_close = |ap: f64, bp: f64, cp: f64| -> bool { let [da, db, dc] = [a - ap, b - bp, c - cp]; let tr = 4. - * Complex64::new( + * c64( da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ); @@ -1016,13 +964,13 @@ impl TwoQubitWeylDecomposition { b - specialized.b, -c - specialized.c, ]; - 4. * Complex64::new( + 4. * c64( da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ) } else { let [da, db, dc] = [a - specialized.a, b - specialized.b, c - specialized.c]; - 4. * Complex64::new( + 4. * c64( da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ) @@ -1597,20 +1545,14 @@ impl TwoQubitBasisDecomposer { } } -static K12R_ARR: [[Complex64; 2]; 2] = [ - [ - Complex64::new(0., FRAC_1_SQRT_2), - Complex64::new(FRAC_1_SQRT_2, 0.), - ], - [ - Complex64::new(-FRAC_1_SQRT_2, 0.), - Complex64::new(0., -FRAC_1_SQRT_2), - ], +static K12R_ARR: GateArray1Q = [ + [c64(0., FRAC_1_SQRT_2), c64(FRAC_1_SQRT_2, 0.)], + [c64(-FRAC_1_SQRT_2, 0.), c64(0., -FRAC_1_SQRT_2)], ]; -static K12L_ARR: [[Complex64; 2]; 2] = [ - [Complex64::new(0.5, 0.5), Complex64::new(0.5, 0.5)], - [Complex64::new(-0.5, 0.5), Complex64::new(0.5, -0.5)], +static K12L_ARR: GateArray1Q = [ + [c64(0.5, 0.5), c64(0.5, 0.5)], + [c64(-0.5, 0.5), c64(0.5, -0.5)], ]; fn decomp0_inner(target: &TwoQubitWeylDecomposition) -> SmallVec<[Array2; 8]> { @@ -1650,90 +1592,71 @@ impl TwoQubitBasisDecomposer { // Create some useful matrices U1, U2, U3 are equivalent to the basis, // expand as Ui = Ki1.Ubasis.Ki2 let b = basis_decomposer.b; - let temp = Complex64::new(0.5, -0.5); + let temp = c64(0.5, -0.5); let k11l = array![ - [ - temp * (Complex64::new(0., -1.) * Complex64::new(0., -b).exp()), - temp * Complex64::new(0., -b).exp() - ], - [ - temp * (Complex64::new(0., -1.) * Complex64::new(0., b).exp()), - temp * -(Complex64::new(0., b).exp()) - ], + [temp * (M_IM * c64(0., -b).exp()), temp * c64(0., -b).exp()], + [temp * (M_IM * c64(0., b).exp()), temp * -(c64(0., b).exp())], ]; let k11r = array![ [ - FRAC_1_SQRT_2 * (Complex64::new(0., 1.) * Complex64::new(0., -b).exp()), - FRAC_1_SQRT_2 * -Complex64::new(0., -b).exp() + FRAC_1_SQRT_2 * (IM * c64(0., -b).exp()), + FRAC_1_SQRT_2 * -c64(0., -b).exp() ], [ - FRAC_1_SQRT_2 * Complex64::new(0., b).exp(), - FRAC_1_SQRT_2 * (Complex64::new(0., -1.) * Complex64::new(0., b).exp()) + FRAC_1_SQRT_2 * c64(0., b).exp(), + FRAC_1_SQRT_2 * (M_IM * c64(0., b).exp()) ], ]; let k12l = aview2(&K12L_ARR); let k12r = aview2(&K12R_ARR); let k32l_k21l = array![ [ - FRAC_1_SQRT_2 * Complex64::new(1., (2. * b).cos()), - FRAC_1_SQRT_2 * (Complex64::new(0., 1.) * (2. * b).sin()) + FRAC_1_SQRT_2 * c64(1., (2. * b).cos()), + FRAC_1_SQRT_2 * (IM * (2. * b).sin()) ], [ - FRAC_1_SQRT_2 * (Complex64::new(0., 1.) * (2. * b).sin()), - FRAC_1_SQRT_2 * Complex64::new(1., -(2. * b).cos()) + FRAC_1_SQRT_2 * (IM * (2. * b).sin()), + FRAC_1_SQRT_2 * c64(1., -(2. * b).cos()) ], ]; - let temp = Complex64::new(0.5, 0.5); + let temp = c64(0.5, 0.5); let k21r = array![ [ - temp * (Complex64::new(0., -1.) * Complex64::new(0., -2. * b).exp()), - temp * Complex64::new(0., -2. * b).exp() + temp * (M_IM * c64(0., -2. * b).exp()), + temp * c64(0., -2. * b).exp() ], [ - temp * (Complex64::new(0., 1.) * Complex64::new(0., 2. * b).exp()), - temp * Complex64::new(0., 2. * b).exp() + temp * (IM * c64(0., 2. * b).exp()), + temp * c64(0., 2. * b).exp() ], ]; - const K22L_ARR: [[Complex64; 2]; 2] = [ - [ - Complex64::new(FRAC_1_SQRT_2, 0.), - Complex64::new(-FRAC_1_SQRT_2, 0.), - ], - [ - Complex64::new(FRAC_1_SQRT_2, 0.), - Complex64::new(FRAC_1_SQRT_2, 0.), - ], + const K22L_ARR: 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.)], ]; let k22l = aview2(&K22L_ARR); - let k22r_arr: [[Complex64; 2]; 2] = [ - [Complex64::zero(), Complex64::new(1., 0.)], - [Complex64::new(-1., 0.), Complex64::zero()], - ]; + let k22r_arr: GateArray1Q = [[Complex64::zero(), C_ONE], [C_M_ONE, Complex64::zero()]]; let k22r = aview2(&k22r_arr); let k31l = array![ [ - FRAC_1_SQRT_2 * Complex64::new(0., -b).exp(), - FRAC_1_SQRT_2 * Complex64::new(0., -b).exp() + FRAC_1_SQRT_2 * c64(0., -b).exp(), + FRAC_1_SQRT_2 * c64(0., -b).exp() ], [ - FRAC_1_SQRT_2 * -Complex64::new(0., b).exp(), - FRAC_1_SQRT_2 * Complex64::new(0., b).exp() + FRAC_1_SQRT_2 * -c64(0., b).exp(), + FRAC_1_SQRT_2 * c64(0., b).exp() ], ]; - let temp = Complex64::new(0., 1.); let k31r = array![ - [temp * Complex64::new(0., b).exp(), Complex64::zero()], - [Complex64::zero(), temp * -Complex64::new(0., -b).exp()], + [IM * c64(0., b).exp(), Complex64::zero()], + [Complex64::zero(), M_IM * c64(0., -b).exp()], ]; - let temp = Complex64::new(0.5, 0.5); + let temp = c64(0.5, 0.5); let k32r = array![ + [temp * c64(0., b).exp(), temp * -c64(0., -b).exp()], [ - temp * Complex64::new(0., b).exp(), - temp * -Complex64::new(0., -b).exp() - ], - [ - temp * (Complex64::new(0., -1.) * Complex64::new(0., b).exp()), - temp * (Complex64::new(0., -1.) * Complex64::new(0., -b).exp()) + temp * (M_IM * c64(0., b).exp()), + temp * (M_IM * c64(0., -b).exp()) ], ]; let k1ld = transpose_conjugate(basis_decomposer.K1l.view()); @@ -1793,11 +1716,11 @@ impl TwoQubitBasisDecomposer { fn traces(&self, target: &TwoQubitWeylDecomposition) -> [Complex64; 4] { [ - 4. * Complex64::new( + 4. * c64( target.a.cos() * target.b.cos() * target.c.cos(), target.a.sin() * target.b.sin() * target.c.sin(), ), - 4. * Complex64::new( + 4. * c64( (PI4 - target.a).cos() * (self.basis_decomposer.b - target.b).cos() * target.c.cos(), @@ -1805,8 +1728,8 @@ impl TwoQubitBasisDecomposer { * (self.basis_decomposer.b - target.b).sin() * target.c.sin(), ), - Complex64::new(4. * target.c.cos(), 0.), - Complex64::new(4., 0.), + c64(4. * target.c.cos(), 0.), + c64(4., 0.), ] } diff --git a/crates/accelerate/src/uc_gate.rs b/crates/accelerate/src/uc_gate.rs index 3a5f74a6f0b1..21fd7fa04656 100644 --- a/crates/accelerate/src/uc_gate.rs +++ b/crates/accelerate/src/uc_gate.rs @@ -21,14 +21,14 @@ use ndarray::prelude::*; use numpy::{IntoPyArray, PyReadonlyArray2}; use crate::euler_one_qubit_decomposer::det_one_qubit; +use qiskit_circuit::util::{c64, C_ZERO, IM}; -const PI2: f64 = PI / 2.; const EPS: f64 = 1e-10; // These constants are the non-zero elements of an RZ gate's unitary with an // angle of pi / 2 -const RZ_PI2_11: Complex64 = Complex64::new(FRAC_1_SQRT_2, -FRAC_1_SQRT_2); -const RZ_PI2_00: Complex64 = Complex64::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2); +const RZ_PI2_11: Complex64 = c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2); +const RZ_PI2_00: Complex64 = c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2); /// This method implements the decomposition given in equation (3) in /// https://arxiv.org/pdf/quant-ph/0410066.pdf. @@ -48,10 +48,10 @@ fn demultiplex_single_uc( let x11 = x[[0, 0]] / det_x.sqrt(); let phi = det_x.arg(); - let r1 = (Complex64::new(0., 1.) / 2. * (PI2 - phi / 2. - x11.arg())).exp(); - let r2 = (Complex64::new(0., 1.) / 2. * (PI2 - phi / 2. + x11.arg() + PI)).exp(); + let r1 = (IM / 2. * (PI / 2. - phi / 2. - x11.arg())).exp(); + let r2 = (IM / 2. * (PI / 2. - phi / 2. + x11.arg() + PI)).exp(); - let r = array![[r1, Complex64::new(0., 0.)], [Complex64::new(0., 0.), r2],]; + let r = array![[r1, C_ZERO], [C_ZERO, r2],]; let decomp = r .dot(&x) @@ -67,7 +67,7 @@ fn demultiplex_single_uc( // If d is not equal to diag(i,-i), then we put it into this "standard" form // (see eq. (13) in https://arxiv.org/pdf/quant-ph/0410066.pdf) by interchanging // the eigenvalues and eigenvectors - if (diag[0] + Complex64::new(0., 1.)).abs() < EPS { + if (diag[0] + IM).abs() < EPS { diag = diag.slice(s![..;-1]).to_owned(); u = u.slice(s![.., ..;-1]).to_owned(); } diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 2a3fcdf88289..2f085ea79c0a 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -10,22 +10,16 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use num_complex::Complex64; use std::f64::consts::FRAC_1_SQRT_2; -// num-complex exposes an equivalent function but it's not a const function -// so it's not compatible with static definitions. This is a const func and -// just reduces the amount of typing we need. -#[inline(always)] -const fn c64(re: f64, im: f64) -> Complex64 { - Complex64::new(re, im) -} +use crate::util::{ + c64, GateArray0Q, GateArray1Q, GateArray2Q, GateArray3Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM, +}; -pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = - [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(1., 0.)]]; +pub static ONE_QUBIT_IDENTITY: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_ONE]]; #[inline] -pub fn r_gate(theta: f64, phi: f64) -> [[Complex64; 2]; 2] { +pub fn r_gate(theta: f64, phi: f64) -> GateArray1Q { let half_theta = theta / 2.; let cost = c64(half_theta.cos(), 0.); let sint = half_theta.sin(); @@ -38,7 +32,7 @@ pub fn r_gate(theta: f64, phi: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn rx_gate(theta: f64) -> [[Complex64; 2]; 2] { +pub fn rx_gate(theta: f64) -> GateArray1Q { let half_theta = theta / 2.; let cos = c64(half_theta.cos(), 0.); let isin = c64(0., -half_theta.sin()); @@ -46,7 +40,7 @@ pub fn rx_gate(theta: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn ry_gate(theta: f64) -> [[Complex64; 2]; 2] { +pub fn ry_gate(theta: f64) -> GateArray1Q { let half_theta = theta / 2.; let cos = c64(half_theta.cos(), 0.); let sin = c64(half_theta.sin(), 0.); @@ -54,213 +48,150 @@ pub fn ry_gate(theta: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn rz_gate(theta: f64) -> [[Complex64; 2]; 2] { +pub fn rz_gate(theta: f64) -> GateArray1Q { let ilam2 = c64(0., 0.5 * theta); - [[(-ilam2).exp(), c64(0., 0.)], [c64(0., 0.), ilam2.exp()]] + [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] } -pub static H_GATE: [[Complex64; 2]; 2] = [ +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.)], ]; -pub static CX_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], +pub static CX_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], ]; -pub static SX_GATE: [[Complex64; 2]; 2] = [ +pub static SX_GATE: GateArray1Q = [ [c64(0.5, 0.5), c64(0.5, -0.5)], [c64(0.5, -0.5), c64(0.5, 0.5)], ]; -pub static SXDG_GATE: [[Complex64; 2]; 2] = [ +pub static SXDG_GATE: GateArray1Q = [ [c64(0.5, -0.5), c64(0.5, 0.5)], [c64(0.5, 0.5), c64(0.5, -0.5)], ]; -pub static X_GATE: [[Complex64; 2]; 2] = [[c64(0., 0.), c64(1., 0.)], [c64(1., 0.), c64(0., 0.)]]; +pub static X_GATE: GateArray1Q = [[C_ZERO, C_ONE], [C_ONE, C_ZERO]]; -pub static Z_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(-1., 0.)]]; +pub static Z_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_M_ONE]]; -pub static Y_GATE: [[Complex64; 2]; 2] = [[c64(0., 0.), c64(0., -1.)], [c64(0., 1.), c64(0., 0.)]]; +pub static Y_GATE: GateArray1Q = [[C_ZERO, M_IM], [IM, C_ZERO]]; -pub static CZ_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [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., 0.), c64(0., 0.), c64(0., 0.), c64(-1., 0.)], +pub static CZ_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_M_ONE], ]; -pub static CY_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(0., -1.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 1.), c64(0., 0.), c64(0., 0.)], +pub static CY_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, M_IM], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, IM, C_ZERO, C_ZERO], ]; -pub static CCX_GATE: [[Complex64; 8]; 8] = [ +pub static CCX_GATE: GateArray3Q = [ [ - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, ], ]; -pub static ECR_GATE: [[Complex64; 4]; 4] = [ +pub static ECR_GATE: GateArray2Q = [ [ - c64(0., 0.), + C_ZERO, c64(FRAC_1_SQRT_2, 0.), - c64(0., 0.), + C_ZERO, c64(0., FRAC_1_SQRT_2), ], [ c64(FRAC_1_SQRT_2, 0.), - c64(0., 0.), + C_ZERO, c64(0., -FRAC_1_SQRT_2), - c64(0., 0.), + C_ZERO, ], [ - c64(0., 0.), + C_ZERO, c64(0., FRAC_1_SQRT_2), - c64(0., 0.), + C_ZERO, c64(FRAC_1_SQRT_2, 0.), ], [ c64(0., -FRAC_1_SQRT_2), - c64(0., 0.), + C_ZERO, c64(FRAC_1_SQRT_2, 0.), - c64(0., 0.), + C_ZERO, ], ]; -pub static SWAP_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], +pub static SWAP_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; -pub static ISWAP_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 1.), c64(0., 0.)], - [c64(0., 0.), c64(0., 1.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], +pub static ISWAP_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, IM, C_ZERO], + [C_ZERO, IM, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; -pub static S_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., 1.)]]; +pub static S_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, IM]]; -pub static SDG_GATE: [[Complex64; 2]; 2] = - [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., -1.)]]; +pub static SDG_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, M_IM]]; -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 T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, 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)], +pub static TDG_GATE: GateArray1Q = [ + [C_ONE, C_ZERO], + [C_ZERO, c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], ]; -pub static DCX_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], - [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], +pub static DCX_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], ]; #[inline] -pub fn global_phase_gate(theta: f64) -> [[Complex64; 1]; 1] { +pub fn global_phase_gate(theta: f64) -> GateArray0Q { [[c64(0., theta).exp()]] } #[inline] -pub fn phase_gate(lam: f64) -> [[Complex64; 2]; 2] { - [ - [c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., lam).exp()], - ] +pub fn phase_gate(lam: f64) -> GateArray1Q { + [[C_ONE, C_ZERO], [C_ZERO, c64(0., lam).exp()]] } #[inline] -pub fn u_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { +pub fn u_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { let cos = (theta / 2.).cos(); let sin = (theta / 2.).sin(); [ @@ -270,37 +201,34 @@ pub fn u_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { +pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { let cos = (theta / 2.).cos(); let sin = (theta / 2.).sin(); [ [ c64(cos, 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, + C_ZERO, 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.)], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], [ c64(0., -sin) * c64(0., beta).exp(), - c64(0., 0.), - c64(0., 0.), + C_ZERO, + C_ZERO, c64(cos, 0.), ], ] } #[inline] -pub fn u1_gate(lam: f64) -> [[Complex64; 2]; 2] { - [ - [c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., lam).exp()], - ] +pub fn u1_gate(lam: f64) -> GateArray1Q { + [[C_ONE, C_ZERO], [C_ZERO, c64(0., lam).exp()]] } #[inline] -pub fn u2_gate(phi: f64, lam: f64) -> [[Complex64; 2]; 2] { +pub fn u2_gate(phi: f64, lam: f64) -> GateArray1Q { [ [ c64(FRAC_1_SQRT_2, 0.), @@ -314,7 +242,7 @@ pub fn u2_gate(phi: f64, lam: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { +pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { let cos = (theta / 2.).cos(); let sin = (theta / 2.).sin(); [ @@ -324,23 +252,23 @@ pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { +pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { let cos = (theta / 2.).cos(); let sin = (theta / 2.).sin(); [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], + [C_ONE, C_ZERO, C_ZERO, C_ZERO], [ - c64(0., 0.), + C_ZERO, c64(cos, 0.), c64(0., -sin) * c64(0., -beta).exp(), - c64(0., 0.), + C_ZERO, ], [ - c64(0., 0.), + C_ZERO, c64(0., -sin) * c64(0., beta).exp(), c64(cos, 0.), - c64(0., 0.), + C_ZERO, ], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], ] } diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index d7f285911750..9fcaa36480cf 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -17,6 +17,7 @@ pub mod gate_matrix; pub mod imports; pub mod operations; pub mod parameter_table; +pub mod util; mod bit_data; mod interner; diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index e0e93726735d..ff730744c80f 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -24,9 +24,6 @@ use pyo3::prelude::*; use pyo3::{intern, IntoPy, Python}; use smallvec::smallvec; -const PI2: f64 = PI / 2.0; -const PI4: f64 = PI / 4.0; - /// Valid types for an operation field in a CircuitInstruction /// /// These are basically the types allowed in a QuantumCircuit @@ -563,7 +560,11 @@ impl Operation for StandardGate { 1, [( Self::UGate, - smallvec![Param::Float(PI), Param::Float(PI2), Param::Float(PI2),], + smallvec![ + Param::Float(PI), + Param::Float(PI / 2.), + Param::Float(PI / 2.), + ], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -732,7 +733,7 @@ impl Operation for StandardGate { 1, [( Self::UGate, - smallvec![Param::Float(PI2), Param::Float(0.), Param::Float(PI)], + smallvec![Param::Float(PI / 2.), Param::Float(0.), Param::Float(PI)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -763,7 +764,7 @@ impl Operation for StandardGate { 1, [( Self::PhaseGate, - smallvec![Param::Float(PI2)], + smallvec![Param::Float(PI / 2.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -793,7 +794,7 @@ impl Operation for StandardGate { 1, [( Self::PhaseGate, - smallvec![Param::Float(-PI2)], + smallvec![Param::Float(-PI / 2.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -823,7 +824,7 @@ impl Operation for StandardGate { 1, [( Self::PhaseGate, - smallvec![Param::Float(PI4)], + smallvec![Param::Float(PI / 4.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -853,7 +854,7 @@ impl Operation for StandardGate { 1, [( Self::PhaseGate, - smallvec![Param::Float(-PI4)], + smallvec![Param::Float(-PI / 4.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -895,9 +896,9 @@ impl Operation for StandardGate { smallvec![multiply_param(beta, -1.0, py)], q1.clone(), ), - (Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), (Self::SXGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(PI2)], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0.clone()), (Self::SGate, smallvec![], q1.clone()), (Self::CXGate, smallvec![], q0_1.clone()), ( @@ -912,9 +913,9 @@ impl Operation for StandardGate { ), (Self::CXGate, smallvec![], q0_1), (Self::SdgGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), (Self::SXdgGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(PI2)], q0), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0), (Self::RZGate, smallvec![beta.clone()], q1), ], FLOAT_ZERO, @@ -934,9 +935,9 @@ impl Operation for StandardGate { 2, [ (Self::RZGate, smallvec![beta.clone()], q0.clone()), - (Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), (Self::SXGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(PI2)], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1.clone()), (Self::SGate, smallvec![], q0.clone()), (Self::CXGate, smallvec![], q1_0.clone()), ( @@ -951,9 +952,9 @@ impl Operation for StandardGate { ), (Self::CXGate, smallvec![], q1_0), (Self::SdgGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), (Self::SXdgGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(PI2)], q1), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1), (Self::RZGate, smallvec![multiply_param(beta, -1.0, py)], q0), ], FLOAT_ZERO, @@ -964,7 +965,7 @@ 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 = add_param(¶ms[1], -PI2, py); + let phi_expr1 = add_param(¶ms[1], -PI / 2., py); let phi_expr2 = multiply_param(&phi_expr1, -1.0, py); let defparams = smallvec![theta_expr, phi_expr1, phi_expr2]; Some( diff --git a/crates/circuit/src/util.rs b/crates/circuit/src/util.rs new file mode 100644 index 000000000000..11562b0a48cd --- /dev/null +++ b/crates/circuit/src/util.rs @@ -0,0 +1,48 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use num_complex::Complex64; + +// This is a very conservative version of an abbreviation for constructing new Complex64. +// A couple of alternatives to this function are +// `c64, V: Into>(re: T, im: V) -> Complex64` +// Disadvantages are: +// 1. Some people don't like that this allows things like `c64(1, 0)`. Presumably, +// they prefer a more explicit construction. +// 2. This will not work in `const` and `static` constructs. +// Another alternative is +// macro_rules! c64 { +// ($re: expr, $im: expr $(,)*) => { +// Complex64::new($re as f64, $im as f64) +// }; +// Advantages: This allows things like `c64!(1, 2.0)`, including in +// `static` and `const` constructs. +// Disadvantages: +// 1. Three characters `c64!` rather than two `c64`. +// 2. Some people prefer the opposite of the advantages, i.e. more explicitness. +/// Create a new [`Complex`] +#[inline(always)] +pub const fn c64(re: f64, im: f64) -> Complex64 { + Complex64::new(re, im) +} + +pub type GateArray0Q = [[Complex64; 1]; 1]; +pub type GateArray1Q = [[Complex64; 2]; 2]; +pub type GateArray2Q = [[Complex64; 4]; 4]; +pub type GateArray3Q = [[Complex64; 8]; 8]; + +// Use prefix `C_` to distinguish from real, for example +pub const C_ZERO: Complex64 = c64(0., 0.); +pub const C_ONE: Complex64 = c64(1., 0.); +pub const C_M_ONE: Complex64 = c64(-1., 0.); +pub const IM: Complex64 = c64(0., 1.); +pub const M_IM: Complex64 = c64(0., -1.); From 3adcd5d3dfa8f67cb4ebb37f99a0d388942602af Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 28 Jun 2024 14:36:32 +0100 Subject: [PATCH 05/17] Suppress nonsense `DeprecationWarning` caused by `unittest` (#12676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Suppress nonsense `DeprecationWarning` caused by `unittest` `unittest.TestCase.assertWarns` in context-manager form has an awkward habit of querying the `__warningregistry__` attribute on every module in existence. This interacts poorly with a Numpy 2 deprecation warning trigger for code that's attempting to import functions from modules that became private in Numpy 2, if a warning has previously been triggered out of `numpy.linalg._linalg`. This simply suppresses that particular warning from the test suite. * Refine filter * Pin Rustworkx to avoid buggy graphviz drawing * Update test/utils/base.py Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- constraints.txt | 4 ++++ test/utils/base.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/constraints.txt b/constraints.txt index 6681de226d93..8c561f52f0f6 100644 --- a/constraints.txt +++ b/constraints.txt @@ -7,6 +7,10 @@ scipy<1.11; python_version<'3.12' # See https://github.com/Qiskit/qiskit/issues/12655 for current details. scipy==1.13.1; python_version=='3.12' +# Rustworkx 0.15.0 contains a bug that breaks graphviz-related tests. +# See https://github.com/Qiskit/rustworkx/pull/1229 for the fix. +rustworkx==0.14.2 + # z3-solver from 4.12.3 onwards upped the minimum macOS API version for its # wheels to 11.7. The Azure VM images contain pre-built CPythons, of which at # least CPython 3.8 was compiled for an older macOS, so does not match a diff --git a/test/utils/base.py b/test/utils/base.py index bebf03008858..ce9509709bad 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -204,6 +204,20 @@ def setUpClass(cls): warnings.filterwarnings("error", category=DeprecationWarning) warnings.filterwarnings("error", category=QiskitWarning) + # Numpy 2 made a few new modules private, and have warnings that trigger if you try to + # access attributes that _would_ have existed. Unfortunately, Python's `warnings` module + # adds a field called `__warningregistry__` to any module that triggers a warning, and + # `unittest.TestCase.assertWarns` then queries said fields on all existing modules. On + # macOS ARM, we see some (we think harmless) warnings come out of `numpy.linalg._linalg` (a + # now-private module) during transpilation, which means that subsequent `assertWarns` calls + # can spuriously trick Numpy into sending out a nonsense `DeprecationWarning`. + # Tracking issue: https://github.com/Qiskit/qiskit/issues/12679 + warnings.filterwarnings( + "ignore", + category=DeprecationWarning, + message=r".*numpy\.(\w+\.)*__warningregistry__", + ) + # We only use pandas transitively through seaborn, so it's their responsibility to mark if # their use of pandas would be a problem. warnings.filterwarnings( From 9b0a5849f637a25f938fcd118352cdfeae1a58e5 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Fri, 28 Jun 2024 17:34:30 +0200 Subject: [PATCH 06/17] Port CRX/Y/Z gates to Rust (#12648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * v0 of CR-Pauli gates * fix inevitable matrix typos * update multiply_param and prepare for U1/2/3 PR * fix num params/qubits * cct methods to append rust gates --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/gate_matrix.rs | 37 +++++++ crates/circuit/src/imports.rs | 6 +- crates/circuit/src/operations.rs | 110 ++++++++++++++++++-- qiskit/circuit/library/standard_gates/rx.py | 2 + qiskit/circuit/library/standard_gates/ry.py | 2 + qiskit/circuit/library/standard_gates/rz.py | 2 + qiskit/circuit/quantumcircuit.py | 18 ++++ 7 files changed, 168 insertions(+), 9 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 2f085ea79c0a..074b1c2ac682 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -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.)], diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index bf06685ba53b..530e635c94f1 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -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 diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index ff730744c80f..85192b63dbd7 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -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 ]; @@ -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 ]; @@ -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, @@ -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()) @@ -673,6 +684,94 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CRXGate => Python::with_gil(|py| -> Option { + let theta = ¶ms[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 { + let theta = ¶ms[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 { + let theta = ¶ms[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 { Some( @@ -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 { let theta_expr = clone_param(¶ms[0], py); let phi_expr1 = add_param(¶ms[1], -PI / 2., py); diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 5579f9d3707d..cb851a740d28 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -199,6 +199,8 @@ class CRXGate(ControlledGate): \end{pmatrix} """ + _standard_gate = StandardGate.CRXGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py index e27398cc2960..b60b34ffde6f 100644 --- a/qiskit/circuit/library/standard_gates/ry.py +++ b/qiskit/circuit/library/standard_gates/ry.py @@ -198,6 +198,8 @@ class CRYGate(ControlledGate): \end{pmatrix} """ + _standard_gate = StandardGate.CRYGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py index e8ee0f976036..78cf20efa5c6 100644 --- a/qiskit/circuit/library/standard_gates/rz.py +++ b/qiskit/circuit/library/standard_gates/rz.py @@ -216,6 +216,8 @@ class CRZGate(ControlledGate): phase difference. """ + _standard_gate = StandardGate.CRZGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 485591a8a3bb..7b8fe6e031f1 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -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]: + 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], @@ -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], @@ -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], From 24ee7c69b5acf9c0b10030c1f180299e6df5a4c7 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 28 Jun 2024 12:03:37 -0400 Subject: [PATCH 07/17] Fix clippy warnings on latest stable rust (#12675) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Rust 1.79.0 several new clippy rules were added and/or enabled by default. This was causing some new issues to be flagged when building qiskit with the this release of Rust. This commit fixes these issues flagged by clippy. Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/accelerate/src/dense_layout.rs | 2 +- crates/accelerate/src/nlayout.rs | 6 +++--- crates/accelerate/src/stochastic_swap.rs | 10 +++++----- crates/circuit/src/circuit_instruction.rs | 5 +---- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/accelerate/src/dense_layout.rs b/crates/accelerate/src/dense_layout.rs index 901a906d9c81..9529742d7e62 100644 --- a/crates/accelerate/src/dense_layout.rs +++ b/crates/accelerate/src/dense_layout.rs @@ -197,7 +197,7 @@ pub fn best_subset_inner( SubsetResult { count: 0, map: Vec::new(), - error: std::f64::INFINITY, + error: f64::INFINITY, subgraph: Vec::new(), } }; diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs index 1a0b73b25fed..b3709d2804bb 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -107,8 +107,8 @@ impl NLayout { physical_qubits: usize, ) -> Self { let mut res = NLayout { - virt_to_phys: vec![PhysicalQubit(std::u32::MAX); virtual_qubits], - phys_to_virt: vec![VirtualQubit(std::u32::MAX); physical_qubits], + virt_to_phys: vec![PhysicalQubit(u32::MAX); virtual_qubits], + phys_to_virt: vec![VirtualQubit(u32::MAX); physical_qubits], }; for (virt, phys) in qubit_indices { res.virt_to_phys[virt.index()] = phys; @@ -184,7 +184,7 @@ impl NLayout { #[staticmethod] pub fn from_virtual_to_physical(virt_to_phys: Vec) -> PyResult { - let mut phys_to_virt = vec![VirtualQubit(std::u32::MAX); virt_to_phys.len()]; + let mut phys_to_virt = vec![VirtualQubit(u32::MAX); virt_to_phys.len()]; for (virt, phys) in virt_to_phys.iter().enumerate() { phys_to_virt[phys.index()] = VirtualQubit(virt.try_into()?); } diff --git a/crates/accelerate/src/stochastic_swap.rs b/crates/accelerate/src/stochastic_swap.rs index bc13325d8d96..d4e3890b9ccb 100644 --- a/crates/accelerate/src/stochastic_swap.rs +++ b/crates/accelerate/src/stochastic_swap.rs @@ -112,10 +112,10 @@ fn swap_trial( let mut new_cost: f64; let mut dist: f64; - let mut optimal_start = PhysicalQubit::new(std::u32::MAX); - let mut optimal_end = PhysicalQubit::new(std::u32::MAX); - let mut optimal_start_qubit = VirtualQubit::new(std::u32::MAX); - let mut optimal_end_qubit = VirtualQubit::new(std::u32::MAX); + let mut optimal_start = PhysicalQubit::new(u32::MAX); + let mut optimal_end = PhysicalQubit::new(u32::MAX); + let mut optimal_start_qubit = VirtualQubit::new(u32::MAX); + let mut optimal_end_qubit = VirtualQubit::new(u32::MAX); let mut scale = Array2::zeros((num_qubits, num_qubits)); @@ -270,7 +270,7 @@ pub fn swap_trials( // unless force threads is set. let run_in_parallel = getenv_use_multiple_threads(); - let mut best_depth = std::usize::MAX; + let mut best_depth = usize::MAX; let mut best_edges: Option = None; let mut best_layout: Option = None; if run_in_parallel { diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 781a776c1566..74302b526d51 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -825,10 +825,7 @@ pub(crate) fn convert_py_to_operation_type( }; let op_type: Bound = raw_op_type.into_bound(py); let mut standard: Option = match op_type.getattr(attr) { - Ok(stdgate) => match stdgate.extract().ok() { - Some(gate) => gate, - None => None, - }, + Ok(stdgate) => stdgate.extract().ok().unwrap_or_default(), Err(_) => None, }; // If the input instruction is a standard gate and a singleton instance From 3af991856ff3cc6925093224d5eecf6798be5265 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:14:30 +0000 Subject: [PATCH 08/17] Bump num-bigint from 0.4.5 to 0.4.6 (#12681) Bumps [num-bigint](https://github.com/rust-num/num-bigint) from 0.4.5 to 0.4.6. - [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md) - [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.5...num-bigint-0.4.6) --- updated-dependencies: - dependency-name: num-bigint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 454823748e8d..68a3d3214060 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -795,9 +795,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", From 44fe59b04045c52f9031a5e8d91f8d5990f7d553 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 28 Jun 2024 20:39:56 +0100 Subject: [PATCH 09/17] Relax CI constraint on Rustworkx 0.15.0 (#12690) The release of Rustworkx 0.15.1 fixes the bug that was previously blocking CI. --- constraints.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/constraints.txt b/constraints.txt index 8c561f52f0f6..6681de226d93 100644 --- a/constraints.txt +++ b/constraints.txt @@ -7,10 +7,6 @@ scipy<1.11; python_version<'3.12' # See https://github.com/Qiskit/qiskit/issues/12655 for current details. scipy==1.13.1; python_version=='3.12' -# Rustworkx 0.15.0 contains a bug that breaks graphviz-related tests. -# See https://github.com/Qiskit/rustworkx/pull/1229 for the fix. -rustworkx==0.14.2 - # z3-solver from 4.12.3 onwards upped the minimum macOS API version for its # wheels to 11.7. The Azure VM images contain pre-built CPythons, of which at # least CPython 3.8 was compiled for an older macOS, so does not match a From e9208a6339becd95b3e5e3a28e593c61d71aeb54 Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Fri, 28 Jun 2024 23:18:17 +0300 Subject: [PATCH 10/17] binary matrices utils in rust (#12456) * gaussian elimination in rust * handle lint errors * replace python function by rust function for gauss elimination * change matrix elements type from bool to i8 * add parallelization in row operations * update matrices in place * change matrix type in rust code to bool * handle type in python code * update filter following review * remove parallelization using rayon * move _gauss_elimination_with_perm to rust * fix fmt error * simplify _gauss_elimination function * update _compute_rank_after_gauss_elim to rust * update _row_op and _col_op * transfer _row_op and _col_op from python to rust * fix code due to failing tests * minor update of types * move calc_inverse_matrix to rust, add _binary_matmul in rust * fix failing tests, by changing mat type from int to bool * update rust docstrings * add function _add_row_or_col to rust code * improve binary_matmul * proper error handling * unified format of function names * move compute_rank from python to rust, update errors * update type of mat in compute_rank * move random_invertible_binary_matrix and check_invertible_binary_matrix to rust * Updating HighLevelSynthesis tests that depend on the specific random number * Updating LinearSynthesis tests to pass seeds * Updating tests in test_linear_function * changing the matrix type in random dyhedral to be a matrix of ints rather than bools * updating cx_cz synthesis tests * updating clifford tests * remove unused imports * add option seed=None * enhance rust docs * add release notes * remove unnecessary copy in python * another copy fix * another copy fix * update rust docstrings * update release notes --------- Co-authored-by: AlexanderIvrii --- crates/accelerate/src/synthesis/linear/mod.rs | 190 +++++++++++++++++ .../accelerate/src/synthesis/linear/utils.rs | 200 ++++++++++++++++++ crates/accelerate/src/synthesis/mod.rs | 2 + qiskit/__init__.py | 1 + .../quantum_info/operators/dihedral/random.py | 3 +- .../clifford/clifford_decompose_layers.py | 28 +-- qiskit/synthesis/linear/__init__.py | 1 + qiskit/synthesis/linear/linear_depth_lnn.py | 14 +- .../synthesis/linear/linear_matrix_utils.py | 174 ++------------- .../stabilizer/stabilizer_decompose.py | 2 +- .../passes/synthesis/high_level_synthesis.py | 4 +- ...ry-matrix-utils-rust-c48b5577749c34ab.yaml | 8 + .../circuit/library/test_linear_function.py | 15 +- .../operators/symplectic/test_clifford.py | 6 +- test/python/synthesis/test_cx_cz_synthesis.py | 5 +- .../python/synthesis/test_linear_synthesis.py | 9 +- .../transpiler/test_high_level_synthesis.py | 22 +- 17 files changed, 474 insertions(+), 210 deletions(-) create mode 100644 crates/accelerate/src/synthesis/linear/mod.rs create mode 100644 crates/accelerate/src/synthesis/linear/utils.rs create mode 100644 releasenotes/notes/linear-binary-matrix-utils-rust-c48b5577749c34ab.yaml diff --git a/crates/accelerate/src/synthesis/linear/mod.rs b/crates/accelerate/src/synthesis/linear/mod.rs new file mode 100644 index 000000000000..2fa158ea761f --- /dev/null +++ b/crates/accelerate/src/synthesis/linear/mod.rs @@ -0,0 +1,190 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use crate::QiskitError; +use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2, PyReadwriteArray2}; +use pyo3::prelude::*; + +mod utils; + +#[pyfunction] +#[pyo3(signature = (mat, ncols=None, full_elim=false))] +/// Gauss elimination of a matrix mat with m rows and n columns. +/// If full_elim = True, it allows full elimination of mat[:, 0 : ncols] +/// Modifies the matrix mat in-place, and returns the permutation perm that was done +/// on the rows during the process. perm[0 : rank] represents the indices of linearly +/// independent rows in the original matrix. +/// Args: +/// mat: a boolean matrix with n rows and m columns +/// ncols: the number of columns for the gaussian elimination, +/// if ncols=None, then the elimination is done over all the columns +/// full_elim: whether to do a full elimination, or partial (upper triangular form) +/// Returns: +/// perm: the permutation perm that was done on the rows during the process +fn gauss_elimination_with_perm( + py: Python, + mut mat: PyReadwriteArray2, + ncols: Option, + full_elim: Option, +) -> PyResult { + let matmut = mat.as_array_mut(); + let perm = utils::gauss_elimination_with_perm_inner(matmut, ncols, full_elim); + Ok(perm.to_object(py)) +} + +#[pyfunction] +#[pyo3(signature = (mat, ncols=None, full_elim=false))] +/// Gauss elimination of a matrix mat with m rows and n columns. +/// If full_elim = True, it allows full elimination of mat[:, 0 : ncols] +/// This function modifies the input matrix in-place. +/// Args: +/// mat: a boolean matrix with n rows and m columns +/// ncols: the number of columns for the gaussian elimination, +/// if ncols=None, then the elimination is done over all the columns +/// full_elim: whether to do a full elimination, or partial (upper triangular form) +fn gauss_elimination( + mut mat: PyReadwriteArray2, + ncols: Option, + full_elim: Option, +) { + let matmut = mat.as_array_mut(); + let _perm = utils::gauss_elimination_with_perm_inner(matmut, ncols, full_elim); +} + +#[pyfunction] +#[pyo3(signature = (mat))] +/// Given a boolean matrix mat after Gaussian elimination, computes its rank +/// (i.e. simply the number of nonzero rows) +/// Args: +/// mat: a boolean matrix after gaussian elimination +/// Returns: +/// rank: the rank of the matrix +fn compute_rank_after_gauss_elim(py: Python, mat: PyReadonlyArray2) -> PyResult { + let view = mat.as_array(); + let rank = utils::compute_rank_after_gauss_elim_inner(view); + Ok(rank.to_object(py)) +} + +#[pyfunction] +#[pyo3(signature = (mat))] +/// Given a boolean matrix mat computes its rank +/// Args: +/// mat: a boolean matrix +/// Returns: +/// rank: the rank of the matrix +fn compute_rank(py: Python, mat: PyReadonlyArray2) -> PyResult { + let rank = utils::compute_rank_inner(mat.as_array()); + Ok(rank.to_object(py)) +} + +#[pyfunction] +#[pyo3(signature = (mat, verify=false))] +/// Given a boolean matrix mat, tries to calculate its inverse matrix +/// Args: +/// mat: a boolean square matrix. +/// verify: if True asserts that the multiplication of mat and its inverse is the identity matrix. +/// Returns: +/// the inverse matrix. +/// Raises: +/// QiskitError: if the matrix is not square or not invertible. +pub fn calc_inverse_matrix( + py: Python, + mat: PyReadonlyArray2, + verify: Option, +) -> PyResult>> { + let view = mat.as_array(); + let invmat = + utils::calc_inverse_matrix_inner(view, verify.is_some()).map_err(QiskitError::new_err)?; + Ok(invmat.into_pyarray_bound(py).unbind()) +} + +#[pyfunction] +#[pyo3(signature = (mat1, mat2))] +/// Binary matrix multiplication +/// Args: +/// mat1: a boolean matrix +/// mat2: a boolean matrix +/// Returns: +/// a boolean matrix which is the multiplication of mat1 and mat2 +/// Raises: +/// QiskitError: if the dimensions of mat1 and mat2 do not match +pub fn binary_matmul( + py: Python, + mat1: PyReadonlyArray2, + mat2: PyReadonlyArray2, +) -> PyResult>> { + let view1 = mat1.as_array(); + let view2 = mat2.as_array(); + let result = utils::binary_matmul_inner(view1, view2).map_err(QiskitError::new_err)?; + Ok(result.into_pyarray_bound(py).unbind()) +} + +#[pyfunction] +#[pyo3(signature = (mat, ctrl, trgt))] +/// Perform ROW operation on a matrix mat +fn row_op(mut mat: PyReadwriteArray2, ctrl: usize, trgt: usize) { + let matmut = mat.as_array_mut(); + utils::_add_row_or_col(matmut, &false, ctrl, trgt) +} + +#[pyfunction] +#[pyo3(signature = (mat, ctrl, trgt))] +/// Perform COL operation on a matrix mat (in the inverse direction) +fn col_op(mut mat: PyReadwriteArray2, ctrl: usize, trgt: usize) { + let matmut = mat.as_array_mut(); + utils::_add_row_or_col(matmut, &true, trgt, ctrl) +} + +#[pyfunction] +#[pyo3(signature = (num_qubits, seed=None))] +/// Generate a random invertible n x n binary matrix. +/// Args: +/// num_qubits: the matrix size. +/// seed: a random seed. +/// Returns: +/// np.ndarray: A random invertible binary matrix of size num_qubits. +fn random_invertible_binary_matrix( + py: Python, + num_qubits: usize, + seed: Option, +) -> PyResult>> { + let matrix = utils::random_invertible_binary_matrix_inner(num_qubits, seed); + Ok(matrix.into_pyarray_bound(py).unbind()) +} + +#[pyfunction] +#[pyo3(signature = (mat))] +/// Check that a binary matrix is invertible. +/// Args: +/// mat: a binary matrix. +/// Returns: +/// bool: True if mat in invertible and False otherwise. +fn check_invertible_binary_matrix(py: Python, mat: PyReadonlyArray2) -> PyResult { + let view = mat.as_array(); + let out = utils::check_invertible_binary_matrix_inner(view); + Ok(out.to_object(py)) +} + +#[pymodule] +pub fn linear(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(gauss_elimination_with_perm))?; + m.add_wrapped(wrap_pyfunction!(gauss_elimination))?; + m.add_wrapped(wrap_pyfunction!(compute_rank_after_gauss_elim))?; + m.add_wrapped(wrap_pyfunction!(compute_rank))?; + m.add_wrapped(wrap_pyfunction!(calc_inverse_matrix))?; + m.add_wrapped(wrap_pyfunction!(row_op))?; + m.add_wrapped(wrap_pyfunction!(col_op))?; + m.add_wrapped(wrap_pyfunction!(binary_matmul))?; + m.add_wrapped(wrap_pyfunction!(random_invertible_binary_matrix))?; + m.add_wrapped(wrap_pyfunction!(check_invertible_binary_matrix))?; + Ok(()) +} diff --git a/crates/accelerate/src/synthesis/linear/utils.rs b/crates/accelerate/src/synthesis/linear/utils.rs new file mode 100644 index 000000000000..b4dbf4993081 --- /dev/null +++ b/crates/accelerate/src/synthesis/linear/utils.rs @@ -0,0 +1,200 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use ndarray::{concatenate, s, Array2, ArrayView2, ArrayViewMut2, Axis}; +use rand::{Rng, SeedableRng}; +use rand_pcg::Pcg64Mcg; + +/// Binary matrix multiplication +pub fn binary_matmul_inner( + mat1: ArrayView2, + mat2: ArrayView2, +) -> Result, String> { + let n1_rows = mat1.nrows(); + let n1_cols = mat1.ncols(); + let n2_rows = mat2.nrows(); + let n2_cols = mat2.ncols(); + if n1_cols != n2_rows { + return Err(format!( + "Cannot multiply matrices with inappropriate dimensions {}, {}", + n1_cols, n2_rows + )); + } + + Ok(Array2::from_shape_fn((n1_rows, n2_cols), |(i, j)| { + (0..n2_rows) + .map(|k| mat1[[i, k]] & mat2[[k, j]]) + .fold(false, |acc, v| acc ^ v) + })) +} + +/// Gauss elimination of a matrix mat with m rows and n columns. +/// If full_elim = True, it allows full elimination of mat[:, 0 : ncols] +/// Returns the matrix mat, and the permutation perm that was done on the rows during the process. +/// perm[0 : rank] represents the indices of linearly independent rows in the original matrix. +pub fn gauss_elimination_with_perm_inner( + mut mat: ArrayViewMut2, + ncols: Option, + full_elim: Option, +) -> Vec { + let (m, mut n) = (mat.nrows(), mat.ncols()); // no. of rows and columns + if let Some(ncols_val) = ncols { + n = usize::min(n, ncols_val); // no. of active columns + } + let mut perm: Vec = Vec::from_iter(0..m); + + let mut r = 0; // current rank + let k = 0; // current pivot column + let mut new_k = 0; + while (r < m) && (k < n) { + let mut is_non_zero = false; + let mut new_r = r; + for j in k..n { + new_k = k; + for i in r..m { + if mat[(i, j)] { + is_non_zero = true; + new_k = j; + new_r = i; + break; + } + } + if is_non_zero { + break; + } + } + if !is_non_zero { + return perm; // A is in the canonical form + } + + if new_r != r { + let temp_r = mat.slice_mut(s![r, ..]).to_owned(); + let temp_new_r = mat.slice_mut(s![new_r, ..]).to_owned(); + mat.slice_mut(s![r, ..]).assign(&temp_new_r); + mat.slice_mut(s![new_r, ..]).assign(&temp_r); + perm.swap(r, new_r); + } + + // Copy source row to avoid trying multiple borrows at once + let row0 = mat.row(r).to_owned(); + mat.axis_iter_mut(Axis(0)) + .enumerate() + .filter(|(i, row)| { + (full_elim == Some(true) && (*i < r) && row[new_k]) + || (*i > r && *i < m && row[new_k]) + }) + .for_each(|(_i, mut row)| { + row.zip_mut_with(&row0, |x, &y| *x ^= y); + }); + + r += 1; + } + perm +} + +/// Given a boolean matrix A after Gaussian elimination, computes its rank +/// (i.e. simply the number of nonzero rows) +pub fn compute_rank_after_gauss_elim_inner(mat: ArrayView2) -> usize { + let rank: usize = mat + .axis_iter(Axis(0)) + .map(|row| row.fold(false, |out, val| out | *val) as usize) + .sum(); + rank +} + +/// Given a boolean matrix mat computes its rank +pub fn compute_rank_inner(mat: ArrayView2) -> usize { + let mut temp_mat = mat.to_owned(); + gauss_elimination_with_perm_inner(temp_mat.view_mut(), None, Some(false)); + let rank = compute_rank_after_gauss_elim_inner(temp_mat.view()); + rank +} + +/// Given a square boolean matrix mat, tries to compute its inverse. +pub fn calc_inverse_matrix_inner( + mat: ArrayView2, + verify: bool, +) -> Result, String> { + if mat.shape()[0] != mat.shape()[1] { + return Err("Matrix to invert is a non-square matrix.".to_string()); + } + let n = mat.shape()[0]; + + // concatenate the matrix and identity + let identity_matrix: Array2 = Array2::from_shape_fn((n, n), |(i, j)| i == j); + let mut mat1 = concatenate(Axis(1), &[mat.view(), identity_matrix.view()]).unwrap(); + + gauss_elimination_with_perm_inner(mat1.view_mut(), None, Some(true)); + + let r = compute_rank_after_gauss_elim_inner(mat1.slice(s![.., 0..n])); + if r < n { + return Err("The matrix is not invertible.".to_string()); + } + + let invmat = mat1.slice(s![.., n..2 * n]).to_owned(); + + if verify { + let mat2 = binary_matmul_inner(mat, (&invmat).into())?; + let identity_matrix: Array2 = Array2::from_shape_fn((n, n), |(i, j)| i == j); + if mat2.ne(&identity_matrix) { + return Err("The inverse matrix is not correct.".to_string()); + } + } + + Ok(invmat) +} + +/// Mutate a matrix inplace by adding the value of the ``ctrl`` row to the +/// ``target`` row. If ``add_cols`` is true, add columns instead of rows. +pub fn _add_row_or_col(mut mat: ArrayViewMut2, add_cols: &bool, ctrl: usize, trgt: usize) { + // get the two rows (or columns) + let info = if *add_cols { + (s![.., ctrl], s![.., trgt]) + } else { + (s![ctrl, ..], s![trgt, ..]) + }; + let (row0, mut row1) = mat.multi_slice_mut(info); + + // add them inplace + row1.zip_mut_with(&row0, |x, &y| *x ^= y); +} + +/// Generate a random invertible n x n binary matrix. +pub fn random_invertible_binary_matrix_inner(num_qubits: usize, seed: Option) -> Array2 { + let mut rng = match seed { + Some(seed) => Pcg64Mcg::seed_from_u64(seed), + None => Pcg64Mcg::from_entropy(), + }; + + let mut matrix = Array2::from_elem((num_qubits, num_qubits), false); + + loop { + for value in matrix.iter_mut() { + *value = rng.gen_bool(0.5); + } + + let rank = compute_rank_inner(matrix.view()); + if rank == num_qubits { + break; + } + } + matrix +} + +/// Check that a binary matrix is invertible. +pub fn check_invertible_binary_matrix_inner(mat: ArrayView2) -> bool { + if mat.nrows() != mat.ncols() { + return false; + } + let rank = compute_rank_inner(mat); + rank == mat.nrows() +} diff --git a/crates/accelerate/src/synthesis/mod.rs b/crates/accelerate/src/synthesis/mod.rs index f1a720459211..db28751437f6 100644 --- a/crates/accelerate/src/synthesis/mod.rs +++ b/crates/accelerate/src/synthesis/mod.rs @@ -10,6 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +mod linear; mod permutation; use pyo3::prelude::*; @@ -18,5 +19,6 @@ use pyo3::wrap_pymodule; #[pymodule] pub fn synthesis(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(permutation::permutation))?; + m.add_wrapped(wrap_pymodule!(linear::linear))?; Ok(()) } diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 5b8505654428..aca555da8cb8 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -81,6 +81,7 @@ sys.modules["qiskit._accelerate.two_qubit_decompose"] = _accelerate.two_qubit_decompose sys.modules["qiskit._accelerate.vf2_layout"] = _accelerate.vf2_layout sys.modules["qiskit._accelerate.synthesis.permutation"] = _accelerate.synthesis.permutation +sys.modules["qiskit._accelerate.synthesis.linear"] = _accelerate.synthesis.linear from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/quantum_info/operators/dihedral/random.py b/qiskit/quantum_info/operators/dihedral/random.py index 4331d618d73d..8223f87e9a17 100644 --- a/qiskit/quantum_info/operators/dihedral/random.py +++ b/qiskit/quantum_info/operators/dihedral/random.py @@ -53,7 +53,8 @@ def random_cnotdihedral(num_qubits, seed=None): random_invertible_binary_matrix, ) - linear = random_invertible_binary_matrix(num_qubits, seed=rng) + seed = rng.integers(100000, size=1, dtype=np.uint64)[0] + linear = random_invertible_binary_matrix(num_qubits, seed=seed).astype(int, copy=False) elem.linear = linear # Random shift diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 21bea89b6575..8b745823dc10 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -33,9 +33,10 @@ from qiskit.synthesis.linear_phase import synth_cz_depth_line_mr, synth_cx_cz_depth_line_my from qiskit.synthesis.linear.linear_matrix_utils import ( calc_inverse_matrix, - _compute_rank, - _gauss_elimination, - _gauss_elimination_with_perm, + compute_rank, + gauss_elimination, + gauss_elimination_with_perm, + binary_matmul, ) @@ -203,24 +204,25 @@ def _create_graph_state(cliff, validate=False): """ num_qubits = cliff.num_qubits - rank = _compute_rank(cliff.stab_x) + rank = compute_rank(np.asarray(cliff.stab_x, dtype=bool)) H1_circ = QuantumCircuit(num_qubits, name="H1") cliffh = cliff.copy() if rank < num_qubits: stab = cliff.stab[:, :-1] - stab = _gauss_elimination(stab, num_qubits) + stab = stab.astype(bool, copy=True) + gauss_elimination(stab, num_qubits) Cmat = stab[rank:num_qubits, num_qubits:] Cmat = np.transpose(Cmat) - Cmat, perm = _gauss_elimination_with_perm(Cmat) + perm = gauss_elimination_with_perm(Cmat) perm = perm[0 : num_qubits - rank] # validate that the output matrix has the same rank if validate: - if _compute_rank(Cmat) != num_qubits - rank: + if compute_rank(Cmat) != num_qubits - rank: raise QiskitError("The matrix Cmat after Gauss elimination has wrong rank.") - if _compute_rank(stab[:, 0:num_qubits]) != rank: + if compute_rank(stab[:, 0:num_qubits]) != rank: raise QiskitError("The matrix after Gauss elimination has wrong rank.") # validate that we have a num_qubits - rank zero rows for i in range(rank, num_qubits): @@ -236,8 +238,8 @@ def _create_graph_state(cliff, validate=False): # validate that a layer of Hadamard gates and then appending cliff, provides a graph state. if validate: - stabh = cliffh.stab_x - if _compute_rank(stabh) != num_qubits: + stabh = (cliffh.stab_x).astype(bool, copy=False) + if compute_rank(stabh) != num_qubits: raise QiskitError("The state is not a graph state.") return H1_circ, cliffh @@ -267,7 +269,7 @@ def _decompose_graph_state(cliff, validate, cz_synth_func): """ num_qubits = cliff.num_qubits - rank = _compute_rank(cliff.stab_x) + rank = compute_rank(np.asarray(cliff.stab_x, dtype=bool)) cliff_cpy = cliff.copy() if rank < num_qubits: raise QiskitError("The stabilizer state is not a graph state.") @@ -278,7 +280,7 @@ def _decompose_graph_state(cliff, validate, cz_synth_func): stabx = cliff.stab_x stabz = cliff.stab_z stabx_inv = calc_inverse_matrix(stabx, validate) - stabz_update = np.matmul(stabx_inv, stabz) % 2 + stabz_update = binary_matmul(stabx_inv, stabz) # Assert that stabz_update is a symmetric matrix. if validate: @@ -340,7 +342,7 @@ def _decompose_hadamard_free( if not (stabx == np.zeros((num_qubits, num_qubits))).all(): raise QiskitError("The given Clifford is not Hadamard-free.") - destabz_update = np.matmul(calc_inverse_matrix(destabx), destabz) % 2 + destabz_update = binary_matmul(calc_inverse_matrix(destabx), destabz) # Assert that destabz_update is a symmetric matrix. if validate: if (destabz_update != destabz_update.T).any(): diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index 115fc557bfa4..f3537de9c3ff 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -18,6 +18,7 @@ random_invertible_binary_matrix, calc_inverse_matrix, check_invertible_binary_matrix, + binary_matmul, ) # This is re-import is kept for compatibility with Terra 0.23. Eligible for deprecation in 0.25+. diff --git a/qiskit/synthesis/linear/linear_depth_lnn.py b/qiskit/synthesis/linear/linear_depth_lnn.py index 2811b755fa42..7c7360915e0f 100644 --- a/qiskit/synthesis/linear/linear_depth_lnn.py +++ b/qiskit/synthesis/linear/linear_depth_lnn.py @@ -28,15 +28,15 @@ from qiskit.synthesis.linear.linear_matrix_utils import ( calc_inverse_matrix, check_invertible_binary_matrix, - _col_op, - _row_op, + col_op, + row_op, ) def _row_op_update_instructions(cx_instructions, mat, a, b): # Add a cx gate to the instructions and update the matrix mat cx_instructions.append((a, b)) - _row_op(mat, a, b) + row_op(mat, a, b) def _get_lower_triangular(n, mat, mat_inv): @@ -62,7 +62,7 @@ def _get_lower_triangular(n, mat, mat_inv): first_j = j else: # cx_instructions_cols (L instructions) are not needed - _col_op(mat, j, first_j) + col_op(mat, j, first_j) # Use row operations directed upwards to zero out all "1"s above the remaining "1" in row i for k in reversed(range(0, i)): if mat[k, first_j]: @@ -70,8 +70,8 @@ def _get_lower_triangular(n, mat, mat_inv): # Apply only U instructions to get the permuted L for inst in cx_instructions_rows: - _row_op(mat_t, inst[0], inst[1]) - _col_op(mat_inv_t, inst[0], inst[1]) + row_op(mat_t, inst[0], inst[1]) + col_op(mat_inv_t, inst[0], inst[1]) return mat_t, mat_inv_t @@ -222,7 +222,7 @@ def _optimize_cx_circ_depth_5n_line(mat): # According to [1] the synthesis is done on the inverse matrix # so the matrix mat is inverted at this step - mat_inv = mat.copy() + mat_inv = mat.astype(bool, copy=True) mat_cpy = calc_inverse_matrix(mat_inv) n = len(mat_cpy) diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index 7a5b60641475..a76efdbb8d72 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -12,164 +12,16 @@ """Utility functions for handling binary matrices.""" -from typing import Optional, Union -import numpy as np -from qiskit.exceptions import QiskitError - - -def check_invertible_binary_matrix(mat: np.ndarray): - """Check that a binary matrix is invertible. - - Args: - mat: a binary matrix. - - Returns: - bool: True if mat in invertible and False otherwise. - """ - if len(mat.shape) != 2 or mat.shape[0] != mat.shape[1]: - return False - - rank = _compute_rank(mat) - return rank == mat.shape[0] - - -def random_invertible_binary_matrix( - num_qubits: int, seed: Optional[Union[np.random.Generator, int]] = None -): - """Generates a random invertible n x n binary matrix. - - Args: - num_qubits: the matrix size. - seed: a random seed. - - Returns: - np.ndarray: A random invertible binary matrix of size num_qubits. - """ - if isinstance(seed, np.random.Generator): - rng = seed - else: - rng = np.random.default_rng(seed) - - rank = 0 - while rank != num_qubits: - mat = rng.integers(2, size=(num_qubits, num_qubits)) - rank = _compute_rank(mat) - return mat - - -def _gauss_elimination(mat, ncols=None, full_elim=False): - """Gauss elimination of a matrix mat with m rows and n columns. - If full_elim = True, it allows full elimination of mat[:, 0 : ncols] - Returns the matrix mat.""" - - mat, _ = _gauss_elimination_with_perm(mat, ncols, full_elim) - return mat - - -def _gauss_elimination_with_perm(mat, ncols=None, full_elim=False): - """Gauss elimination of a matrix mat with m rows and n columns. - If full_elim = True, it allows full elimination of mat[:, 0 : ncols] - Returns the matrix mat, and the permutation perm that was done on the rows during the process. - perm[0 : rank] represents the indices of linearly independent rows in the original matrix.""" - - # Treat the matrix A as containing integer values - mat = np.array(mat, dtype=int, copy=True) - - m = mat.shape[0] # no. of rows - n = mat.shape[1] # no. of columns - if ncols is not None: - n = min(n, ncols) # no. of active columns - - perm = np.array(range(m)) # permutation on the rows - - r = 0 # current rank - k = 0 # current pivot column - while (r < m) and (k < n): - is_non_zero = False - new_r = r - for j in range(k, n): - for i in range(r, m): - if mat[i][j]: - is_non_zero = True - k = j - new_r = i - break - if is_non_zero: - break - if not is_non_zero: - return mat, perm # A is in the canonical form - - if new_r != r: - mat[[r, new_r]] = mat[[new_r, r]] - perm[r], perm[new_r] = perm[new_r], perm[r] - - if full_elim: - for i in range(0, r): - if mat[i][k]: - mat[i] = mat[i] ^ mat[r] - - for i in range(r + 1, m): - if mat[i][k]: - mat[i] = mat[i] ^ mat[r] - r += 1 - - return mat, perm - - -def calc_inverse_matrix(mat: np.ndarray, verify: bool = False): - """Given a square numpy(dtype=int) matrix mat, tries to compute its inverse. - - Args: - mat: a boolean square matrix. - verify: if True asserts that the multiplication of mat and its inverse is the identity matrix. - - Returns: - np.ndarray: the inverse matrix. - - Raises: - QiskitError: if the matrix is not square. - QiskitError: if the matrix is not invertible. - """ - - if mat.shape[0] != mat.shape[1]: - raise QiskitError("Matrix to invert is a non-square matrix.") - - n = mat.shape[0] - # concatenate the matrix and identity - mat1 = np.concatenate((mat, np.eye(n, dtype=int)), axis=1) - mat1 = _gauss_elimination(mat1, None, full_elim=True) - - r = _compute_rank_after_gauss_elim(mat1[:, 0:n]) - - if r < n: - raise QiskitError("The matrix is not invertible.") - - matinv = mat1[:, n : 2 * n] - - if verify: - mat2 = np.dot(mat, matinv) % 2 - assert np.array_equal(mat2, np.eye(n)) - - return matinv - - -def _compute_rank_after_gauss_elim(mat): - """Given a matrix A after Gaussian elimination, computes its rank - (i.e. simply the number of nonzero rows)""" - return np.sum(mat.any(axis=1)) - - -def _compute_rank(mat): - """Given a matrix A computes its rank""" - mat = _gauss_elimination(mat) - return np.sum(mat.any(axis=1)) - - -def _row_op(mat, ctrl, trgt): - # Perform ROW operation on a matrix mat - mat[trgt] = mat[trgt] ^ mat[ctrl] - - -def _col_op(mat, ctrl, trgt): - # Perform COL operation on a matrix mat - mat[:, ctrl] = mat[:, trgt] ^ mat[:, ctrl] +# pylint: disable=unused-import +from qiskit._accelerate.synthesis.linear import ( + gauss_elimination, + gauss_elimination_with_perm, + compute_rank_after_gauss_elim, + compute_rank, + calc_inverse_matrix, + binary_matmul, + random_invertible_binary_matrix, + check_invertible_binary_matrix, + row_op, + col_op, +) diff --git a/qiskit/synthesis/stabilizer/stabilizer_decompose.py b/qiskit/synthesis/stabilizer/stabilizer_decompose.py index ecdc1b3257ef..ef324bc3cad1 100644 --- a/qiskit/synthesis/stabilizer/stabilizer_decompose.py +++ b/qiskit/synthesis/stabilizer/stabilizer_decompose.py @@ -143,7 +143,7 @@ def _calc_pauli_diff_stabilizer(cliff, cliff_target): phase.extend(phase_stab) phase = np.array(phase, dtype=int) - A = cliff.symplectic_matrix.astype(int) + A = cliff.symplectic_matrix.astype(bool, copy=False) Ainv = calc_inverse_matrix(A) # By carefully writing how X, Y, Z gates affect each qubit, all we need to compute diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 668d10f32bcc..bbc986621050 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -779,7 +779,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** use_inverted = options.get("use_inverted", False) use_transposed = options.get("use_transposed", False) - mat = high_level_object.linear.astype(int) + mat = high_level_object.linear.astype(bool, copy=False) if use_transposed: mat = np.transpose(mat) @@ -831,7 +831,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** use_inverted = options.get("use_inverted", False) use_transposed = options.get("use_transposed", False) - mat = high_level_object.linear.astype(int) + mat = high_level_object.linear.astype(bool, copy=False) if use_transposed: mat = np.transpose(mat) diff --git a/releasenotes/notes/linear-binary-matrix-utils-rust-c48b5577749c34ab.yaml b/releasenotes/notes/linear-binary-matrix-utils-rust-c48b5577749c34ab.yaml new file mode 100644 index 000000000000..a8e9ec743808 --- /dev/null +++ b/releasenotes/notes/linear-binary-matrix-utils-rust-c48b5577749c34ab.yaml @@ -0,0 +1,8 @@ +--- +features_synthesis: + - | + Port internal binary matrix utils from Python to Rust, including + binary matrix multiplication, gaussian elimination, rank calculation, + binary matrix inversion, and random invertible binary matrix generation. + These functions are not part of the Qiskit API, and porting them to rust + improves the performance of certain synthesis methods. diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index a2d868fbc1be..a3df1e9664a2 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -86,7 +86,8 @@ def random_linear_circuit( elif name == "linear": nqargs = rng.choice(range(1, num_qubits + 1)) qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() - mat = random_invertible_binary_matrix(nqargs, seed=rng) + seed = rng.integers(100000, size=1, dtype=np.uint64)[0] + mat = random_invertible_binary_matrix(nqargs, seed=seed) circ.append(LinearFunction(mat), qargs) elif name == "permutation": nqargs = rng.choice(range(1, num_qubits + 1)) @@ -140,10 +141,11 @@ def test_conversion_to_matrix_and_back(self, num_qubits): and then synthesizing this linear function to a quantum circuit.""" rng = np.random.default_rng(1234) - for _ in range(10): - for num_gates in [0, 5, 5 * num_qubits]: + for num_gates in [0, 5, 5 * num_qubits]: + seeds = rng.integers(100000, size=10, dtype=np.uint64) + for seed in seeds: # create a random linear circuit - linear_circuit = random_linear_circuit(num_qubits, num_gates, seed=rng) + linear_circuit = random_linear_circuit(num_qubits, num_gates, seed=seed) self.assertIsInstance(linear_circuit, QuantumCircuit) # convert it to a linear function @@ -168,10 +170,11 @@ def test_conversion_to_linear_function_and_back(self, num_qubits): """Test correctness of first synthesizing a linear circuit from a linear function, and then converting this linear circuit to a linear function.""" rng = np.random.default_rng(5678) + seeds = rng.integers(100000, size=10, dtype=np.uint64) - for _ in range(10): + for seed in seeds: # create a random invertible binary matrix - binary_matrix = random_invertible_binary_matrix(num_qubits, seed=rng) + binary_matrix = random_invertible_binary_matrix(num_qubits, seed=seed) # create a linear function with this matrix linear_function = LinearFunction(binary_matrix, validate_input=True) diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 043a9eca78b6..36d716d2d85a 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -424,9 +424,9 @@ def test_from_linear_function(self, num_qubits): """Test initialization from linear function.""" rng = np.random.default_rng(1234) samples = 50 - - for _ in range(samples): - mat = random_invertible_binary_matrix(num_qubits, seed=rng) + seeds = rng.integers(100000, size=samples, dtype=np.uint64) + for seed in seeds: + mat = random_invertible_binary_matrix(num_qubits, seed=seed) lin = LinearFunction(mat) cliff = Clifford(lin) self.assertTrue(Operator(cliff).equiv(Operator(lin))) diff --git a/test/python/synthesis/test_cx_cz_synthesis.py b/test/python/synthesis/test_cx_cz_synthesis.py index 63353dab95df..28a26df181a2 100644 --- a/test/python/synthesis/test_cx_cz_synthesis.py +++ b/test/python/synthesis/test_cx_cz_synthesis.py @@ -39,8 +39,9 @@ def test_cx_cz_synth_lnn(self, num_qubits): rng = np.random.default_rng(seed) num_gates = 10 num_trials = 8 + seeds = rng.integers(100000, size=num_trials, dtype=np.uint64) - for _ in range(num_trials): + for seed in seeds: # Generate a random CZ circuit mat_z = np.zeros((num_qubits, num_qubits)) cir_z = QuantumCircuit(num_qubits) @@ -55,7 +56,7 @@ def test_cx_cz_synth_lnn(self, num_qubits): mat_z[j][i] = (mat_z[j][i] + 1) % 2 # Generate a random CX circuit - mat_x = random_invertible_binary_matrix(num_qubits, seed=rng) + mat_x = random_invertible_binary_matrix(num_qubits, seed=seed) mat_x = np.array(mat_x, dtype=bool) cir_x = synth_cnot_depth_line_kms(mat_x) diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index 98a49b6642f7..bbfab20a30fc 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -24,6 +24,7 @@ random_invertible_binary_matrix, check_invertible_binary_matrix, calc_inverse_matrix, + binary_matmul, ) from qiskit.synthesis.linear.linear_circuits_utils import transpose_cx_circ, optimize_cx_4_options from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -107,8 +108,9 @@ def test_invertible_matrix(self, n): """Test the functions for generating a random invertible matrix and inverting it.""" mat = random_invertible_binary_matrix(n, seed=1234) out = check_invertible_binary_matrix(mat) + mat = mat.astype(bool) mat_inv = calc_inverse_matrix(mat, verify=True) - mat_out = np.dot(mat, mat_inv) % 2 + mat_out = binary_matmul(mat, mat_inv) self.assertTrue(np.array_equal(mat_out, np.eye(n))) self.assertTrue(out) @@ -117,8 +119,9 @@ def test_synth_lnn_kms(self, num_qubits): """Test that synth_cnot_depth_line_kms produces the correct synthesis.""" rng = np.random.default_rng(1234) num_trials = 10 - for _ in range(num_trials): - mat = random_invertible_binary_matrix(num_qubits, seed=rng) + seeds = rng.integers(100000, size=num_trials, dtype=np.uint64) + for seed in seeds: + mat = random_invertible_binary_matrix(num_qubits, seed=seed) mat = np.array(mat, dtype=bool) qc = synth_cnot_depth_line_kms(mat) mat1 = LinearFunction(qc).linear diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index ff54169374bc..a76ab08d90e1 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -534,22 +534,22 @@ def test_section_size(self): hls_config = HLSConfig(linear_function=[("pmh", {"section_size": 1})]) qct = HighLevelSynthesis(hls_config=hls_config)(qc) self.assertEqual(LinearFunction(qct), LinearFunction(qc)) - self.assertEqual(qct.size(), 22) - self.assertEqual(qct.depth(), 20) + self.assertEqual(qct.size(), 30) + self.assertEqual(qct.depth(), 27) with self.subTest("section_size_2"): hls_config = HLSConfig(linear_function=[("pmh", {"section_size": 2})]) qct = HighLevelSynthesis(hls_config=hls_config)(qc) self.assertEqual(LinearFunction(qct), LinearFunction(qc)) - self.assertEqual(qct.size(), 23) - self.assertEqual(qct.depth(), 19) + self.assertEqual(qct.size(), 27) + self.assertEqual(qct.depth(), 23) with self.subTest("section_size_3"): hls_config = HLSConfig(linear_function=[("pmh", {"section_size": 3})]) qct = HighLevelSynthesis(hls_config=hls_config)(qc) self.assertEqual(LinearFunction(qct), LinearFunction(qc)) - self.assertEqual(qct.size(), 23) - self.assertEqual(qct.depth(), 17) + self.assertEqual(qct.size(), 29) + self.assertEqual(qct.depth(), 23) def test_invert_and_transpose(self): """Test that the plugin takes the use_inverted and use_transposed arguments into account.""" @@ -623,7 +623,7 @@ def test_plugin_selection_all_with_metrix(self): # The seed is chosen so that we get different best circuits depending on whether we # want to minimize size or depth. - mat = random_invertible_binary_matrix(7, seed=37) + mat = random_invertible_binary_matrix(7, seed=38) qc = QuantumCircuit(7) qc.append(LinearFunction(mat), [0, 1, 2, 3, 4, 5, 6]) @@ -641,8 +641,8 @@ def test_plugin_selection_all_with_metrix(self): ) qct = HighLevelSynthesis(hls_config=hls_config)(qc) self.assertEqual(LinearFunction(qct), LinearFunction(qc)) - self.assertEqual(qct.size(), 20) - self.assertEqual(qct.depth(), 15) + self.assertEqual(qct.size(), 23) + self.assertEqual(qct.depth(), 19) with self.subTest("depth_fn"): # We want to minimize the "depth" (aka the number of layers) in the circuit @@ -658,8 +658,8 @@ def test_plugin_selection_all_with_metrix(self): ) qct = HighLevelSynthesis(hls_config=hls_config)(qc) self.assertEqual(LinearFunction(qct), LinearFunction(qc)) - self.assertEqual(qct.size(), 23) - self.assertEqual(qct.depth(), 12) + self.assertEqual(qct.size(), 24) + self.assertEqual(qct.depth(), 13) class TestKMSSynthesisLinearFunctionPlugin(QiskitTestCase): From 70561982429ee45640b87773a697c3d98d4247c6 Mon Sep 17 00:00:00 2001 From: Eli Arbel <46826214+eliarbel@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:02:51 +0300 Subject: [PATCH 11/17] Add Rust representation for RXX, RYY, RZZ & RZX gates (#12672) * Updating tables * Updating mapping tables * Adding remaining functionality * Handling Black, clippy, fmt --- crates/circuit/src/gate_matrix.rs | 60 ++++++++++ crates/circuit/src/imports.rs | 8 +- crates/circuit/src/operations.rs | 114 +++++++++++++++++-- qiskit/circuit/library/standard_gates/rxx.py | 3 + qiskit/circuit/library/standard_gates/ryy.py | 3 + qiskit/circuit/library/standard_gates/rzx.py | 3 + qiskit/circuit/library/standard_gates/rzz.py | 3 + qiskit/circuit/quantumcircuit.py | 16 +-- 8 files changed, 185 insertions(+), 25 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 074b1c2ac682..d5965afc4964 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -309,3 +309,63 @@ pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { [C_ZERO, C_ZERO, C_ZERO, C_ONE], ] } + +#[inline] +pub fn rxx_gate(theta: f64) -> GateArray2Q { + let (sint, cost) = (theta / 2.0).sin_cos(); + let ccos = c64(cost, 0.); + let csinm = c64(0., -sint); + let c0 = c64(0., 0.); + + [ + [ccos, c0, c0, csinm], + [c0, ccos, csinm, c0], + [c0, csinm, ccos, c0], + [csinm, c0, c0, ccos], + ] +} + +#[inline] +pub fn ryy_gate(theta: f64) -> GateArray2Q { + let (sint, cost) = (theta / 2.0).sin_cos(); + let ccos = c64(cost, 0.); + let csin = c64(0., sint); + let c0 = c64(0., 0.); + + [ + [ccos, c0, c0, csin], + [c0, ccos, -csin, c0], + [c0, -csin, ccos, c0], + [csin, c0, c0, ccos], + ] +} + +#[inline] +pub fn rzz_gate(theta: f64) -> GateArray2Q { + let (sint, cost) = (theta / 2.0).sin_cos(); + let c0 = c64(0., 0.); + let exp_it2 = c64(cost, sint); + let exp_mit2 = c64(cost, -sint); + + [ + [exp_mit2, c0, c0, c0], + [c0, exp_it2, c0, c0], + [c0, c0, exp_it2, c0], + [c0, c0, c0, exp_mit2], + ] +} + +#[inline] +pub fn rzx_gate(theta: f64) -> GateArray2Q { + let (sint, cost) = (theta / 2.0).sin_cos(); + let ccos = c64(cost, 0.); + let csin = c64(0., sint); + let c0 = c64(0., 0.); + + [ + [ccos, c0, -csin, c0], + [c0, ccos, c0, csin], + [-csin, c0, ccos, c0], + [c0, csin, c0, ccos], + ] +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 530e635c94f1..53fee34f486e 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -191,13 +191,13 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // RC3XGate = 48 ["placeholder", "placeholder"], // RXXGate = 49 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.rxx", "RXXGate"], // RYYGate = 50 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.ryy", "RYYGate"], // RZZGate = 51 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.rzz", "RZZGate"], // RZXGate = 52 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.rzx", "RZXGate"], ]; /// A mapping from the enum variant in crate::operations::StandardGate to the python object for the diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 85192b63dbd7..3ac17e4da8f6 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -237,8 +237,8 @@ static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, // 10-19 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 + 2, 2, 34, 34, 34, 2, 34, 34, 34, 2, // 40-49 + 2, 2, 2, // 50-52 ]; // TODO: replace all 34s (placeholders) with actual number @@ -247,8 +247,8 @@ static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, // 10-19 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 + 1, 3, 34, 34, 34, 0, 34, 34, 34, 1, // 40-49 + 1, 1, 1, // 50-52 ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ @@ -542,8 +542,22 @@ impl Operation for StandardGate { }, Self::CCZGate => todo!(), Self::RCCXGate | Self::RC3XGate => todo!(), - Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), - Self::RZXGate => todo!(), + Self::RXXGate => match params[0] { + Param::Float(theta) => Some(aview2(&gate_matrix::rxx_gate(theta)).to_owned()), + _ => None, + }, + Self::RYYGate => match params[0] { + Param::Float(theta) => Some(aview2(&gate_matrix::ryy_gate(theta)).to_owned()), + _ => None, + }, + Self::RZZGate => match params[0] { + Param::Float(theta) => Some(aview2(&gate_matrix::rzz_gate(theta)).to_owned()), + _ => None, + }, + Self::RZXGate => match params[0] { + Param::Float(theta) => Some(aview2(&gate_matrix::rzx_gate(theta)).to_owned()), + _ => None, + }, } } @@ -1103,8 +1117,90 @@ impl Operation for StandardGate { Self::CCZGate => todo!(), Self::RCCXGate | Self::RC3XGate => todo!(), - Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), - Self::RZXGate => todo!(), + Self::RXXGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::HGate, smallvec![], q0.clone()), + (Self::HGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_q1.clone()), + (Self::RZGate, smallvec![theta.clone()], q1.clone()), + (Self::CXGate, smallvec![], q0_q1), + (Self::HGate, smallvec![], q1), + (Self::HGate, smallvec![], q0), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::RYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::RXGate, smallvec![Param::Float(PI / 2.)], q0.clone()), + (Self::RXGate, smallvec![Param::Float(PI / 2.)], q1.clone()), + (Self::CXGate, smallvec![], q0_q1.clone()), + (Self::RZGate, smallvec![theta.clone()], q1.clone()), + (Self::CXGate, smallvec![], q0_q1), + (Self::RXGate, smallvec![Param::Float(-PI / 2.)], q0), + (Self::RXGate, smallvec![Param::Float(-PI / 2.)], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::RZZGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::CXGate, smallvec![], q0_q1.clone()), + (Self::RZGate, smallvec![theta.clone()], q1), + (Self::CXGate, smallvec![], q0_q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::RZXGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::HGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_q1.clone()), + (Self::RZGate, smallvec![theta.clone()], q1.clone()), + (Self::CXGate, smallvec![], q0_q1), + (Self::HGate, smallvec![], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), } } @@ -1115,7 +1211,7 @@ impl Operation for StandardGate { const FLOAT_ZERO: Param = Param::Float(0.0); -// Return explictly requested copy of `param`, handling +// Return explicitly requested copy of `param`, handling // each variant separately. fn clone_param(param: &Param, py: Python) -> Param { match param { diff --git a/qiskit/circuit/library/standard_gates/rxx.py b/qiskit/circuit/library/standard_gates/rxx.py index c4e35e53d55e..1c06ae05a85b 100644 --- a/qiskit/circuit/library/standard_gates/rxx.py +++ b/qiskit/circuit/library/standard_gates/rxx.py @@ -17,6 +17,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 RXXGate(Gate): @@ -72,6 +73,8 @@ class RXXGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.RXXGate + def __init__( self, theta: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt" ): diff --git a/qiskit/circuit/library/standard_gates/ryy.py b/qiskit/circuit/library/standard_gates/ryy.py index 98847b7b2182..91d7d8096cf9 100644 --- a/qiskit/circuit/library/standard_gates/ryy.py +++ b/qiskit/circuit/library/standard_gates/ryy.py @@ -17,6 +17,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 RYYGate(Gate): @@ -72,6 +73,8 @@ class RYYGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.RYYGate + def __init__( self, theta: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt" ): diff --git a/qiskit/circuit/library/standard_gates/rzx.py b/qiskit/circuit/library/standard_gates/rzx.py index 1f930ab422df..90e7b71c0a33 100644 --- a/qiskit/circuit/library/standard_gates/rzx.py +++ b/qiskit/circuit/library/standard_gates/rzx.py @@ -16,6 +16,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 RZXGate(Gate): @@ -117,6 +118,8 @@ class RZXGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.RZXGate + def __init__( self, theta: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt" ): diff --git a/qiskit/circuit/library/standard_gates/rzz.py b/qiskit/circuit/library/standard_gates/rzz.py index 5ca974764d32..119dd370e20c 100644 --- a/qiskit/circuit/library/standard_gates/rzz.py +++ b/qiskit/circuit/library/standard_gates/rzz.py @@ -16,6 +16,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 RZZGate(Gate): @@ -84,6 +85,8 @@ class RZZGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.RZZGate + def __init__( self, theta: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt" ): diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 7b8fe6e031f1..ef160ec064c6 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -4802,9 +4802,7 @@ def rxx( Returns: A handle to the instructions created. """ - from .library.standard_gates.rxx import RXXGate - - return self.append(RXXGate(theta), [qubit1, qubit2], [], copy=False) + return self._append_standard_gate(StandardGate.RXXGate, [theta], [qubit1, qubit2]) def ry( self, theta: ParameterValueType, qubit: QubitSpecifier, label: str | None = None @@ -4877,9 +4875,7 @@ def ryy( Returns: A handle to the instructions created. """ - from .library.standard_gates.ryy import RYYGate - - return self.append(RYYGate(theta), [qubit1, qubit2], [], copy=False) + return self._append_standard_gate(StandardGate.RYYGate, [theta], [qubit1, qubit2]) def rz(self, phi: ParameterValueType, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.RZGate`. @@ -4949,9 +4945,7 @@ def rzx( Returns: A handle to the instructions created. """ - from .library.standard_gates.rzx import RZXGate - - return self.append(RZXGate(theta), [qubit1, qubit2], [], copy=False) + return self._append_standard_gate(StandardGate.RZXGate, [theta], [qubit1, qubit2]) def rzz( self, theta: ParameterValueType, qubit1: QubitSpecifier, qubit2: QubitSpecifier @@ -4968,9 +4962,7 @@ def rzz( Returns: A handle to the instructions created. """ - from .library.standard_gates.rzz import RZZGate - - return self.append(RZZGate(theta), [qubit1, qubit2], [], copy=False) + return self._append_standard_gate(StandardGate.RZZGate, [theta], [qubit1, qubit2]) def ecr(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.ECRGate`. From c452694d70bfa17df8419335c7fb138c9b81963b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:27:45 +0200 Subject: [PATCH 12/17] Bump rustworkx-core from 0.14.2 to 0.15.0 (#12682) Bumps [rustworkx-core](https://github.com/Qiskit/rustworkx) from 0.14.2 to 0.15.0. - [Release notes](https://github.com/Qiskit/rustworkx/releases) - [Commits](https://github.com/Qiskit/rustworkx/compare/0.14.2...0.15.0) --- updated-dependencies: - dependency-name: rustworkx-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Matthew Treinish --- Cargo.lock | 38 ++++++++++++++---------------------- crates/accelerate/Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68a3d3214060..380db7394bca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,16 +548,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.2.6" @@ -906,7 +896,7 @@ checksum = "a8c3d637a7db9ddb3811719db8a466bd4960ea668df4b2d14043a1b0038465b0" dependencies = [ "cov-mark", "either", - "indexmap 2.2.6", + "indexmap", "itertools 0.10.5", "once_cell", "oq3_lexer", @@ -996,12 +986,12 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.6", + "indexmap", ] [[package]] @@ -1018,12 +1008,13 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "priority-queue" -version = "1.4.0" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bda9164fe05bc9225752d54aae413343c36f684380005398a6a8fde95fe785" +checksum = "70c501afe3a2e25c9bd219aa56ec1e04cdb3fcdd763055be268778c13fa82c1f" dependencies = [ "autocfg", - "indexmap 1.9.3", + "equivalent", + "indexmap", ] [[package]] @@ -1104,7 +1095,7 @@ checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" dependencies = [ "cfg-if", "hashbrown 0.14.5", - "indexmap 2.2.6", + "indexmap", "indoc", "libc", "memoffset", @@ -1173,7 +1164,7 @@ dependencies = [ "faer", "faer-ext", "hashbrown 0.14.5", - "indexmap 2.2.6", + "indexmap", "itertools 0.13.0", "ndarray", "num-bigint", @@ -1228,7 +1219,7 @@ name = "qiskit-qasm3" version = "1.2.0" dependencies = [ "hashbrown 0.14.5", - "indexmap 2.2.6", + "indexmap", "oq3_semantics", "pyo3", ] @@ -1399,14 +1390,15 @@ checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "rustworkx-core" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529027dfaa8125aa61bb7736ae9484f41e8544f448af96918c8da6b1def7f57b" +checksum = "c2b9aa5926b35dd3029530aef27eac0926b544c78f8e8f1aad4d37854b132fe9" dependencies = [ "ahash 0.8.11", "fixedbitset", "hashbrown 0.14.5", - "indexmap 2.2.6", + "indexmap", + "ndarray", "num-traits", "petgraph", "priority-queue", diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index b377a9b38a6d..875243096514 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -19,7 +19,7 @@ ahash = "0.8.11" num-traits = "0.2" num-complex.workspace = true num-bigint = "0.4" -rustworkx-core = "0.14" +rustworkx-core = "0.15" faer = "0.19.1" itertools = "0.13.0" qiskit-circuit.workspace = true From a7fc2daf4c0fb0d7b10511d40bde8d1b3201ebce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:49:06 +0200 Subject: [PATCH 13/17] Add Rust representation for CHGate, CPhaseGate, CSGate, CSdgGate, CSXGate, CSwapGate (#12639) * Add CHGate, CPhaseGate, CSGate, CSdgGate, CSXGate, CSwapGate * Fix tests, add labels * Fix oversights in gate definitions * Fix test * Add ctrl_state 1 to rust building path. --- crates/circuit/src/gate_matrix.rs | 185 ++++++++++----- crates/circuit/src/operations.rs | 210 +++++++++++++++--- qiskit/circuit/library/standard_gates/h.py | 2 + qiskit/circuit/library/standard_gates/p.py | 2 + qiskit/circuit/library/standard_gates/s.py | 4 + qiskit/circuit/library/standard_gates/swap.py | 2 + qiskit/circuit/library/standard_gates/sx.py | 2 + qiskit/circuit/quantumcircuit.py | 162 +++++++++----- test/python/circuit/test_rust_equivalence.py | 14 +- 9 files changed, 436 insertions(+), 147 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index d5965afc4964..46585ff6da6e 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -53,43 +53,6 @@ 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.)], @@ -210,6 +173,71 @@ pub static TDG_GATE: GateArray1Q = [ [C_ZERO, c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], ]; +pub static CH_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [ + C_ZERO, + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + c64(FRAC_1_SQRT_2, 0.), + ], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [ + C_ZERO, + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + c64(-FRAC_1_SQRT_2, 0.), + ], +]; + +pub static CS_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, IM], +]; + +pub static CSDG_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, M_IM], +]; + +pub static CSX_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, c64(0.5, 0.5), C_ZERO, c64(0.5, -0.5)], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, c64(0.5, -0.5), C_ZERO, c64(0.5, 0.5)], +]; + +pub static CSWAP_GATE: GateArray3Q = [ + [ + C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, + ], +]; + pub static DCX_GATE: GateArray2Q = [ [C_ONE, C_ZERO, C_ZERO, C_ZERO], [C_ZERO, C_ZERO, C_ZERO, C_ONE], @@ -217,6 +245,43 @@ pub static DCX_GATE: GateArray2Q = [ [C_ZERO, C_ZERO, C_ONE, C_ZERO], ]; +#[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()], + ] +} + #[inline] pub fn global_phase_gate(theta: f64) -> GateArray0Q { [[c64(0., theta).exp()]] @@ -310,18 +375,27 @@ pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { ] } +#[inline] +pub fn cp_gate(lam: f64) -> GateArray2Q { + [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, c64(0., lam).exp()], + ] +} + #[inline] pub fn rxx_gate(theta: f64) -> GateArray2Q { let (sint, cost) = (theta / 2.0).sin_cos(); let ccos = c64(cost, 0.); let csinm = c64(0., -sint); - let c0 = c64(0., 0.); [ - [ccos, c0, c0, csinm], - [c0, ccos, csinm, c0], - [c0, csinm, ccos, c0], - [csinm, c0, c0, ccos], + [ccos, C_ZERO, C_ZERO, csinm], + [C_ZERO, ccos, csinm, C_ZERO], + [C_ZERO, csinm, ccos, C_ZERO], + [csinm, C_ZERO, C_ZERO, ccos], ] } @@ -330,28 +404,26 @@ pub fn ryy_gate(theta: f64) -> GateArray2Q { let (sint, cost) = (theta / 2.0).sin_cos(); let ccos = c64(cost, 0.); let csin = c64(0., sint); - let c0 = c64(0., 0.); [ - [ccos, c0, c0, csin], - [c0, ccos, -csin, c0], - [c0, -csin, ccos, c0], - [csin, c0, c0, ccos], + [ccos, C_ZERO, C_ZERO, csin], + [C_ZERO, ccos, -csin, C_ZERO], + [C_ZERO, -csin, ccos, C_ZERO], + [csin, C_ZERO, C_ZERO, ccos], ] } #[inline] pub fn rzz_gate(theta: f64) -> GateArray2Q { let (sint, cost) = (theta / 2.0).sin_cos(); - let c0 = c64(0., 0.); let exp_it2 = c64(cost, sint); let exp_mit2 = c64(cost, -sint); [ - [exp_mit2, c0, c0, c0], - [c0, exp_it2, c0, c0], - [c0, c0, exp_it2, c0], - [c0, c0, c0, exp_mit2], + [exp_mit2, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, exp_it2, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, exp_it2, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, exp_mit2], ] } @@ -360,12 +432,11 @@ pub fn rzx_gate(theta: f64) -> GateArray2Q { let (sint, cost) = (theta / 2.0).sin_cos(); let ccos = c64(cost, 0.); let csin = c64(0., sint); - let c0 = c64(0., 0.); [ - [ccos, c0, -csin, c0], - [c0, ccos, c0, csin], - [-csin, c0, ccos, c0], - [c0, csin, c0, ccos], + [ccos, C_ZERO, -csin, C_ZERO], + [C_ZERO, ccos, C_ZERO, csin], + [-csin, C_ZERO, ccos, C_ZERO], + [C_ZERO, csin, C_ZERO, ccos], ] } diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 3ac17e4da8f6..df15b4abb415 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -522,20 +522,38 @@ impl Operation for StandardGate { } _ => None, }, + Self::CHGate => match params { + [] => Some(aview2(&gate_matrix::CH_GATE).to_owned()), + _ => None, + }, + Self::CPhaseGate => match params { + [Param::Float(lam)] => Some(aview2(&gate_matrix::cp_gate(*lam)).to_owned()), + _ => None, + }, + Self::CSGate => match params { + [] => Some(aview2(&gate_matrix::CS_GATE).to_owned()), + _ => None, + }, + Self::CSdgGate => match params { + [] => Some(aview2(&gate_matrix::CSDG_GATE).to_owned()), + _ => None, + }, + Self::CSXGate => match params { + [] => Some(aview2(&gate_matrix::CSX_GATE).to_owned()), + _ => None, + }, + Self::CSwapGate => match params { + [] => Some(aview2(&gate_matrix::CSWAP_GATE).to_owned()), + _ => None, + }, + Self::CUGate | Self::CU1Gate | Self::CU3Gate => todo!(), + Self::C3XGate | Self::C3SXGate | Self::C4XGate => 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!(), - Self::CSdgGate => todo!(), - Self::CSXGate => todo!(), - Self::CSwapGate => todo!(), - Self::CUGate | Self::CU1Gate | Self::CU3Gate => todo!(), - Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), Self::DCXGate => match params { [] => Some(aview2(&gate_matrix::DCX_GATE).to_owned()), _ => None, @@ -870,14 +888,14 @@ impl Operation for StandardGate { ) }), Self::UGate => None, - Self::SGate => Python::with_gil(|py| -> Option { + Self::U1Gate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( Self::PhaseGate, - smallvec![Param::Float(PI / 2.)], + params.iter().cloned().collect(), smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -885,14 +903,14 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::U1Gate => Python::with_gil(|py| -> Option { + Self::U2Gate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::PhaseGate, - params.iter().cloned().collect(), + Self::UGate, + smallvec![Param::Float(PI / 2.), params[0].clone(), params[1].clone()], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -900,14 +918,14 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::SdgGate => Python::with_gil(|py| -> Option { + Self::U3Gate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::PhaseGate, - smallvec![Param::Float(-PI / 2.)], + Self::UGate, + params.iter().cloned().collect(), smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -915,14 +933,14 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::U2Gate => Python::with_gil(|py| -> Option { + Self::SGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::UGate, - smallvec![Param::Float(PI / 2.), params[0].clone(), params[1].clone()], + Self::PhaseGate, + smallvec![Param::Float(PI / 2.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -930,14 +948,14 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::TGate => Python::with_gil(|py| -> Option { + Self::SdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( Self::PhaseGate, - smallvec![Param::Float(PI / 4.)], + smallvec![Param::Float(-PI / 2.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -945,14 +963,14 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::U3Gate => Python::with_gil(|py| -> Option { + Self::TGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::UGate, - params.iter().cloned().collect(), + Self::PhaseGate, + smallvec![Param::Float(PI / 4.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -1075,6 +1093,143 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CHGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::SGate, smallvec![], q1.clone()), + (Self::HGate, smallvec![], q1.clone()), + (Self::TGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1), + (Self::TdgGate, smallvec![], q1.clone()), + (Self::HGate, smallvec![], q1.clone()), + (Self::SdgGate, smallvec![], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CPhaseGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::PhaseGate, + smallvec![multiply_param(¶ms[0], 0.5, py)], + q0, + ), + (Self::CXGate, smallvec![], q0_1.clone()), + ( + Self::PhaseGate, + smallvec![multiply_param(¶ms[0], -0.5, py)], + q1.clone(), + ), + (Self::CXGate, smallvec![], q0_1), + ( + Self::PhaseGate, + smallvec![multiply_param(¶ms[0], 0.5, py)], + q1, + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CSGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::PhaseGate, smallvec![Param::Float(PI / 4.)], q0), + (Self::CXGate, smallvec![], q0_1.clone()), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 4.)], + q1.clone(), + ), + (Self::CXGate, smallvec![], q0_1), + (Self::PhaseGate, smallvec![Param::Float(PI / 4.)], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CSdgGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::PhaseGate, smallvec![Param::Float(-PI / 4.)], q0), + (Self::CXGate, smallvec![], q0_1.clone()), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 4.)], + q1.clone(), + ), + (Self::CXGate, smallvec![], q0_1), + (Self::PhaseGate, smallvec![Param::Float(-PI / 4.)], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CSXGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::HGate, smallvec![], q1.clone()), + (Self::CPhaseGate, smallvec![Param::Float(PI / 2.)], q0_1), + (Self::HGate, smallvec![], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CSwapGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(1)]), + ( + Self::CCXGate, + smallvec![], + smallvec![Qubit(0), Qubit(1), Qubit(2)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(1)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::RGate => Python::with_gil(|py| -> Option { let theta_expr = clone_param(¶ms[0], py); let phi_expr1 = add_param(¶ms[1], -PI / 2., py); @@ -1090,12 +1245,6 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CHGate => todo!(), - Self::CPhaseGate => todo!(), - Self::CSGate => todo!(), - Self::CSdgGate => todo!(), - Self::CSXGate => todo!(), - Self::CSwapGate => todo!(), Self::CUGate => todo!(), Self::CU1Gate => todo!(), Self::CU3Gate => todo!(), @@ -1114,7 +1263,6 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CCZGate => todo!(), Self::RCCXGate | Self::RC3XGate => todo!(), Self::RXXGate => Python::with_gil(|py| -> Option { diff --git a/qiskit/circuit/library/standard_gates/h.py b/qiskit/circuit/library/standard_gates/h.py index 2d273eed74d5..c07895ebbeaa 100644 --- a/qiskit/circuit/library/standard_gates/h.py +++ b/qiskit/circuit/library/standard_gates/h.py @@ -185,6 +185,8 @@ class CHGate(SingletonControlledGate): \end{pmatrix} """ + _standard_gate = StandardGate.CHGate + def __init__( self, label: Optional[str] = None, diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py index 1a792649feab..8c83aa464027 100644 --- a/qiskit/circuit/library/standard_gates/p.py +++ b/qiskit/circuit/library/standard_gates/p.py @@ -200,6 +200,8 @@ class CPhaseGate(ControlledGate): phase difference. """ + _standard_gate = StandardGate.CPhaseGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/s.py b/qiskit/circuit/library/standard_gates/s.py index f62d16a10d40..975d1cb3be8c 100644 --- a/qiskit/circuit/library/standard_gates/s.py +++ b/qiskit/circuit/library/standard_gates/s.py @@ -215,6 +215,8 @@ class CSGate(SingletonControlledGate): \end{pmatrix} """ + _standard_gate = StandardGate.CSGate + def __init__( self, label: Optional[str] = None, @@ -301,6 +303,8 @@ class CSdgGate(SingletonControlledGate): \end{pmatrix} """ + _standard_gate = StandardGate.CSdgGate + def __init__( self, label: Optional[str] = None, diff --git a/qiskit/circuit/library/standard_gates/swap.py b/qiskit/circuit/library/standard_gates/swap.py index 243a84701ef5..5d33bc74b8d0 100644 --- a/qiskit/circuit/library/standard_gates/swap.py +++ b/qiskit/circuit/library/standard_gates/swap.py @@ -216,6 +216,8 @@ class CSwapGate(SingletonControlledGate): |1, b, c\rangle \rightarrow |1, c, b\rangle """ + _standard_gate = StandardGate.CSwapGate + def __init__( self, label: Optional[str] = None, diff --git a/qiskit/circuit/library/standard_gates/sx.py b/qiskit/circuit/library/standard_gates/sx.py index 72e4a8f9b5bf..ec3c87653148 100644 --- a/qiskit/circuit/library/standard_gates/sx.py +++ b/qiskit/circuit/library/standard_gates/sx.py @@ -266,6 +266,8 @@ class CSXGate(SingletonControlledGate): """ + _standard_gate = StandardGate.CSXGate + def __init__( self, label: Optional[str] = None, diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index ef160ec064c6..0019a15443e7 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -4516,6 +4516,12 @@ def ch( Returns: A handle to the instructions created. """ + # 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.CHGate, [], qargs=[control_qubit, target_qubit], label=label + ) + from .library.standard_gates.h import CHGate return self.append( @@ -4593,6 +4599,12 @@ def cp( Returns: A handle to the instructions created. """ + # 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.CPhaseGate, [theta], qargs=[control_qubit, target_qubit], label=label + ) + from .library.standard_gates.p import CPhaseGate return self.append( @@ -4772,14 +4784,14 @@ def crx( Returns: A handle to the instructions created. """ - 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]: return self._append_standard_gate( StandardGate.CRXGate, [theta], [control_qubit, target_qubit], None, label=label ) + from .library.standard_gates.rx import CRXGate + return self.append( CRXGate(theta, label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], @@ -4845,14 +4857,14 @@ def cry( Returns: A handle to the instructions created. """ - 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 ) + from .library.standard_gates.ry import CRYGate + return self.append( CRYGate(theta, label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], @@ -4915,14 +4927,14 @@ def crz( Returns: A handle to the instructions created. """ - 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 ) + from .library.standard_gates.rz import CRZGate + return self.append( CRZGate(theta, label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], @@ -4975,9 +4987,7 @@ def ecr(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate( - StandardGate.ECRGate, [], qargs=[qubit1, qubit2], cargs=None - ) + return self._append_standard_gate(StandardGate.ECRGate, [], qargs=[qubit1, qubit2]) def s(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.SGate`. @@ -4990,7 +5000,7 @@ def s(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.SGate, [], [qubit], cargs=None) + return self._append_standard_gate(StandardGate.SGate, [], qargs=[qubit]) def sdg(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.SdgGate`. @@ -5003,7 +5013,7 @@ def sdg(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.SdgGate, [], [qubit], cargs=None) + return self._append_standard_gate(StandardGate.SdgGate, [], qargs=[qubit]) def cs( self, @@ -5027,6 +5037,12 @@ def cs( Returns: A handle to the instructions created. """ + # 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.CSGate, [], qargs=[control_qubit, target_qubit], label=label + ) + from .library.standard_gates.s import CSGate return self.append( @@ -5058,6 +5074,12 @@ def csdg( Returns: A handle to the instructions created. """ + # 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.CSdgGate, [], qargs=[control_qubit, target_qubit], label=label + ) + from .library.standard_gates.s import CSdgGate return self.append( @@ -5082,7 +5104,6 @@ def swap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet StandardGate.SwapGate, [], qargs=[qubit1, qubit2], - cargs=None, ) def iswap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: @@ -5096,7 +5117,7 @@ def iswap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSe Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.ISwapGate, [], [qubit1, qubit2], cargs=None) + return self._append_standard_gate(StandardGate.ISwapGate, [], qargs=[qubit1, qubit2]) def cswap( self, @@ -5122,6 +5143,15 @@ def cswap( Returns: A handle to the instructions created. """ + # 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.CSwapGate, + [], + qargs=[control_qubit, target_qubit1, target_qubit2], + label=label, + ) + from .library.standard_gates.swap import CSwapGate return self.append( @@ -5142,7 +5172,7 @@ def sx(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.SXGate, None, qargs=[qubit]) + return self._append_standard_gate(StandardGate.SXGate, [], qargs=[qubit]) def sxdg(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.SXdgGate`. @@ -5155,7 +5185,7 @@ def sxdg(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.SXdgGate, None, qargs=[qubit]) + return self._append_standard_gate(StandardGate.SXdgGate, [], qargs=[qubit]) def csx( self, @@ -5179,6 +5209,12 @@ def csx( Returns: A handle to the instructions created. """ + # 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.CSXGate, [], qargs=[control_qubit, target_qubit], label=label + ) + from .library.standard_gates.sx import CSXGate return self.append( @@ -5199,7 +5235,7 @@ def t(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.TGate, [], [qubit], cargs=None) + return self._append_standard_gate(StandardGate.TGate, [], qargs=[qubit]) def tdg(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.TdgGate`. @@ -5212,7 +5248,7 @@ def tdg(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.TdgGate, [], [qubit], cargs=None) + return self._append_standard_gate(StandardGate.TdgGate, [], qargs=[qubit]) def u( self, @@ -5311,17 +5347,23 @@ def cx( Returns: A handle to the instructions created. """ - if ctrl_state is not None: - from .library.standard_gates.x import CXGate - - return self.append( - CXGate(label=label, ctrl_state=ctrl_state), - [control_qubit, target_qubit], + # 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.CXGate, [], - copy=False, + qargs=[control_qubit, target_qubit], + cargs=None, + label=label, ) - return self._append_standard_gate( - StandardGate.CXGate, [], qargs=[control_qubit, target_qubit], cargs=None, label=label + + from .library.standard_gates.x import CXGate + + return self.append( + CXGate(label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def dcx(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: @@ -5360,20 +5402,22 @@ def ccx( Returns: A handle to the instructions created. """ - if ctrl_state is not None: - from .library.standard_gates.x import CCXGate - - return self.append( - CCXGate(ctrl_state=ctrl_state), - [control_qubit1, control_qubit2, target_qubit], + # 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.CCXGate, [], - copy=False, + qargs=[control_qubit1, control_qubit2, target_qubit], + cargs=None, ) - return self._append_standard_gate( - StandardGate.CCXGate, + + from .library.standard_gates.x import CCXGate + + return self.append( + CCXGate(ctrl_state=ctrl_state), + [control_qubit1, control_qubit2, target_qubit], [], - qargs=[control_qubit1, control_qubit2, target_qubit], - cargs=None, + copy=False, ) def mcx( @@ -5495,18 +5539,23 @@ def cy( Returns: A handle to the instructions created. """ - if ctrl_state is not None: - from .library.standard_gates.y import CYGate - - return self.append( - CYGate(label=label, ctrl_state=ctrl_state), - [control_qubit, target_qubit], + # 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.CYGate, [], - copy=False, + qargs=[control_qubit, target_qubit], + cargs=None, + label=label, ) - return self._append_standard_gate( - StandardGate.CYGate, [], qargs=[control_qubit, target_qubit], cargs=None, label=label + from .library.standard_gates.y import CYGate + + return self.append( + CYGate(label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def z(self, qubit: QubitSpecifier) -> InstructionSet: @@ -5544,18 +5593,19 @@ def cz( Returns: A handle to the instructions created. """ - if ctrl_state is not None: - from .library.standard_gates.z import CZGate - - return self.append( - CZGate(label=label, ctrl_state=ctrl_state), - [control_qubit, target_qubit], - [], - copy=False, + # 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.CZGate, [], qargs=[control_qubit, target_qubit], label=label ) - return self._append_standard_gate( - StandardGate.CZGate, [], qargs=[control_qubit, target_qubit], cargs=None, label=label + from .library.standard_gates.z import CZGate + + return self.append( + CZGate(label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def ccz( diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index b20db4c79f94..6c0cc977e58a 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -73,6 +73,7 @@ def test_definitions(self): self.assertIsNone(rs_def) else: rs_def = QuantumCircuit._from_circuit_data(rs_def) + for rs_inst, py_inst in zip(rs_def._data, py_def._data): # Rust uses U but python still uses U3 and u2 if rs_inst.operation.name == "u": @@ -92,8 +93,8 @@ def test_definitions(self): [py_def.find_bit(x).index for x in py_inst.qubits], [rs_def.find_bit(x).index for x in rs_inst.qubits], ) - # Rust uses P but python still uses u1 - elif rs_inst.operation.name == "p": + # Rust uses p but python still uses u1/u3 in some cases + elif rs_inst.operation.name == "p" and not name in ["cp", "cs", "csdg"]: if py_inst.operation.name == "u1": self.assertEqual(py_inst.operation.name, "u1") self.assertEqual(rs_inst.operation.params, py_inst.operation.params) @@ -110,7 +111,14 @@ def test_definitions(self): [py_def.find_bit(x).index for x in py_inst.qubits], [rs_def.find_bit(x).index for x in rs_inst.qubits], ) - + # Rust uses cp but python still uses cu1 in some cases + elif rs_inst.operation.name == "cp": + self.assertEqual(py_inst.operation.name, "cu1") + self.assertEqual(rs_inst.operation.params, py_inst.operation.params) + self.assertEqual( + [py_def.find_bit(x).index for x in py_inst.qubits], + [rs_def.find_bit(x).index for x in rs_inst.qubits], + ) else: self.assertEqual(py_inst.operation.name, rs_inst.operation.name) self.assertEqual(rs_inst.operation.params, py_inst.operation.params) From 373e8a68c852a445fa9b11548a437c09e34d2d74 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 1 Jul 2024 13:59:21 +0100 Subject: [PATCH 14/17] Encapsulate Python sequence-like indexers (#12669) This encapsulates a lot of the common logic around Python sequence-like indexers (`SliceOrInt`) into iterators that handle adapting negative indices and slices in `usize` for containers of a given size. These indexers now all implement `ExactSizeIterator` and `DoubleEndedIterator`, so they can be used with all `Iterator` methods, and can be used (with `Iterator::map` and friends) as inputs to `PyList::new_bound`, which makes code simpler at all points of use. The special-cased uses of this kind of thing from `CircuitData` are replaced with the new forms. This had no measurable impact on performance on my machine, and removes a lot noise from error-handling and highly specialised functions. --- Cargo.lock | 2 + Cargo.toml | 1 + crates/accelerate/Cargo.toml | 1 + .../src/euler_one_qubit_decomposer.rs | 55 +-- crates/accelerate/src/two_qubit_decompose.rs | 59 +-- crates/circuit/Cargo.toml | 1 + crates/circuit/src/circuit_data.rs | 307 +++++--------- crates/circuit/src/lib.rs | 12 +- crates/circuit/src/slice.rs | 375 ++++++++++++++++++ 9 files changed, 517 insertions(+), 296 deletions(-) create mode 100644 crates/circuit/src/slice.rs diff --git a/Cargo.lock b/Cargo.lock index 380db7394bca..6ce26b3baea5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1180,6 +1180,7 @@ dependencies = [ "rayon", "rustworkx-core", "smallvec", + "thiserror", ] [[package]] @@ -1192,6 +1193,7 @@ dependencies = [ "numpy", "pyo3", "smallvec", + "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 13f43cfabcdc..a6ccf60f7f4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ num-complex = "0.4" ndarray = "^0.15.6" numpy = "0.21.0" smallvec = "1.13" +thiserror = "1.0" # Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an # actual C extension (the feature disables linking in `libpython`, which is forbidden in Python diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index 875243096514..9d6024783996 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -23,6 +23,7 @@ rustworkx-core = "0.15" faer = "0.19.1" itertools = "0.13.0" qiskit-circuit.workspace = true +thiserror.workspace = true [dependencies.smallvec] workspace = true diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 9f10f76de467..01725269bb84 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -21,9 +21,9 @@ use std::f64::consts::PI; use std::ops::Deref; use std::str::FromStr; -use pyo3::exceptions::{PyIndexError, PyValueError}; +use pyo3::exceptions::PyValueError; use pyo3::prelude::*; -use pyo3::types::PyString; +use pyo3::types::{PyList, PyString}; use pyo3::wrap_pyfunction; use pyo3::Python; @@ -31,8 +31,8 @@ use ndarray::prelude::*; use numpy::PyReadonlyArray2; use pyo3::pybacked::PyBackedStr; +use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex}; use qiskit_circuit::util::c64; -use qiskit_circuit::SliceOrInt; pub const ANGLE_ZERO_EPSILON: f64 = 1e-12; @@ -97,46 +97,15 @@ impl OneQubitGateSequence { Ok(self.gates.len()) } - fn __getitem__(&self, py: Python, idx: SliceOrInt) -> PyResult { - match idx { - SliceOrInt::Slice(slc) => { - let len = self.gates.len().try_into().unwrap(); - let indices = slc.indices(len)?; - let mut out_vec: Vec<(String, SmallVec<[f64; 3]>)> = Vec::new(); - // Start and stop will always be positive the slice api converts - // negatives to the index for example: - // list(range(5))[-1:-3:-1] - // will return start=4, stop=2, and step=-1 - let mut pos: isize = indices.start; - let mut cond = if indices.step < 0 { - pos > indices.stop - } else { - pos < indices.stop - }; - while cond { - if pos < len as isize { - out_vec.push(self.gates[pos as usize].clone()); - } - pos += indices.step; - if indices.step < 0 { - cond = pos > indices.stop; - } else { - cond = pos < indices.stop; - } - } - Ok(out_vec.into_py(py)) - } - SliceOrInt::Int(idx) => { - let len = self.gates.len() as isize; - if idx >= len || idx < -len { - Err(PyIndexError::new_err(format!("Invalid index, {idx}"))) - } else if idx < 0 { - let len = self.gates.len(); - Ok(self.gates[len - idx.unsigned_abs()].to_object(py)) - } else { - Ok(self.gates[idx as usize].to_object(py)) - } - } + fn __getitem__(&self, py: Python, idx: PySequenceIndex) -> PyResult { + match idx.with_len(self.gates.len())? { + SequenceIndex::Int(idx) => Ok(self.gates[idx].to_object(py)), + indices => Ok(PyList::new_bound( + py, + indices.iter().map(|pos| self.gates[pos].to_object(py)), + ) + .into_any() + .unbind()), } } } diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index 8637cb03c735..37061d5159f4 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -21,10 +21,6 @@ use approx::{abs_diff_eq, relative_eq}; use num_complex::{Complex, Complex64, ComplexFloat}; use num_traits::Zero; -use pyo3::exceptions::{PyIndexError, PyValueError}; -use pyo3::prelude::*; -use pyo3::wrap_pyfunction; -use pyo3::Python; use smallvec::{smallvec, SmallVec}; use std::f64::consts::{FRAC_1_SQRT_2, PI}; use std::ops::Deref; @@ -37,7 +33,11 @@ use ndarray::prelude::*; use ndarray::Zip; use numpy::PyReadonlyArray2; use numpy::{IntoPyArray, ToPyArray}; + +use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; use pyo3::pybacked::PyBackedStr; +use pyo3::types::PyList; use crate::convert_2q_block_matrix::change_basis; use crate::euler_one_qubit_decomposer::{ @@ -52,8 +52,8 @@ use rand_distr::StandardNormal; use rand_pcg::Pcg64Mcg; use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE}; +use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex}; use qiskit_circuit::util::{c64, GateArray1Q, GateArray2Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM}; -use qiskit_circuit::SliceOrInt; const PI2: f64 = PI / 2.; const PI4: f64 = PI / 4.; @@ -1131,46 +1131,15 @@ impl TwoQubitGateSequence { Ok(self.gates.len()) } - fn __getitem__(&self, py: Python, idx: SliceOrInt) -> PyResult { - match idx { - SliceOrInt::Slice(slc) => { - let len = self.gates.len().try_into().unwrap(); - let indices = slc.indices(len)?; - let mut out_vec: TwoQubitSequenceVec = Vec::new(); - // Start and stop will always be positive the slice api converts - // negatives to the index for example: - // list(range(5))[-1:-3:-1] - // will return start=4, stop=2, and step=- - let mut pos: isize = indices.start; - let mut cond = if indices.step < 0 { - pos > indices.stop - } else { - pos < indices.stop - }; - while cond { - if pos < len as isize { - out_vec.push(self.gates[pos as usize].clone()); - } - pos += indices.step; - if indices.step < 0 { - cond = pos > indices.stop; - } else { - cond = pos < indices.stop; - } - } - Ok(out_vec.into_py(py)) - } - SliceOrInt::Int(idx) => { - let len = self.gates.len() as isize; - if idx >= len || idx < -len { - Err(PyIndexError::new_err(format!("Invalid index, {idx}"))) - } else if idx < 0 { - let len = self.gates.len(); - Ok(self.gates[len - idx.unsigned_abs()].to_object(py)) - } else { - Ok(self.gates[idx as usize].to_object(py)) - } - } + fn __getitem__(&self, py: Python, idx: PySequenceIndex) -> PyResult { + match idx.with_len(self.gates.len())? { + SequenceIndex::Int(idx) => Ok(self.gates[idx].to_object(py)), + indices => Ok(PyList::new_bound( + py, + indices.iter().map(|pos| self.gates[pos].to_object(py)), + ) + .into_any() + .unbind()), } } } diff --git a/crates/circuit/Cargo.toml b/crates/circuit/Cargo.toml index dd7e878537d9..50160c7bac17 100644 --- a/crates/circuit/Cargo.toml +++ b/crates/circuit/Cargo.toml @@ -14,6 +14,7 @@ hashbrown.workspace = true num-complex.workspace = true ndarray.workspace = true numpy.workspace = true +thiserror.workspace = true [dependencies.pyo3] workspace = true diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 07f4579a4cd3..10e0691021a1 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -22,11 +22,12 @@ use crate::imports::{BUILTIN_LIST, QUBIT}; use crate::interner::{IndexedInterner, Interner, InternerKey}; use crate::operations::{Operation, OperationType, Param, StandardGate}; use crate::parameter_table::{ParamEntry, ParamTable, GLOBAL_PHASE_INDEX}; -use crate::{Clbit, Qubit, SliceOrInt}; +use crate::slice::{PySequenceIndex, SequenceIndex}; +use crate::{Clbit, Qubit}; use pyo3::exceptions::{PyIndexError, PyValueError}; use pyo3::prelude::*; -use pyo3::types::{PyList, PySet, PySlice, PyTuple, PyType}; +use pyo3::types::{PyList, PySet, PyTuple, PyType}; use pyo3::{intern, PyTraverseError, PyVisit}; use hashbrown::{HashMap, HashSet}; @@ -321,7 +322,7 @@ impl CircuitData { } pub fn append_inner(&mut self, py: Python, value: PyRef) -> PyResult { - let packed = self.pack(py, value)?; + let packed = self.pack(value)?; let new_index = self.data.len(); self.data.push(packed); self.update_param_table(py, new_index, None) @@ -744,184 +745,130 @@ impl CircuitData { } // Note: we also rely on this to make us iterable! - pub fn __getitem__(&self, py: Python, index: &Bound) -> PyResult { - // Internal helper function to get a specific - // instruction by index. - fn get_at( - self_: &CircuitData, - py: Python<'_>, - index: isize, - ) -> PyResult> { - let index = self_.convert_py_index(index)?; - if let Some(inst) = self_.data.get(index) { - let qubits = self_.qargs_interner.intern(inst.qubits_id); - let clbits = self_.cargs_interner.intern(inst.clbits_id); - Py::new( - py, - CircuitInstruction::new( - py, - inst.op.clone(), - self_.qubits.map_indices(qubits.value), - self_.clbits.map_indices(clbits.value), - inst.params.clone(), - inst.extra_attrs.clone(), - ), - ) - } else { - Err(PyIndexError::new_err(format!( - "No element at index {:?} in circuit data", - index - ))) - } - } - - if index.is_exact_instance_of::() { - let slice = self.convert_py_slice(index.downcast_exact::()?)?; - let result = slice - .into_iter() - .map(|i| get_at(self, py, i)) - .collect::>>()?; - Ok(result.into_py(py)) - } else { - Ok(get_at(self, py, index.extract()?)?.into_py(py)) + pub fn __getitem__(&self, py: Python, index: PySequenceIndex) -> PyResult { + // Get a single item, assuming the index is validated as in bounds. + let get_single = |index: usize| { + let inst = &self.data[index]; + let qubits = self.qargs_interner.intern(inst.qubits_id); + let clbits = self.cargs_interner.intern(inst.clbits_id); + CircuitInstruction::new( + py, + inst.op.clone(), + self.qubits.map_indices(qubits.value), + self.clbits.map_indices(clbits.value), + inst.params.clone(), + inst.extra_attrs.clone(), + ) + .into_py(py) + }; + match index.with_len(self.data.len())? { + SequenceIndex::Int(index) => Ok(get_single(index)), + indices => Ok(PyList::new_bound(py, indices.iter().map(get_single)).into_py(py)), } } - pub fn __delitem__(&mut self, py: Python, index: SliceOrInt) -> PyResult<()> { - match index { - SliceOrInt::Slice(slice) => { - let slice = { - let mut s = self.convert_py_slice(&slice)?; - if s.len() > 1 && s.first().unwrap() < s.last().unwrap() { - // Reverse the order so we're sure to delete items - // at the back first (avoids messing up indices). - s.reverse() - } - s - }; - for i in slice.into_iter() { - self.__delitem__(py, SliceOrInt::Int(i))?; - } - self.reindex_parameter_table(py)?; - Ok(()) - } - SliceOrInt::Int(index) => { - let index = self.convert_py_index(index)?; - if self.data.get(index).is_some() { - if index == self.data.len() { - // For individual removal from param table before - // deletion - self.remove_from_parameter_table(py, index)?; - self.data.remove(index); - } else { - // For delete in the middle delete before reindexing - self.data.remove(index); - self.reindex_parameter_table(py)?; - } - Ok(()) - } else { - Err(PyIndexError::new_err(format!( - "No element at index {:?} in circuit data", - index - ))) - } - } - } + pub fn __delitem__(&mut self, py: Python, index: PySequenceIndex) -> PyResult<()> { + self.delitem(py, index.with_len(self.data.len())?) } pub fn setitem_no_param_table_update( &mut self, - py: Python<'_>, - index: isize, - value: &Bound, + index: usize, + value: PyRef, ) -> PyResult<()> { - let index = self.convert_py_index(index)?; - let value: PyRef = value.downcast()?.borrow(); - let mut packed = self.pack(py, value)?; + let mut packed = self.pack(value)?; std::mem::swap(&mut packed, &mut self.data[index]); Ok(()) } - pub fn __setitem__( - &mut self, - py: Python<'_>, - index: SliceOrInt, - value: &Bound, - ) -> PyResult<()> { - match index { - SliceOrInt::Slice(slice) => { - let indices = slice.indices(self.data.len().try_into().unwrap())?; - let slice = self.convert_py_slice(&slice)?; - let values = value.iter()?.collect::>>>()?; - if indices.step != 1 && slice.len() != values.len() { - // A replacement of a different length when step isn't exactly '1' - // would result in holes. - return Err(PyValueError::new_err(format!( - "attempt to assign sequence of size {:?} to extended slice of size {:?}", - values.len(), - slice.len(), - ))); - } + pub fn __setitem__(&mut self, index: PySequenceIndex, value: &Bound) -> PyResult<()> { + fn set_single(slf: &mut CircuitData, index: usize, value: &Bound) -> PyResult<()> { + let py = value.py(); + let mut packed = slf.pack(value.downcast::()?.borrow())?; + slf.remove_from_parameter_table(py, index)?; + std::mem::swap(&mut packed, &mut slf.data[index]); + slf.update_param_table(py, index, None)?; + Ok(()) + } - for (i, v) in slice.iter().zip(values.iter()) { - self.__setitem__(py, SliceOrInt::Int(*i), v)?; + let py = value.py(); + match index.with_len(self.data.len())? { + SequenceIndex::Int(index) => set_single(self, index, value), + indices @ SequenceIndex::PosRange { + start, + stop, + step: 1, + } => { + // `list` allows setting a slice with step +1 to an arbitrary length. + let values = value.iter()?.collect::>>()?; + for (index, value) in indices.iter().zip(values.iter()) { + set_single(self, index, value)?; } - - if slice.len() > values.len() { - // Delete any extras. - let slice = PySlice::new_bound( + if indices.len() > values.len() { + self.delitem( py, - indices.start + values.len() as isize, - indices.stop, - 1isize, - ); - self.__delitem__(py, SliceOrInt::Slice(slice))?; + SequenceIndex::PosRange { + start: start + values.len(), + stop, + step: 1, + }, + )? } else { - // Insert any extra values. - for v in values.iter().skip(slice.len()).rev() { - let v: PyRef = v.extract()?; - self.insert(py, indices.stop, v)?; + for value in values[indices.len()..].iter().rev() { + self.insert(stop as isize, value.downcast()?.borrow())?; } } - Ok(()) } - SliceOrInt::Int(index) => { - let index = self.convert_py_index(index)?; - let value: PyRef = value.extract()?; - let mut packed = self.pack(py, value)?; - self.remove_from_parameter_table(py, index)?; - std::mem::swap(&mut packed, &mut self.data[index]); - self.update_param_table(py, index, None)?; - Ok(()) + indices => { + let values = value.iter()?.collect::>>()?; + if indices.len() == values.len() { + for (index, value) in indices.iter().zip(values.iter()) { + set_single(self, index, value)?; + } + Ok(()) + } else { + Err(PyValueError::new_err(format!( + "attempt to assign sequence of size {:?} to extended slice of size {:?}", + values.len(), + indices.len(), + ))) + } } } } - pub fn insert( - &mut self, - py: Python<'_>, - index: isize, - value: PyRef, - ) -> PyResult<()> { - let index = self.convert_py_index_clamped(index); - let old_len = self.data.len(); - let packed = self.pack(py, value)?; + pub fn insert(&mut self, mut index: isize, value: PyRef) -> PyResult<()> { + // `list.insert` has special-case extra clamping logic for its index argument. + let index = { + if index < 0 { + // This can't exceed `isize::MAX` because `self.data[0]` is larger than a byte. + index += self.data.len() as isize; + } + if index < 0 { + 0 + } else if index as usize > self.data.len() { + self.data.len() + } else { + index as usize + } + }; + let py = value.py(); + let packed = self.pack(value)?; self.data.insert(index, packed); - if index == old_len { - self.update_param_table(py, old_len, None)?; + if index == self.data.len() - 1 { + self.update_param_table(py, index, None)?; } else { self.reindex_parameter_table(py)?; } Ok(()) } - pub fn pop(&mut self, py: Python<'_>, index: Option) -> PyResult { - let index = - index.unwrap_or_else(|| std::cmp::max(0, self.data.len() as isize - 1).into_py(py)); - let item = self.__getitem__(py, index.bind(py))?; - - self.__delitem__(py, index.bind(py).extract()?)?; + pub fn pop(&mut self, py: Python<'_>, index: Option) -> PyResult { + let index = index.unwrap_or(PySequenceIndex::Int(-1)); + let native_index = index.with_len(self.data.len())?; + let item = self.__getitem__(py, index)?; + self.delitem(py, native_index)?; Ok(item) } @@ -931,7 +878,7 @@ impl CircuitData { value: &Bound, params: Option)>>, ) -> PyResult { - let packed = self.pack(py, value.try_borrow()?)?; + let packed = self.pack(value.try_borrow()?)?; let new_index = self.data.len(); self.data.push(packed); self.update_param_table(py, new_index, params) @@ -1175,56 +1122,22 @@ impl CircuitData { } impl CircuitData { - /// Converts a Python slice to a `Vec` of indices into - /// the instruction listing, [CircuitData.data]. - fn convert_py_slice(&self, slice: &Bound) -> PyResult> { - let indices = slice.indices(self.data.len().try_into().unwrap())?; - if indices.step > 0 { - Ok((indices.start..indices.stop) - .step_by(indices.step as usize) - .collect()) - } else { - let mut out = Vec::with_capacity(indices.slicelength as usize); - let mut x = indices.start; - while x > indices.stop { - out.push(x); - x += indices.step; - } - Ok(out) + /// Native internal driver of `__delitem__` that uses a Rust-space version of the + /// `SequenceIndex`. This assumes that the `SequenceIndex` contains only in-bounds indices, and + /// panics if not. + fn delitem(&mut self, py: Python, indices: SequenceIndex) -> PyResult<()> { + // We need to delete in reverse order so we don't invalidate higher indices with a deletion. + for index in indices.descending() { + self.data.remove(index); } - } - - /// Converts a Python index to an index into the instruction listing, - /// or one past its end. - /// If the resulting index would be < 0, clamps to 0. - /// If the resulting index would be > len(data), clamps to len(data). - fn convert_py_index_clamped(&self, index: isize) -> usize { - let index = if index < 0 { - index + self.data.len() as isize - } else { - index - }; - std::cmp::min(std::cmp::max(0, index), self.data.len() as isize) as usize - } - - /// Converts a Python index to an index into the instruction listing. - fn convert_py_index(&self, index: isize) -> PyResult { - let index = if index < 0 { - index + self.data.len() as isize - } else { - index - }; - - if index < 0 || index >= self.data.len() as isize { - return Err(PyIndexError::new_err(format!( - "Index {:?} is out of bounds.", - index, - ))); + if !indices.is_empty() { + self.reindex_parameter_table(py)?; } - Ok(index as usize) + Ok(()) } - fn pack(&mut self, py: Python, inst: PyRef) -> PyResult { + fn pack(&mut self, inst: PyRef) -> PyResult { + let py = inst.py(); let qubits = Interner::intern( &mut self.qargs_interner, InternerKey::Value(self.qubits.map_bits(inst.qubits.bind(py))?.collect()), diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 9fcaa36480cf..9f0a8017bf21 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -17,23 +17,13 @@ pub mod gate_matrix; pub mod imports; pub mod operations; pub mod parameter_table; +pub mod slice; pub mod util; mod bit_data; mod interner; use pyo3::prelude::*; -use pyo3::types::PySlice; - -/// A private enumeration type used to extract arguments to pymethod -/// that may be either an index or a slice -#[derive(FromPyObject)] -pub enum SliceOrInt<'a> { - // The order here defines the order the variants are tried in the FromPyObject` derivation. - // `Int` is _much_ more common, so that should be first. - Int(isize), - Slice(Bound<'a, PySlice>), -} pub type BitType = u32; #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] diff --git a/crates/circuit/src/slice.rs b/crates/circuit/src/slice.rs new file mode 100644 index 000000000000..056adff0a282 --- /dev/null +++ b/crates/circuit/src/slice.rs @@ -0,0 +1,375 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use thiserror::Error; + +use pyo3::exceptions::PyIndexError; +use pyo3::prelude::*; +use pyo3::types::PySlice; + +use self::sealed::{Descending, SequenceIndexIter}; + +/// A Python-space indexer for the standard `PySequence` type; a single integer or a slice. +/// +/// These come in as `isize`s from Python space, since Python typically allows negative indices. +/// Use `with_len` to specialize the index to a valid Rust-space indexer into a collection of the +/// given length. +pub enum PySequenceIndex<'py> { + Int(isize), + Slice(Bound<'py, PySlice>), +} + +impl<'py> FromPyObject<'py> for PySequenceIndex<'py> { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + // `slice` can't be subclassed in Python, so it's safe (and faster) to check for it exactly. + // The `downcast_exact` check is just a pointer comparison, so while `slice` is the less + // common input, doing that first has little-to-no impact on the speed of the `isize` path, + // while the reverse makes `slice` inputs significantly slower. + if let Ok(slice) = ob.downcast_exact::() { + return Ok(Self::Slice(slice.clone())); + } + Ok(Self::Int(ob.extract()?)) + } +} + +impl<'py> PySequenceIndex<'py> { + /// Specialize this index to a collection of the given `len`, returning a Rust-native type. + pub fn with_len(&self, len: usize) -> Result { + match self { + PySequenceIndex::Int(index) => { + let index = if *index >= 0 { + let index = *index as usize; + if index >= len { + return Err(PySequenceIndexError::OutOfRange); + } + index + } else { + len.checked_sub(index.unsigned_abs()) + .ok_or(PySequenceIndexError::OutOfRange)? + }; + Ok(SequenceIndex::Int(index)) + } + PySequenceIndex::Slice(slice) => { + let indices = slice + .indices(len as ::std::os::raw::c_long) + .map_err(PySequenceIndexError::from)?; + if indices.step > 0 { + Ok(SequenceIndex::PosRange { + start: indices.start as usize, + stop: indices.stop as usize, + step: indices.step as usize, + }) + } else { + Ok(SequenceIndex::NegRange { + // `indices.start` can be negative if the collection length is 0. + start: (indices.start >= 0).then_some(indices.start as usize), + // `indices.stop` can be negative if the 0 index should be output. + stop: (indices.stop >= 0).then_some(indices.stop as usize), + step: indices.step.unsigned_abs(), + }) + } + } + } + } +} + +/// Error type for problems encountered when calling methods on `PySequenceIndex`. +#[derive(Error, Debug)] +pub enum PySequenceIndexError { + #[error("index out of range")] + OutOfRange, + #[error(transparent)] + InnerPy(#[from] PyErr), +} +impl From for PyErr { + fn from(value: PySequenceIndexError) -> PyErr { + match value { + PySequenceIndexError::OutOfRange => PyIndexError::new_err("index out of range"), + PySequenceIndexError::InnerPy(inner) => inner, + } + } +} + +/// Rust-native version of a Python sequence-like indexer. +/// +/// Typically this is constructed by a call to `PySequenceIndex::with_len`, which guarantees that +/// all the indices will be in bounds for a collection of the given length. +/// +/// This splits the positive- and negative-step versions of the slice in two so it can be translated +/// more easily into static dispatch. This type can be converted into several types of iterator. +#[derive(Clone, Copy, Debug)] +pub enum SequenceIndex { + Int(usize), + PosRange { + start: usize, + stop: usize, + step: usize, + }, + NegRange { + start: Option, + stop: Option, + step: usize, + }, +} + +impl SequenceIndex { + /// The number of indices this refers to. + pub fn len(&self) -> usize { + match self { + Self::Int(_) => 1, + Self::PosRange { start, stop, step } => { + let gap = stop.saturating_sub(*start); + gap / *step + (gap % *step != 0) as usize + } + Self::NegRange { start, stop, step } => 'arm: { + let Some(start) = start else { break 'arm 0 }; + let gap = stop + .map(|stop| start.saturating_sub(stop)) + .unwrap_or(*start + 1); + gap / step + (gap % step != 0) as usize + } + } + } + + pub fn is_empty(&self) -> bool { + // This is just to keep clippy happy; the length is already fairly inexpensive to calculate. + self.len() == 0 + } + + /// Get an iterator over the indices. This will be a single-item iterator for the case of + /// `Self::Int`, but you probably wanted to destructure off that case beforehand anyway. + pub fn iter(&self) -> SequenceIndexIter { + match self { + Self::Int(value) => SequenceIndexIter::Int(Some(*value)), + Self::PosRange { start, step, .. } => SequenceIndexIter::PosRange { + lowest: *start, + step: *step, + indices: 0..self.len(), + }, + Self::NegRange { start, step, .. } => SequenceIndexIter::NegRange { + // We can unwrap `highest` to an arbitrary value if `None`, because in that case the + // `len` is 0 and the iterator will not yield any objects. + highest: start.unwrap_or_default(), + step: *step, + indices: 0..self.len(), + }, + } + } + + // Get an iterator over the contained indices that is guaranteed to iterate from the highest + // index to the lowest. + pub fn descending(&self) -> Descending { + Descending(self.iter()) + } +} + +impl IntoIterator for SequenceIndex { + type Item = usize; + type IntoIter = SequenceIndexIter; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +// Private module to make it impossible to construct or inspect the internals of the iterator types +// from outside this file, while still allowing them to be used. +mod sealed { + /// Custom iterator for indices for Python sequence-likes. + /// + /// In the range types, the `indices ` are `Range` objects that run from 0 to the length of the + /// iterator. In theory, we could generate the iterators ourselves, but that ends up with a lot of + /// boilerplate. + #[derive(Clone, Debug)] + pub enum SequenceIndexIter { + Int(Option), + PosRange { + lowest: usize, + step: usize, + indices: ::std::ops::Range, + }, + NegRange { + highest: usize, + // The step of the iterator, but note that this is a negative range, so the forwards method + // steps downwards from `upper` towards `lower`. + step: usize, + indices: ::std::ops::Range, + }, + } + impl Iterator for SequenceIndexIter { + type Item = usize; + + #[inline] + fn next(&mut self) -> Option { + match self { + Self::Int(value) => value.take(), + Self::PosRange { + lowest, + step, + indices, + } => indices.next().map(|idx| *lowest + idx * *step), + Self::NegRange { + highest, + step, + indices, + } => indices.next().map(|idx| *highest - idx * *step), + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self { + Self::Int(None) => (0, Some(0)), + Self::Int(Some(_)) => (1, Some(1)), + Self::PosRange { indices, .. } | Self::NegRange { indices, .. } => { + indices.size_hint() + } + } + } + } + impl DoubleEndedIterator for SequenceIndexIter { + #[inline] + fn next_back(&mut self) -> Option { + match self { + Self::Int(value) => value.take(), + Self::PosRange { + lowest, + step, + indices, + } => indices.next_back().map(|idx| *lowest + idx * *step), + Self::NegRange { + highest, + step, + indices, + } => indices.next_back().map(|idx| *highest - idx * *step), + } + } + } + impl ExactSizeIterator for SequenceIndexIter {} + + pub struct Descending(pub SequenceIndexIter); + impl Iterator for Descending { + type Item = usize; + + #[inline] + fn next(&mut self) -> Option { + match self.0 { + SequenceIndexIter::Int(_) | SequenceIndexIter::NegRange { .. } => self.0.next(), + SequenceIndexIter::PosRange { .. } => self.0.next_back(), + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + } + impl DoubleEndedIterator for Descending { + #[inline] + fn next_back(&mut self) -> Option { + match self.0 { + SequenceIndexIter::Int(_) | SequenceIndexIter::NegRange { .. } => { + self.0.next_back() + } + SequenceIndexIter::PosRange { .. } => self.0.next(), + } + } + } + impl ExactSizeIterator for Descending {} +} + +#[cfg(test)] +mod test { + use super::*; + + /// Get a set of test parametrisations for iterator methods. The second argument is the + /// expected values from a normal forward iteration. + fn index_iterator_cases() -> impl Iterator)> { + let pos = |start, stop, step| SequenceIndex::PosRange { start, stop, step }; + let neg = |start, stop, step| SequenceIndex::NegRange { start, stop, step }; + + [ + (SequenceIndex::Int(3), vec![3]), + (pos(0, 5, 2), vec![0, 2, 4]), + (pos(2, 10, 1), vec![2, 3, 4, 5, 6, 7, 8, 9]), + (pos(1, 15, 3), vec![1, 4, 7, 10, 13]), + (neg(Some(3), None, 1), vec![3, 2, 1, 0]), + (neg(Some(3), None, 2), vec![3, 1]), + (neg(Some(2), Some(0), 1), vec![2, 1]), + (neg(Some(2), Some(0), 2), vec![2]), + (neg(Some(2), Some(0), 3), vec![2]), + (neg(Some(10), Some(2), 3), vec![10, 7, 4]), + (neg(None, None, 1), vec![]), + (neg(None, None, 3), vec![]), + ] + .into_iter() + } + + /// Test that the index iterator's implementation of `ExactSizeIterator` is correct. + #[test] + fn index_iterator() { + for (index, forwards) in index_iterator_cases() { + // We're testing that all the values are the same, and the `size_hint` is correct at + // every single point. + let mut actual = Vec::new(); + let mut sizes = Vec::new(); + let mut iter = index.iter(); + loop { + sizes.push(iter.size_hint().0); + if let Some(next) = iter.next() { + actual.push(next); + } else { + break; + } + } + assert_eq!( + actual, forwards, + "values for {:?}\nActual : {:?}\nExpected: {:?}", + index, actual, forwards, + ); + let expected_sizes = (0..=forwards.len()).rev().collect::>(); + assert_eq!( + sizes, expected_sizes, + "sizes for {:?}\nActual : {:?}\nExpected: {:?}", + index, sizes, expected_sizes, + ); + } + } + + /// Test that the index iterator's implementation of `DoubleEndedIterator` is correct. + #[test] + fn reversed_index_iterator() { + for (index, forwards) in index_iterator_cases() { + let actual = index.iter().rev().collect::>(); + let expected = forwards.into_iter().rev().collect::>(); + assert_eq!( + actual, expected, + "reversed {:?}\nActual : {:?}\nExpected: {:?}", + index, actual, expected, + ); + } + } + + /// Test that `descending` produces its values in reverse-sorted order. + #[test] + fn descending() { + for (index, mut expected) in index_iterator_cases() { + let actual = index.descending().collect::>(); + expected.sort_by(|left, right| right.cmp(left)); + assert_eq!( + actual, expected, + "descending {:?}\nActual : {:?}\nExpected: {:?}", + index, actual, expected, + ); + } + } +} From 5deed7a73875277ff4e5669497d4ff1aa0abf785 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Mon, 1 Jul 2024 16:02:14 +0300 Subject: [PATCH 15/17] improving `quantum_causal_cone` method in python (#12668) * improving quantum_causal_cone * fixing release note --- qiskit/dagcircuit/dagcircuit.py | 60 +++++++----- ...-quantum-causal-cone-f63eaaa9ab658811.yaml | 5 + test/python/dagcircuit/test_dagcircuit.py | 97 +++++++++++++++++++ 3 files changed, 136 insertions(+), 26 deletions(-) create mode 100644 releasenotes/notes/improve-quantum-causal-cone-f63eaaa9ab658811.yaml diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index d14340a8cb9b..626b7ef053ec 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -2264,36 +2264,44 @@ def quantum_causal_cone(self, qubit): output_node = self.output_map.get(qubit, None) if not output_node: raise DAGCircuitError(f"Qubit {qubit} is not part of this circuit.") - # Add the qubit to the causal cone. - qubits_to_check = {qubit} - # Add predecessors of output node to the queue. - queue = deque(self.predecessors(output_node)) - # While queue isn't empty + qubits_in_cone = {qubit} + queue = deque(self.quantum_predecessors(output_node)) + + # The processed_non_directive_nodes stores the set of processed non-directive nodes. + # This is an optimization to avoid considering the same non-directive node multiple + # times when reached from different paths. + # The directive nodes (such as barriers or measures) are trickier since when processing + # them we only add their predecessors that intersect qubits_in_cone. Hence, directive + # nodes have to be considered multiple times. + processed_non_directive_nodes = set() + while queue: - # Pop first element. node_to_check = queue.popleft() - # Check whether element is input or output node. + if isinstance(node_to_check, DAGOpNode): - # Keep all the qubits in the operation inside a set. - qubit_set = set(node_to_check.qargs) - # Check if there are any qubits in common and that the operation is not a barrier. - if ( - len(qubit_set.intersection(qubits_to_check)) > 0 - and node_to_check.op.name != "barrier" - and not getattr(node_to_check.op, "_directive") - ): - # If so, add all the qubits to the causal cone. - qubits_to_check = qubits_to_check.union(qubit_set) - # For each predecessor of the current node, filter input/output nodes, - # also make sure it has at least one qubit in common. Then append. - for node in self.quantum_predecessors(node_to_check): - if ( - isinstance(node, DAGOpNode) - and len(qubits_to_check.intersection(set(node.qargs))) > 0 - ): - queue.append(node) - return qubits_to_check + # If the operation is not a directive (in particular not a barrier nor a measure), + # we do not do anything if it was already processed. Otherwise, we add its qubits + # to qubits_in_cone, and append its predecessors to queue. + if not getattr(node_to_check.op, "_directive"): + if node_to_check in processed_non_directive_nodes: + continue + qubits_in_cone = qubits_in_cone.union(set(node_to_check.qargs)) + processed_non_directive_nodes.add(node_to_check) + for pred in self.quantum_predecessors(node_to_check): + if isinstance(pred, DAGOpNode): + queue.append(pred) + else: + # Directives (such as barriers and measures) may be defined over all the qubits, + # yet not all of these qubits should be considered in the causal cone. So we + # only add those predecessors that have qubits in common with qubits_in_cone. + for pred in self.quantum_predecessors(node_to_check): + if isinstance(pred, DAGOpNode) and not qubits_in_cone.isdisjoint( + set(pred.qargs) + ): + queue.append(pred) + + return qubits_in_cone def properties(self): """Return a dictionary of circuit properties.""" diff --git a/releasenotes/notes/improve-quantum-causal-cone-f63eaaa9ab658811.yaml b/releasenotes/notes/improve-quantum-causal-cone-f63eaaa9ab658811.yaml new file mode 100644 index 000000000000..5a072f481abc --- /dev/null +++ b/releasenotes/notes/improve-quantum-causal-cone-f63eaaa9ab658811.yaml @@ -0,0 +1,5 @@ +--- +features_circuits: + - | + Improved performance of the method :meth:`.DAGCircuit.quantum_causal_cone` by not examining + the same non-directive node multiple times when reached from different paths. diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 4ab4e392cbb1..3e4d4bf4e686 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -3475,6 +3475,103 @@ def test_causal_cone_barriers(self): self.assertEqual(result, expected) + def test_causal_cone_more_barriers(self): + """Test causal cone for circuit with barriers. This example shows + why barriers may need to be examined multiple times.""" + + # q0_0: ──■────────░──────────────────────── + # ┌─┴─┐ ░ + # q0_1: ┤ X ├──────░───■──────────────────── + # ├───┤ ░ ┌─┴─┐┌───┐┌───┐┌───┐ + # q0_2: ┤ H ├──────░─┤ X ├┤ H ├┤ H ├┤ H ├─X─ + # ├───┤┌───┐ ░ └───┘└───┘└───┘└───┘ │ + # q0_3: ┤ H ├┤ X ├─░──────────────────────X─ + # ├───┤└─┬─┘ ░ + # q0_4: ┤ X ├──■───░──────────────────────── + # └─┬─┘ ░ + # q0_5: ──■────────░──────────────────────── + + qreg = QuantumRegister(6) + qc = QuantumCircuit(qreg) + qc.cx(0, 1) + qc.h(2) + qc.cx(5, 4) + qc.h(3) + qc.cx(4, 3) + qc.barrier() + qc.cx(1, 2) + + qc.h(2) + qc.h(2) + qc.h(2) + qc.swap(2, 3) + + dag = circuit_to_dag(qc) + + result = dag.quantum_causal_cone(qreg[2]) + expected = {qreg[0], qreg[1], qreg[2], qreg[3], qreg[4], qreg[5]} + + self.assertEqual(result, expected) + + def test_causal_cone_measure(self): + """Test causal cone with measures.""" + + # ┌───┐ ░ ┌─┐ + # q_0: ┤ H ├─░─┤M├──────────── + # ├───┤ ░ └╥┘┌─┐ + # q_1: ┤ H ├─░──╫─┤M├───────── + # ├───┤ ░ ║ └╥┘┌─┐ + # q_2: ┤ H ├─░──╫──╫─┤M├────── + # ├───┤ ░ ║ ║ └╥┘┌─┐ + # q_3: ┤ H ├─░──╫──╫──╫─┤M├─── + # ├───┤ ░ ║ ║ ║ └╥┘┌─┐ + # q_4: ┤ H ├─░──╫──╫──╫──╫─┤M├ + # └───┘ ░ ║ ║ ║ ║ └╥┘ + # c: 5/═════════╬══╬══╬══╬══╬═ + # ║ ║ ║ ║ ║ + # meas: 5/═════════╩══╩══╩══╩══╩═ + # 0 1 2 3 4 + + qreg = QuantumRegister(5) + creg = ClassicalRegister(5) + circuit = QuantumCircuit(qreg, creg) + for i in range(5): + circuit.h(i) + circuit.measure_all() + + dag = circuit_to_dag(circuit) + + result = dag.quantum_causal_cone(dag.qubits[1]) + expected = {qreg[1]} + self.assertEqual(result, expected) + + def test_reconvergent_paths(self): + """Test circuit with reconvergent paths.""" + + # q0_0: ──■─────────■─────────■─────────■─────────■─────────■─────── + # ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ + # q0_1: ┤ X ├──■──┤ X ├──■──┤ X ├──■──┤ X ├──■──┤ X ├──■──┤ X ├──■── + # └───┘┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐ + # q0_2: ──■──┤ X ├──■──┤ X ├──■──┤ X ├──■──┤ X ├──■──┤ X ├──■──┤ X ├ + # ┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐└───┘ + # q0_3: ┤ X ├─────┤ X ├─────┤ X ├─────┤ X ├─────┤ X ├─────┤ X ├───── + # └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ + # q0_4: ──────────────────────────────────────────────────────────── + + qreg = QuantumRegister(5) + circuit = QuantumCircuit(qreg) + + for _ in range(6): + circuit.cx(0, 1) + circuit.cx(2, 3) + circuit.cx(1, 2) + + dag = circuit_to_dag(circuit) + + result = dag.quantum_causal_cone(dag.qubits[1]) + expected = {qreg[0], qreg[1], qreg[2], qreg[3]} + self.assertEqual(result, expected) + if __name__ == "__main__": unittest.main() From 67fd35a2eacf977f0cd2539b7ae5b4c4210f50bf Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 1 Jul 2024 16:45:52 +0200 Subject: [PATCH 16/17] Fix `replace_block_with_op` on operations with wrong number of qubits (#12637) * fix illegal op insertion * rm dangling print * fix PauliEvolution * Update qiskit/dagcircuit/dagcircuit.py Co-authored-by: John Lapeyre --------- Co-authored-by: John Lapeyre --- qiskit/dagcircuit/dagcircuit.py | 7 +++++++ .../pauli_2q_evolution_commutation.py | 6 +++++- ...n-illegal-replace-block-50cef8da757a580a.yaml | 7 +++++++ test/python/dagcircuit/test_dagcircuit.py | 16 ++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/raise-on-illegal-replace-block-50cef8da757a580a.yaml diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 626b7ef053ec..944e2df625b0 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1342,6 +1342,13 @@ def replace_block_with_op( block_cargs.sort(key=wire_pos_map.get) new_node = DAGOpNode(op, block_qargs, block_cargs, dag=self) + # check the op to insert matches the number of qubits we put it on + if op.num_qubits != len(block_qargs): + raise DAGCircuitError( + f"Number of qubits in the replacement operation ({op.num_qubits}) is not equal to " + f"the number of qubits in the block ({len(block_qargs)})!" + ) + try: new_node._node_id = self._multi_graph.contract_nodes( block_ids, new_node, check_cycle=cycle_check diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py index 641b40c9f3e1..beadc884465b 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py @@ -51,7 +51,11 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: sub_dag = self._decompose_to_2q(dag, node.op) block_op = Commuting2qBlock(set(sub_dag.op_nodes())) - wire_order = {wire: idx for idx, wire in enumerate(dag.qubits)} + wire_order = { + wire: idx + for idx, wire in enumerate(sub_dag.qubits) + if wire not in sub_dag.idle_wires() + } dag.replace_block_with_op([node], block_op, wire_order) return dag diff --git a/releasenotes/notes/raise-on-illegal-replace-block-50cef8da757a580a.yaml b/releasenotes/notes/raise-on-illegal-replace-block-50cef8da757a580a.yaml new file mode 100644 index 000000000000..f4971fe520a0 --- /dev/null +++ b/releasenotes/notes/raise-on-illegal-replace-block-50cef8da757a580a.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Previously, :meth:`.DAGCircuit.replace_block_with_op` allowed to place an + ``n``-qubit operation onto a block of ``m`` qubits, leaving the DAG in an + invalid state. This behavior has been fixed, and the attempt will raise + a :class:`.DAGCircuitError`. diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 3e4d4bf4e686..26f7e4788083 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -2903,6 +2903,22 @@ def test_single_node_block(self): self.assertEqual(expected_dag.count_ops(), dag.count_ops()) self.assertIsInstance(new_node.op, XGate) + def test_invalid_replacement_size(self): + """Test inserting an operation on a wrong number of qubits raises.""" + + # two X gates, normal circuit + qc = QuantumCircuit(2) + qc.x(range(2)) + + # mutilate the DAG + dag = circuit_to_dag(qc) + to_replace = list(dag.op_nodes()) + new_node = XGate() + idx_map = {node.qargs[0]: i for i, node in enumerate(to_replace)} + + with self.assertRaises(DAGCircuitError): + dag.replace_block_with_op(to_replace, new_node, idx_map) + def test_replace_control_flow_block(self): """Test that we can replace a block of control-flow nodes with a single one.""" body = QuantumCircuit(1) From ed87f2fa502ea0d63b072c227b5e813b6320fa56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Abad=20L=C3=B3pez?= <109400222+GuillermoAbadLopez@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:57:00 +0200 Subject: [PATCH 17/17] Add warning for bad `justify` input, in `circuit_drawer` (#12458) * add warning for bad justify input in circuit_drawer * change justify after raising error * typo indentation + improving warning string * Undo max_lenght limit by autoformater (120 > 105) * Make lines fullfill 105 max lenght requirement * Solve regex matching parenthesis problem and don't trigger the wanring for default value * Change justify default value to "left", & add deprecation wrapper * Change and extend warnings tests * Solve various layers of same argument DeprecationWarning * Added a clarification comment for the solution, about multiple deprecationwarnings * Ignore cyclic import error, as above * Apply suggestions from code review Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> * Apply suggestions from code review * Improve DeprecationWarning readability, and fix warning checks tests * Remove `_is_valid_justify_arg` from `@deprecate_arg`, for solving circular import * in `deprecate_arg` change `since` to "1.2.0" * black formater suggestion * negate conditions for `predicate` in `@deprecate_arg` * remove `pending=True`, since then warning is not raised * change qiskit version on tests * remove assert Regex for DeprecationWarning * Add release note, and remove two undesired changes in imports * changing release note naming from "_" to "-" * Add extra line in the end, for lint * Redid release file from start, with shorter name, and correct spacings * Remove final spaces in all lines... * Try without deprecations_visualization section.. * Solve, bad Sphinx spacing, go back to deprecations_visualization * Go back to `None` as default value * Simplifying deprecation logic * Remove unused imports and changed missing default value * Improve docstring for public methods * Improve error readbility and testing of it with regex --------- Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> --- qiskit/circuit/quantumcircuit.py | 10 ++--- qiskit/visualization/circuit/_utils.py | 42 +++++++++++++++---- .../circuit/circuit_visualization.py | 23 +++++----- ...it-draw-warn-justify-03434d30cccda452.yaml | 13 ++++++ .../visualization/test_circuit_drawer.py | 24 +++++++++-- 5 files changed, 84 insertions(+), 28 deletions(-) create mode 100644 releasenotes/notes/circuit-draw-warn-justify-03434d30cccda452.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 0019a15443e7..6d41e6fdcd25 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -3257,11 +3257,11 @@ def draw( alternative value set. For example, ``circuit_reverse_bits = True``. plot_barriers: Enable/disable drawing barriers in the output circuit. Defaults to ``True``. - justify: Options are ``left``, ``right`` or ``none``. If - anything else is supplied, it defaults to left justified. It refers - to where gates should be placed in the output circuit if there is - an option. ``none`` results in each gate being placed in its own - column. + justify: Options are ``"left"``, ``"right"`` or ``"none"`` (str). + If anything else is supplied, left justified will be used instead. + It refers to where gates should be placed in the output circuit if + there is an option. ``none`` results in each gate being placed in + its own column. Defaults to ``left``. vertical_compression: ``high``, ``medium`` or ``low``. It merges the lines generated by the `text` output so the drawing will take less vertical room. Default is ``medium``. Only used by diff --git a/qiskit/visualization/circuit/_utils.py b/qiskit/visualization/circuit/_utils.py index ca29794b9626..2077a3891542 100644 --- a/qiskit/visualization/circuit/_utils.py +++ b/qiskit/visualization/circuit/_utils.py @@ -14,21 +14,25 @@ import re from collections import OrderedDict +from warnings import warn import numpy as np from qiskit.circuit import ( + ClassicalRegister, Clbit, + ControlFlowOp, ControlledGate, Delay, Gate, Instruction, Measure, + QuantumCircuit, + Qubit, ) +from qiskit.circuit.annotated_operation import AnnotatedOperation, InverseModifier, PowerModifier from qiskit.circuit.controlflow import condition_resources from qiskit.circuit.library import PauliEvolutionGate -from qiskit.circuit import ClassicalRegister, QuantumCircuit, Qubit, ControlFlowOp -from qiskit.circuit.annotated_operation import AnnotatedOperation, InverseModifier, PowerModifier from qiskit.circuit.tools import pi_check from qiskit.converters import circuit_to_dag from qiskit.utils import optionals as _optionals @@ -370,6 +374,29 @@ def generate_latex_label(label): return final_str.replace(" ", "\\,") # Put in proper spaces +def _get_valid_justify_arg(justify): + """Returns a valid `justify` argument, warning if necessary.""" + if isinstance(justify, str): + justify = justify.lower() + + if justify is None: + justify = "left" + + if justify not in ("left", "right", "none"): + # This code should be changed to an error raise, once the deprecation is complete. + warn( + f"Setting QuantumCircuit.draw()’s or circuit_drawer()'s justify argument: {justify}, to a " + "value other than 'left', 'right', 'none' or None (='left'). Default 'left' will be used. " + "Support for invalid justify arguments is deprecated as of qiskit 1.2.0. Starting no " + "earlier than 3 months after the release date, invalid arguments will error.", + DeprecationWarning, + 2, + ) + justify = "left" + + return justify + + def _get_layered_instructions( circuit, reverse_bits=False, justify=None, idle_wires=True, wire_order=None, wire_map=None ): @@ -384,9 +411,10 @@ def _get_layered_instructions( reverse_bits (bool): If true the order of the bits in the registers is reversed. justify (str) : `left`, `right` or `none`. Defaults to `left`. Says how - the circuit should be justified. + the circuit should be justified. If an invalid value is provided, + default `left` will be used. idle_wires (bool): Include idle wires. Default is True. - wire_order (list): A list of ints that modifies the order of the bits + wire_order (list): A list of ints that modifies the order of the bits. Returns: Tuple(list,list,list): To be consumed by the visualizer directly. @@ -394,11 +422,7 @@ def _get_layered_instructions( Raises: VisualizationError: if both reverse_bits and wire_order are entered. """ - if justify: - justify = justify.lower() - - # default to left - justify = justify if justify in ("right", "none") else "left" + justify = _get_valid_justify_arg(justify) if wire_map is not None: qubits = [bit for bit in wire_map if isinstance(bit, Qubit)] diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py index 146de9d32dee..33f6cadb46d3 100644 --- a/qiskit/visualization/circuit/circuit_visualization.py +++ b/qiskit/visualization/circuit/circuit_visualization.py @@ -28,21 +28,22 @@ import logging import os +import shutil import subprocess import tempfile -import shutil import typing from warnings import warn from qiskit import user_config -from qiskit.utils import optionals as _optionals from qiskit.circuit import ControlFlowOp, Measure +from qiskit.utils import optionals as _optionals + +from ..exceptions import VisualizationError +from ..utils import _trim as trim_image +from . import _utils from . import latex as _latex -from . import text as _text from . import matplotlib as _matplotlib -from . import _utils -from ..utils import _trim as trim_image -from ..exceptions import VisualizationError +from . import text as _text if typing.TYPE_CHECKING: from typing import Any @@ -131,11 +132,11 @@ def circuit_drawer( alternative value set. For example, ``circuit_reverse_bits = True``. plot_barriers: Enable/disable drawing barriers in the output circuit. Defaults to ``True``. - justify: Options are ``left``, ``right`` or ``none``. If - anything else is supplied, it defaults to left justified. It refers - to where gates should be placed in the output circuit if there is - an option. ``none`` results in each gate being placed in its own - column. + justify: Options are ``"left"``, ``"right"`` or ``"none"`` (str). + If anything else is supplied, left justified will be used instead. + It refers to where gates should be placed in the output circuit if + there is an option. ``none`` results in each gate being placed in + its own column. Defaults to ``left``. vertical_compression: ``high``, ``medium`` or ``low``. It merges the lines generated by the `text` output so the drawing will take less vertical room. Default is ``medium``. Only used by diff --git a/releasenotes/notes/circuit-draw-warn-justify-03434d30cccda452.yaml b/releasenotes/notes/circuit-draw-warn-justify-03434d30cccda452.yaml new file mode 100644 index 000000000000..9ae15212fd56 --- /dev/null +++ b/releasenotes/notes/circuit-draw-warn-justify-03434d30cccda452.yaml @@ -0,0 +1,13 @@ +--- +deprecations_visualization: + - | + The ``justify`` argument of :func:`circuit_drawer` or :meth:`QuantumCircuit.draw`, will + no longer support invalid values (previously changing them to the default), and in a future + release they will error. Valid justify values are ``"left"``, ``"right"`` or ``"none"``. +fixes: + - | + Fixed an issue where :func:`circuit_drawer` or the :meth:`QuantumCircuit.draw` method would + not raise a warning when an invalid value was passed to the ``justify`` argument, before + changing it to the default. Now, it will raise a warning if an invalid value is passed. + Valid justify values are ``"left"``, ``"right"`` or ``"none"``. Refer to + `#12089 ` for more details. diff --git a/test/python/visualization/test_circuit_drawer.py b/test/python/visualization/test_circuit_drawer.py index e6b430c4ee82..d459bd945046 100644 --- a/test/python/visualization/test_circuit_drawer.py +++ b/test/python/visualization/test_circuit_drawer.py @@ -14,16 +14,16 @@ import os import pathlib +import re import shutil import tempfile import unittest import warnings from unittest.mock import patch -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, visualization from qiskit.utils import optionals -from qiskit import visualization -from qiskit.visualization.circuit import text, styles +from qiskit.visualization.circuit import styles, text from qiskit.visualization.exceptions import VisualizationError from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -241,6 +241,24 @@ def test_reverse_bits(self): result = visualization.circuit_drawer(circuit, output="text", reverse_bits=True) self.assertEqual(result.__str__(), expected) + def test_warning_for_bad_justify_argument(self): + """Test that the correct DeprecationWarning is raised when the justify parameter is badly input, + for both of the public interfaces.""" + circuit = QuantumCircuit() + bad_arg = "bad" + error_message = re.escape( + f"Setting QuantumCircuit.draw()’s or circuit_drawer()'s justify argument: {bad_arg}, to a " + "value other than 'left', 'right', 'none' or None (='left'). Default 'left' will be used. " + "Support for invalid justify arguments is deprecated as of qiskit 1.2.0. Starting no " + "earlier than 3 months after the release date, invalid arguments will error.", + ) + + with self.assertWarnsRegex(DeprecationWarning, error_message): + visualization.circuit_drawer(circuit, justify=bad_arg) + + with self.assertWarnsRegex(DeprecationWarning, error_message): + circuit.draw(justify=bad_arg) + @unittest.skipUnless(optionals.HAS_PYLATEX, "needs pylatexenc for LaTeX conversion") def test_no_explict_cregbundle(self): """Test no explicit cregbundle should not raise warnings about being disabled