From 6d6dce327264201d7158baf6af2152bbe387fc57 Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Tue, 18 Jun 2024 05:12:04 -0400 Subject: [PATCH 01/89] Remove Eric from Rust bot notifications (#12596) --- qiskit_bot.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit_bot.yaml b/qiskit_bot.yaml index 2467665e0d00..edff5997c8fe 100644 --- a/qiskit_bot.yaml +++ b/qiskit_bot.yaml @@ -28,7 +28,6 @@ notifications: ".*\\.rs$|^Cargo": - "`@mtreinish`" - "`@kevinhartman`" - - "@Eric-Arellano" "(?!.*pulse.*)\\bvisualization\\b": - "@enavarro51" "^docs/": From d4e795b43146b01103df608d1cf55425b4bfd765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Tue, 18 Jun 2024 21:28:59 +0200 Subject: [PATCH 02/89] Add rust representation for SGates, TGates, and iSwap gate (#12598) * Add SGate, SdgGate, iSWAP, TGate, and TdgGate to standard gates in rust. Add missing gate definitions that depended on these gates (marked as todo). * Add fast path to circuit methods, fix sneaky bugs, unskip cy and sx tests. * Unskip ccx test too! * Fix black --- crates/circuit/src/gate_matrix.rs | 24 ++ crates/circuit/src/imports.rs | 14 +- crates/circuit/src/operations.rs | 220 ++++++++++++++++-- .../circuit/library/standard_gates/iswap.py | 3 + qiskit/circuit/library/standard_gates/s.py | 5 + qiskit/circuit/library/standard_gates/sx.py | 2 + qiskit/circuit/library/standard_gates/t.py | 5 + qiskit/circuit/quantumcircuit.py | 24 +- test/python/circuit/test_rust_equivalence.py | 2 +- 9 files changed, 265 insertions(+), 34 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 72e1087637c0..ad8c918e73bc 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -63,6 +63,11 @@ pub static SX_GATE: [[Complex64; 2]; 2] = [ [c64(0.5, -0.5), c64(0.5, 0.5)], ]; +pub static SXDG_GATE: [[Complex64; 2]; 2] = [ + [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 Z_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(-1., 0.)]]; @@ -199,6 +204,25 @@ pub static SWAP_GATE: [[Complex64; 4]; 4] = [ [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 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 S_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., 1.)]]; +pub static SDG_GATE: [[Complex64; 2]; 2] = + [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., -1.)]]; + +pub static T_GATE: [[Complex64; 2]; 2] = [ + [c64(1., 0.), c64(0., 0.)], + [c64(0., 0.), c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)], +]; +pub static TDG_GATE: [[Complex64; 2]; 2] = [ + [c64(1., 0.), c64(0., 0.)], + [c64(0., 0.), c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], +]; #[inline] pub fn global_phase_gate(theta: f64) -> [[Complex64; 1]; 1] { diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 050f7f2e053c..8db3b88fd7d2 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -77,7 +77,7 @@ pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = /// when a gate is added directly via the StandardGate path and there isn't a Python object /// to poll the _standard_gate attribute for. /// -/// NOTE: the order here is significant it must match the StandardGate variant's number must match +/// NOTE: the order here is significant, the StandardGate variant's number must match /// index of it's entry in this table. This is all done statically for performance static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // ZGate = 0 @@ -119,6 +119,18 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ ["qiskit.circuit.library.standard_gates.p", "PhaseGate"], // UGate = 17 ["qiskit.circuit.library.standard_gates.u", "UGate"], + // SGate = 18 + ["qiskit.circuit.library.standard_gates.s", "SGate"], + // SdgGate = 19 + ["qiskit.circuit.library.standard_gates.s", "SdgGate"], + // TGate = 20 + ["qiskit.circuit.library.standard_gates.s", "TGate"], + // TdgGate = 21 + ["qiskit.circuit.library.standard_gates.s", "TdgGate"], + // SXdgGate = 22 + ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"], + // iSWAPGate = 23 + ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"], ]; /// 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 ead1b8ee1ebb..9048c55d9d48 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -24,6 +24,9 @@ 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 @@ -194,13 +197,21 @@ pub enum StandardGate { HGate = 15, PhaseGate = 16, UGate = 17, + SGate = 18, + SdgGate = 19, + TGate = 20, + TdgGate = 21, + SXdgGate = 22, + ISwapGate = 23, } -static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = - [1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1]; +static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ + 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, +]; -static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = - [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3]; +static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, +]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "z", @@ -221,6 +232,12 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "h", "p", "u", + "s", + "sdg", + "t", + "tdg", + "sxdg", + "iswap", ]; #[pymethods] @@ -269,7 +286,8 @@ impl StandardGate { // // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) -pub const STANDARD_GATE_SIZE: usize = 18; + +pub const STANDARD_GATE_SIZE: usize = 24; impl Operation for StandardGate { fn name(&self) -> &str { @@ -350,6 +368,10 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::SX_GATE).to_owned()), _ => None, }, + Self::SXdgGate => match params { + [] => Some(aview2(&gate_matrix::SXDG_GATE).to_owned()), + _ => None, + }, Self::GlobalPhaseGate => match params { [Param::Float(theta)] => { Some(aview2(&gate_matrix::global_phase_gate(*theta)).to_owned()) @@ -374,6 +396,26 @@ impl Operation for StandardGate { } _ => None, }, + Self::SGate => match params { + [] => Some(aview2(&gate_matrix::S_GATE).to_owned()), + _ => None, + }, + Self::SdgGate => match params { + [] => Some(aview2(&gate_matrix::SDG_GATE).to_owned()), + _ => None, + }, + Self::TGate => match params { + [] => Some(aview2(&gate_matrix::T_GATE).to_owned()), + _ => None, + }, + Self::TdgGate => match params { + [] => Some(aview2(&gate_matrix::TDG_GATE).to_owned()), + _ => None, + }, + Self::ISwapGate => match params { + [] => Some(aview2(&gate_matrix::ISWAP_GATE).to_owned()), + _ => None, + }, } } @@ -401,11 +443,7 @@ impl Operation for StandardGate { 1, [( Self::UGate, - smallvec![ - Param::Float(PI), - Param::Float(PI / 2.), - Param::Float(PI / 2.), - ], + smallvec![Param::Float(PI), Param::Float(PI2), Param::Float(PI2),], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -445,9 +483,56 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CYGate => todo!("Add when we have S and S dagger"), + Self::CYGate => 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::SdgGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1), + (Self::SGate, smallvec![], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CXGate => None, - Self::CCXGate => todo!("Add when we have T and TDagger"), + Self::CCXGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q2 = smallvec![Qubit(2)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q0_2 = smallvec![Qubit(0), Qubit(2)]; + let q1_2 = smallvec![Qubit(1), Qubit(2)]; + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + (Self::HGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q1_2.clone()), + (Self::TdgGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q0_2.clone()), + (Self::TGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q1_2), + (Self::TdgGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q0_2), + (Self::TGate, smallvec![], q1.clone()), + (Self::TGate, smallvec![], q2.clone()), + (Self::HGate, smallvec![], q2), + (Self::CXGate, smallvec![], q0_1.clone()), + (Self::TGate, smallvec![], smallvec![Qubit(0)]), + (Self::TdgGate, smallvec![], q1), + (Self::CXGate, smallvec![], q0_1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::RXGate => todo!("Add when we have R"), Self::RYGate => todo!("Add when we have R"), Self::RZGate => Python::with_gil(|py| -> Option { @@ -501,7 +586,36 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::SXGate => todo!("Add when we have S dagger"), + Self::SXGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [ + (Self::SdgGate, smallvec![], smallvec![Qubit(0)]), + (Self::HGate, smallvec![], smallvec![Qubit(0)]), + (Self::SdgGate, smallvec![], smallvec![Qubit(0)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::SXdgGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [ + (Self::SGate, smallvec![], smallvec![Qubit(0)]), + (Self::HGate, smallvec![], smallvec![Qubit(0)]), + (Self::SGate, smallvec![], smallvec![Qubit(0)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::GlobalPhaseGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates(py, 0, [], params[0].clone()) @@ -516,7 +630,7 @@ impl Operation for StandardGate { 1, [( Self::UGate, - smallvec![Param::Float(PI / 2.), Param::Float(0.), Param::Float(PI)], + smallvec![Param::Float(PI2), Param::Float(0.), Param::Float(PI)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -540,6 +654,84 @@ impl Operation for StandardGate { ) }), Self::UGate => None, + Self::SGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(PI2)], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::SdgGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(-PI2)], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::TGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(PI4)], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::TdgGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(-PI4)], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::ISwapGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::SGate, smallvec![], smallvec![Qubit(0)]), + (Self::SGate, smallvec![], smallvec![Qubit(1)]), + (Self::HGate, smallvec![], smallvec![Qubit(0)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), + (Self::HGate, smallvec![], smallvec![Qubit(1)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), } } diff --git a/qiskit/circuit/library/standard_gates/iswap.py b/qiskit/circuit/library/standard_gates/iswap.py index 50d3a6bb3473..8074990a3840 100644 --- a/qiskit/circuit/library/standard_gates/iswap.py +++ b/qiskit/circuit/library/standard_gates/iswap.py @@ -19,6 +19,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 from .xx_plus_yy import XXPlusYYGate @@ -85,6 +86,8 @@ class iSwapGate(SingletonGate): \end{pmatrix} """ + _standard_gate = StandardGate.ISwapGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create new iSwap gate.""" super().__init__("iswap", 2, [], label=label, duration=duration, unit=unit) diff --git a/qiskit/circuit/library/standard_gates/s.py b/qiskit/circuit/library/standard_gates/s.py index 6fde1c6544e5..f62d16a10d40 100644 --- a/qiskit/circuit/library/standard_gates/s.py +++ b/qiskit/circuit/library/standard_gates/s.py @@ -20,6 +20,7 @@ from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array +from qiskit._accelerate.circuit import StandardGate _S_ARRAY = numpy.array([[1, 0], [0, 1j]]) @@ -57,6 +58,8 @@ class SGate(SingletonGate): Equivalent to a :math:`\pi/2` radian rotation about the Z axis. """ + _standard_gate = StandardGate.SGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create new S gate.""" super().__init__("s", 1, [], label=label, duration=duration, unit=unit) @@ -134,6 +137,8 @@ class SdgGate(SingletonGate): Equivalent to a :math:`-\pi/2` radian rotation about the Z axis. """ + _standard_gate = StandardGate.SdgGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create new Sdg gate.""" super().__init__("sdg", 1, [], label=label, duration=duration, unit=unit) diff --git a/qiskit/circuit/library/standard_gates/sx.py b/qiskit/circuit/library/standard_gates/sx.py index 93ca85da0198..72e4a8f9b5bf 100644 --- a/qiskit/circuit/library/standard_gates/sx.py +++ b/qiskit/circuit/library/standard_gates/sx.py @@ -167,6 +167,8 @@ class SXdgGate(SingletonGate): = e^{-i \pi/4} \sqrt{X}^{\dagger} """ + _standard_gate = StandardGate.SXdgGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create new SXdg gate.""" super().__init__("sxdg", 1, [], label=label, duration=duration, unit=unit) diff --git a/qiskit/circuit/library/standard_gates/t.py b/qiskit/circuit/library/standard_gates/t.py index 87a38d9d44c1..e4301168ac53 100644 --- a/qiskit/circuit/library/standard_gates/t.py +++ b/qiskit/circuit/library/standard_gates/t.py @@ -21,6 +21,7 @@ from qiskit.circuit.library.standard_gates.p import PhaseGate 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, (1 + 1j) / math.sqrt(2)]]) @@ -55,6 +56,8 @@ class TGate(SingletonGate): Equivalent to a :math:`\pi/4` radian rotation about the Z axis. """ + _standard_gate = StandardGate.TGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create new T gate.""" super().__init__("t", 1, [], label=label, duration=duration, unit=unit) @@ -130,6 +133,8 @@ class TdgGate(SingletonGate): Equivalent to a :math:`-\pi/4` radian rotation about the Z axis. """ + _standard_gate = StandardGate.TdgGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create new Tdg gate.""" super().__init__("tdg", 1, [], label=label, duration=duration, unit=unit) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 238a2682522a..e3fbf40da683 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -4979,9 +4979,7 @@ def s(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - from .library.standard_gates.s import SGate - - return self.append(SGate(), [qubit], [], copy=False) + return self._append_standard_gate(StandardGate.SGate, [], [qubit], cargs=None) def sdg(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.SdgGate`. @@ -4994,9 +4992,7 @@ def sdg(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - from .library.standard_gates.s import SdgGate - - return self.append(SdgGate(), [qubit], [], copy=False) + return self._append_standard_gate(StandardGate.SdgGate, [], [qubit], cargs=None) def cs( self, @@ -5089,9 +5085,7 @@ def iswap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSe Returns: A handle to the instructions created. """ - from .library.standard_gates.iswap import iSwapGate - - return self.append(iSwapGate(), [qubit1, qubit2], [], copy=False) + return self._append_standard_gate(StandardGate.ISwapGate, [], [qubit1, qubit2], cargs=None) def cswap( self, @@ -5150,9 +5144,7 @@ def sxdg(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - from .library.standard_gates.sx import SXdgGate - - return self.append(SXdgGate(), [qubit], [], copy=False) + return self._append_standard_gate(StandardGate.SXdgGate, None, qargs=[qubit]) def csx( self, @@ -5196,9 +5188,7 @@ def t(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - from .library.standard_gates.t import TGate - - return self.append(TGate(), [qubit], [], copy=False) + return self._append_standard_gate(StandardGate.TGate, [], [qubit], cargs=None) def tdg(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.TdgGate`. @@ -5211,9 +5201,7 @@ def tdg(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - from .library.standard_gates.t import TdgGate - - return self.append(TdgGate(), [qubit], [], copy=False) + return self._append_standard_gate(StandardGate.TdgGate, [], [qubit], cargs=None) def u( self, diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index 06d4ed86a60a..bb09ae4caf3f 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -21,7 +21,7 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping -SKIP_LIST = {"cy", "ccx", "rx", "ry", "ecr", "sx"} +SKIP_LIST = {"rx", "ry", "ecr"} CUSTOM_MAPPING = {"x", "rz"} From 53667d167e2de2f841d3b781877427f0b459289b Mon Sep 17 00:00:00 2001 From: Joe Schulte Date: Wed, 19 Jun 2024 03:05:56 -0400 Subject: [PATCH 03/89] Remove consider-using-f-string lint rule and updates (#12423) * remove consider-using-f-string lint rule and updates * reverting a latex update * f-string update based on review * Update qiskit/circuit/library/hamiltonian_gate.py Co-authored-by: Matthew Treinish * Update qiskit/circuit/tools/pi_check.py Co-authored-by: Matthew Treinish * Update qiskit/circuit/tools/pi_check.py Co-authored-by: Matthew Treinish * updates after merge * Update qiskit/providers/models/backendproperties.py Co-authored-by: Julien Gacon * Update qiskit/synthesis/linear/cnot_synth.py Co-authored-by: Julien Gacon * updates from PR * latex fixes --------- Co-authored-by: Matthew Treinish Co-authored-by: Julien Gacon --- pyproject.toml | 1 - qiskit/assembler/assemble_circuits.py | 4 +- qiskit/assembler/assemble_schedules.py | 17 ++---- qiskit/assembler/disassemble.py | 2 +- .../classicalfunction/boolean_expression.py | 2 +- .../classical_function_visitor.py | 10 ++-- qiskit/circuit/classicalfunction/utils.py | 2 +- qiskit/circuit/classicalregister.py | 2 +- qiskit/circuit/delay.py | 2 +- qiskit/circuit/duration.py | 4 +- qiskit/circuit/equivalence.py | 12 ++-- qiskit/circuit/gate.py | 4 +- qiskit/circuit/instruction.py | 9 +-- .../arithmetic/linear_pauli_rotations.py | 2 +- .../library/arithmetic/piecewise_chebyshev.py | 2 +- .../piecewise_linear_pauli_rotations.py | 2 +- .../piecewise_polynomial_pauli_rotations.py | 2 +- .../arithmetic/polynomial_pauli_rotations.py | 2 +- .../data_preparation/state_preparation.py | 14 ++--- qiskit/circuit/library/graph_state.py | 2 +- qiskit/circuit/library/hamiltonian_gate.py | 3 +- .../circuit/library/hidden_linear_function.py | 2 +- qiskit/circuit/library/n_local/n_local.py | 5 +- qiskit/circuit/library/n_local/qaoa_ansatz.py | 14 ++--- qiskit/circuit/library/overlap.py | 6 +- qiskit/circuit/library/standard_gates/u3.py | 2 +- qiskit/circuit/parameter.py | 4 +- qiskit/circuit/parameterexpression.py | 18 +++--- qiskit/circuit/quantumcircuit.py | 13 +++-- qiskit/circuit/quantumregister.py | 2 +- qiskit/circuit/register.py | 12 ++-- qiskit/circuit/tools/pi_check.py | 8 +-- qiskit/compiler/assembler.py | 24 ++++---- qiskit/compiler/scheduler.py | 2 +- qiskit/compiler/transpiler.py | 4 +- qiskit/converters/circuit_to_gate.py | 12 ++-- qiskit/converters/circuit_to_instruction.py | 6 +- qiskit/dagcircuit/dagcircuit.py | 57 +++++++++---------- qiskit/dagcircuit/dagdependency.py | 8 +-- qiskit/dagcircuit/dagdependency_v2.py | 8 +-- qiskit/dagcircuit/dagdepnode.py | 2 +- qiskit/passmanager/flow_controllers.py | 2 +- qiskit/passmanager/passmanager.py | 2 +- qiskit/providers/backend.py | 8 +-- .../basic_provider/basic_provider_tools.py | 2 +- .../basic_provider/basic_simulator.py | 4 +- .../providers/fake_provider/fake_backend.py | 4 +- .../fake_provider/generic_backend_v2.py | 4 +- qiskit/providers/models/backendproperties.py | 4 +- qiskit/providers/models/pulsedefaults.py | 7 +-- qiskit/providers/options.py | 4 +- qiskit/pulse/configuration.py | 20 +++---- qiskit/pulse/instruction_schedule_map.py | 6 +- qiskit/pulse/instructions/acquire.py | 15 +++-- qiskit/pulse/instructions/instruction.py | 5 +- qiskit/pulse/library/samplers/decorators.py | 14 ++--- qiskit/pulse/library/symbolic_pulses.py | 7 +-- qiskit/pulse/library/waveform.py | 7 +-- qiskit/pulse/macros.py | 8 +-- qiskit/pulse/parser.py | 14 ++--- qiskit/pulse/schedule.py | 26 +++------ qiskit/pulse/transforms/alignments.py | 4 +- qiskit/pulse/utils.py | 3 +- qiskit/qasm2/export.py | 12 ++-- qiskit/qobj/converters/pulse_instruction.py | 6 +- qiskit/qobj/pulse_qobj.py | 27 ++++----- qiskit/qobj/qasm_qobj.py | 35 +++++------- qiskit/qpy/binary_io/circuits.py | 10 ++-- qiskit/qpy/binary_io/value.py | 6 +- qiskit/qpy/interface.py | 5 +- .../operators/channel/quantum_channel.py | 9 +-- .../quantum_info/operators/channel/superop.py | 4 +- .../operators/dihedral/dihedral_circuits.py | 4 +- qiskit/quantum_info/operators/measures.py | 2 +- qiskit/quantum_info/operators/op_shape.py | 32 ++++------- qiskit/quantum_info/operators/operator.py | 15 ++--- qiskit/quantum_info/operators/predicates.py | 1 + .../operators/symplectic/base_pauli.py | 18 +++--- .../operators/symplectic/pauli.py | 6 +- .../operators/symplectic/pauli_list.py | 17 +++--- .../operators/symplectic/sparse_pauli_op.py | 10 ++-- qiskit/quantum_info/states/densitymatrix.py | 13 ++--- qiskit/quantum_info/states/statevector.py | 12 ++-- qiskit/result/counts.py | 2 +- .../correlated_readout_mitigator.py | 4 +- .../mitigation/local_readout_mitigator.py | 4 +- qiskit/result/mitigation/utils.py | 4 +- qiskit/result/models.py | 21 +++---- qiskit/result/result.py | 25 +++----- qiskit/scheduler/lowering.py | 4 +- qiskit/synthesis/linear/cnot_synth.py | 3 +- .../two_qubit/two_qubit_decompose.py | 2 +- qiskit/transpiler/coupling.py | 6 +- qiskit/transpiler/layout.py | 6 +- .../passes/basis/basis_translator.py | 4 +- .../passes/basis/unroll_3q_or_more.py | 2 +- .../passes/basis/unroll_custom_definitions.py | 6 +- .../passes/calibration/rzx_builder.py | 6 +- .../optimization/inverse_cancellation.py | 4 +- .../passes/optimization/optimize_1q_gates.py | 2 +- .../transpiler/passes/routing/sabre_swap.py | 2 +- .../passes/routing/stochastic_swap.py | 4 +- .../passes/synthesis/high_level_synthesis.py | 4 +- qiskit/transpiler/passes/utils/check_map.py | 6 +- qiskit/transpiler/passes/utils/error.py | 4 +- qiskit/transpiler/passes/utils/fixed_point.py | 6 +- .../transpiler/preset_passmanagers/common.py | 4 +- qiskit/transpiler/target.py | 4 +- qiskit/user_config.py | 12 ++-- .../circuit/circuit_visualization.py | 4 +- qiskit/visualization/circuit/latex.py | 33 +++++------ qiskit/visualization/circuit/matplotlib.py | 2 +- qiskit/visualization/circuit/text.py | 13 +++-- qiskit/visualization/dag_visualization.py | 4 +- qiskit/visualization/pulse_v2/core.py | 2 +- .../pulse_v2/generators/frame.py | 7 +-- .../pulse_v2/generators/waveform.py | 14 ++--- qiskit/visualization/pulse_v2/layouts.py | 6 +- .../pulse_v2/plotters/matplotlib.py | 3 +- qiskit/visualization/state_visualization.py | 11 ++-- .../timeline/plotters/matplotlib.py | 3 +- test/benchmarks/circuit_construction.py | 2 +- test/python/circuit/test_circuit_qasm.py | 2 +- test/python/circuit/test_circuit_registers.py | 2 +- test/python/circuit/test_instructions.py | 10 ++-- test/python/circuit/test_parameters.py | 15 +++-- test/python/dagcircuit/test_dagcircuit.py | 8 +-- test/python/providers/test_fake_backends.py | 7 +-- .../operators/symplectic/test_clifford.py | 10 ++-- .../quantum_info/states/test_densitymatrix.py | 4 +- test/python/result/test_mitigators.py | 54 +++++++----------- .../aqc/fast_gradient/test_layer1q.py | 9 ++- .../aqc/fast_gradient/test_layer2q.py | 10 ++-- .../synthesis/test_permutation_synthesis.py | 8 +-- test/python/test_user_config.py | 2 +- test/python/transpiler/test_pass_scheduler.py | 2 +- .../visualization/timeline/test_generators.py | 11 ++-- .../randomized/test_transpiler_equivalence.py | 7 +-- test/utils/base.py | 12 ++-- test/visual/results.py | 31 +++++----- tools/build_standard_commutations.py | 4 +- tools/find_stray_release_notes.py | 2 +- tools/verify_headers.py | 12 ++-- 143 files changed, 516 insertions(+), 684 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0740861c98c6..2f62557aa15b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -218,7 +218,6 @@ disable = [ # TODO(#9614): these were added in modern Pylint. Decide if we want to enable them. If so, # remove from here and fix the issues. Else, move it above this section and add a comment # with the rationale - "consider-using-f-string", "no-member", # for dynamically created members "not-context-manager", "unnecessary-lambda-assignment", # do not want to implement diff --git a/qiskit/assembler/assemble_circuits.py b/qiskit/assembler/assemble_circuits.py index b27fe47a02e6..a3d9b6bbb549 100644 --- a/qiskit/assembler/assemble_circuits.py +++ b/qiskit/assembler/assemble_circuits.py @@ -153,9 +153,9 @@ def _assemble_circuit( conditional_reg_idx = memory_slots + max_conditional_idx conversion_bfunc = QasmQobjInstruction( name="bfunc", - mask="0x%X" % mask, + mask="0x%X" % mask, # pylint: disable=consider-using-f-string relation="==", - val="0x%X" % val, + val="0x%X" % val, # pylint: disable=consider-using-f-string register=conditional_reg_idx, ) instructions.append(conversion_bfunc) diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index c60c28ff9a50..2d5ebefa2fd1 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -152,7 +152,7 @@ def _assemble_experiments( # TODO: add other experimental header items (see circuit assembler) qobj_experiment_header = qobj.QobjExperimentHeader( memory_slots=max_memory_slot + 1, # Memory slots are 0 indexed - name=sched.name or "Experiment-%d" % idx, + name=sched.name or f"Experiment-{idx}", metadata=metadata, ) @@ -306,18 +306,11 @@ def _validate_meas_map( common_next = next_inst_qubits.intersection(meas_set) if common_instr_qubits and common_next: raise QiskitError( - "Qubits {} and {} are in the same measurement grouping: {}. " + f"Qubits {common_instr_qubits} and {common_next} are in the same measurement " + f"grouping: {meas_map}. " "They must either be acquired at the same time, or disjointly" - ". Instead, they were acquired at times: {}-{} and " - "{}-{}".format( - common_instr_qubits, - common_next, - meas_map, - inst[0][0], - inst_end_time, - next_inst_time, - next_inst_time + next_inst[0][1], - ) + f". Instead, they were acquired at times: {inst[0][0]}-{inst_end_time} and " + f"{next_inst_time}-{next_inst_time + next_inst[0][1]}" ) diff --git a/qiskit/assembler/disassemble.py b/qiskit/assembler/disassemble.py index c94b108c4b25..127bbd35eb26 100644 --- a/qiskit/assembler/disassemble.py +++ b/qiskit/assembler/disassemble.py @@ -109,7 +109,7 @@ def _qobj_to_circuit_cals(qobj, pulse_lib): config = (tuple(gate["qubits"]), tuple(gate["params"])) cal = { config: pulse.Schedule( - name="{} {} {}".format(gate["name"], str(gate["params"]), str(gate["qubits"])) + name=f"{gate['name']} {str(gate['params'])} {str(gate['qubits'])}" ) } for instruction in gate["instructions"]: diff --git a/qiskit/circuit/classicalfunction/boolean_expression.py b/qiskit/circuit/classicalfunction/boolean_expression.py index 0f4a53494af4..e517f36db02b 100644 --- a/qiskit/circuit/classicalfunction/boolean_expression.py +++ b/qiskit/circuit/classicalfunction/boolean_expression.py @@ -116,7 +116,7 @@ def from_dimacs_file(cls, filename: str): expr_obj = cls.__new__(cls) if not isfile(filename): - raise FileNotFoundError("The file %s does not exists." % filename) + raise FileNotFoundError(f"The file {filename} does not exists.") expr_obj._tweedledum_bool_expression = BoolFunction.from_dimacs_file(filename) num_qubits = ( diff --git a/qiskit/circuit/classicalfunction/classical_function_visitor.py b/qiskit/circuit/classicalfunction/classical_function_visitor.py index dfe8b956b092..be89e8ee7f81 100644 --- a/qiskit/circuit/classicalfunction/classical_function_visitor.py +++ b/qiskit/circuit/classicalfunction/classical_function_visitor.py @@ -83,7 +83,7 @@ def bit_binop(self, op, values): """Uses ClassicalFunctionVisitor.bitops to extend self._network""" bitop = ClassicalFunctionVisitor.bitops.get(type(op)) if not bitop: - raise ClassicalFunctionParseError("Unknown binop.op %s" % op) + raise ClassicalFunctionParseError(f"Unknown binop.op {op}") binop = getattr(self._network, bitop) left_type, left_signal = values[0] @@ -112,19 +112,19 @@ def visit_UnaryOp(self, node): operand_type, operand_signal = self.visit(node.operand) if operand_type != "Int1": raise ClassicalFunctionCompilerTypeError( - "UntaryOp.op %s only support operation on Int1s for now" % node.op + f"UntaryOp.op {node.op} only support operation on Int1s for now" ) bitop = ClassicalFunctionVisitor.bitops.get(type(node.op)) if not bitop: raise ClassicalFunctionCompilerTypeError( - "UntaryOp.op %s does not operate with Int1 type " % node.op + f"UntaryOp.op {node.op} does not operate with Int1 type " ) return "Int1", getattr(self._network, bitop)(operand_signal) def visit_Name(self, node): """Reduce variable names.""" if node.id not in self.scopes[-1]: - raise ClassicalFunctionParseError("out of scope: %s" % node.id) + raise ClassicalFunctionParseError(f"out of scope: {node.id}") return self.scopes[-1][node.id] def generic_visit(self, node): @@ -143,7 +143,7 @@ def generic_visit(self, node): ), ): return super().generic_visit(node) - raise ClassicalFunctionParseError("Unknown node: %s" % type(node)) + raise ClassicalFunctionParseError(f"Unknown node: {type(node)}") def extend_scope(self, args_node: _ast.arguments) -> None: """Add the arguments to the scope""" diff --git a/qiskit/circuit/classicalfunction/utils.py b/qiskit/circuit/classicalfunction/utils.py index 237a8b838530..75dcd3e20a7a 100644 --- a/qiskit/circuit/classicalfunction/utils.py +++ b/qiskit/circuit/classicalfunction/utils.py @@ -47,7 +47,7 @@ def _convert_tweedledum_operator(op): if op.kind() == "py_operator": return op.py_op() else: - raise RuntimeError("Unrecognized operator: %s" % op.kind()) + raise RuntimeError(f"Unrecognized operator: {op.kind()}") # TODO: need to deal with cbits too! if op.num_controls() > 0: diff --git a/qiskit/circuit/classicalregister.py b/qiskit/circuit/classicalregister.py index 7a21e6b2fa58..802d8c602e2c 100644 --- a/qiskit/circuit/classicalregister.py +++ b/qiskit/circuit/classicalregister.py @@ -43,7 +43,7 @@ def __init__(self, register=None, index=None): super().__init__(register, index) else: raise CircuitError( - "Clbit needs a ClassicalRegister and %s was provided" % type(register).__name__ + f"Clbit needs a ClassicalRegister and {type(register).__name__} was provided" ) diff --git a/qiskit/circuit/delay.py b/qiskit/circuit/delay.py index a333125a5a2b..25e7a6f3356c 100644 --- a/qiskit/circuit/delay.py +++ b/qiskit/circuit/delay.py @@ -32,7 +32,7 @@ def __init__(self, duration, unit="dt"): unit: the unit of the duration. Must be ``"dt"`` or an SI-prefixed seconds unit. """ if unit not in {"s", "ms", "us", "ns", "ps", "dt"}: - raise CircuitError("Unknown unit %s is specified." % unit) + raise CircuitError(f"Unknown unit {unit} is specified.") super().__init__("delay", 1, 0, params=[duration], unit=unit) diff --git a/qiskit/circuit/duration.py b/qiskit/circuit/duration.py index 6acb230baadd..fdf6e99e6117 100644 --- a/qiskit/circuit/duration.py +++ b/qiskit/circuit/duration.py @@ -35,8 +35,8 @@ def duration_in_dt(duration_in_sec: float, dt_in_sec: float) -> int: rounding_error = abs(duration_in_sec - res * dt_in_sec) if rounding_error > 1e-15: warnings.warn( - "Duration is rounded to %d [dt] = %e [s] from %e [s]" - % (res, res * dt_in_sec, duration_in_sec), + f"Duration is rounded to {res:d} [dt] = {res * dt_in_sec:e} [s] " + f"from {duration_in_sec:e} [s]", UserWarning, ) return res diff --git a/qiskit/circuit/equivalence.py b/qiskit/circuit/equivalence.py index 45921c3f2293..17912517d244 100644 --- a/qiskit/circuit/equivalence.py +++ b/qiskit/circuit/equivalence.py @@ -249,7 +249,7 @@ def _build_basis_graph(self): ) node_map[decomp_basis] = decomp_basis_node - label = "{}\n{}".format(str(params), str(decomp) if num_qubits <= 5 else "...") + label = f"{str(params)}\n{str(decomp) if num_qubits <= 5 else '...'}" graph.add_edge( node_map[basis], node_map[decomp_basis], @@ -273,8 +273,8 @@ def _raise_if_param_mismatch(gate_params, circuit_parameters): if set(gate_parameters) != circuit_parameters: raise CircuitError( "Cannot add equivalence between circuit and gate " - "of different parameters. Gate params: {}. " - "Circuit params: {}.".format(gate_parameters, circuit_parameters) + f"of different parameters. Gate params: {gate_parameters}. " + f"Circuit params: {circuit_parameters}." ) @@ -282,10 +282,8 @@ def _raise_if_shape_mismatch(gate, circuit): if gate.num_qubits != circuit.num_qubits or gate.num_clbits != circuit.num_clbits: raise CircuitError( "Cannot add equivalence between circuit and gate " - "of different shapes. Gate: {} qubits and {} clbits. " - "Circuit: {} qubits and {} clbits.".format( - gate.num_qubits, gate.num_clbits, circuit.num_qubits, circuit.num_clbits - ) + f"of different shapes. Gate: {gate.num_qubits} qubits and {gate.num_clbits} clbits. " + f"Circuit: {circuit.num_qubits} qubits and {circuit.num_clbits} clbits." ) diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index 132526775860..d2c88f40bdb6 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -177,7 +177,7 @@ def _broadcast_3_or_more_args(qargs: list) -> Iterator[tuple[list, list]]: for arg in zip(*qargs): yield list(arg), [] else: - raise CircuitError("Not sure how to combine these qubit arguments:\n %s\n" % qargs) + raise CircuitError(f"Not sure how to combine these qubit arguments:\n {qargs}\n") def broadcast_arguments(self, qargs: list, cargs: list) -> Iterable[tuple[list, list]]: """Validation and handling of the arguments and its relationship. @@ -236,7 +236,7 @@ def broadcast_arguments(self, qargs: list, cargs: list) -> Iterable[tuple[list, elif len(qargs) >= 3: return Gate._broadcast_3_or_more_args(qargs) else: - raise CircuitError("This gate cannot handle %i arguments" % len(qargs)) + raise CircuitError(f"This gate cannot handle {len(qargs)} arguments") def validate_parameter(self, parameter): """Gate parameters should be int, float, or ParameterExpression""" diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 44155783d409..f53c5b9e9b3c 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -81,7 +81,7 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt raise CircuitError("num_qubits and num_clbits must be integer.") if num_qubits < 0 or num_clbits < 0: raise CircuitError( - "bad instruction dimensions: %d qubits, %d clbits." % num_qubits, num_clbits + f"bad instruction dimensions: {num_qubits} qubits, {num_clbits} clbits." ) self._name = name self._num_qubits = num_qubits @@ -222,8 +222,9 @@ def __repr__(self) -> str: str: A representation of the Instruction instance with the name, number of qubits, classical bits and params( if any ) """ - return "Instruction(name='{}', num_qubits={}, num_clbits={}, params={})".format( - self.name, self.num_qubits, self.num_clbits, self.params + return ( + f"Instruction(name='{self.name}', num_qubits={self.num_qubits}, " + f"num_clbits={self.num_clbits}, params={self.params})" ) def soft_compare(self, other: "Instruction") -> bool: @@ -456,7 +457,7 @@ def inverse(self, annotated: bool = False): return AnnotatedOperation(self, InverseModifier()) if self.definition is None: - raise CircuitError("inverse() not implemented for %s." % self.name) + raise CircuitError(f"inverse() not implemented for {self.name}.") from qiskit.circuit import Gate # pylint: disable=cyclic-import diff --git a/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py index a40767154bc9..bc80ef778616 100644 --- a/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py +++ b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py @@ -153,7 +153,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool: if raise_on_failure: raise CircuitError( "Not enough qubits in the circuit, need at least " - "{}.".format(self.num_state_qubits + 1) + f"{self.num_state_qubits + 1}." ) return valid diff --git a/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py b/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py index a27c57ef28fa..cc34d3631f59 100644 --- a/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py +++ b/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py @@ -122,7 +122,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool: if raise_on_failure: raise CircuitError( "Not enough qubits in the circuit, need at least " - "{}.".format(self.num_state_qubits + 1) + f"{self.num_state_qubits + 1}." ) return valid diff --git a/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py b/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py index 509433af5574..3d84e64ccb1b 100644 --- a/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py +++ b/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py @@ -202,7 +202,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool: if raise_on_failure: raise CircuitError( "Not enough qubits in the circuit, need at least " - "{}.".format(self.num_state_qubits + 1) + f"{self.num_state_qubits + 1}." ) if len(self.breakpoints) != len(self.slopes) or len(self.breakpoints) != len(self.offsets): diff --git a/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py b/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py index 7e79ed04da12..741b920e368d 100644 --- a/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py +++ b/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py @@ -237,7 +237,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool: if raise_on_failure: raise CircuitError( "Not enough qubits in the circuit, need at least " - "{}.".format(self.num_state_qubits + 1) + f"{self.num_state_qubits + 1}." ) if len(self.breakpoints) != len(self.coeffs) + 1: diff --git a/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py index 13fb82298819..4f04a04dd522 100644 --- a/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py +++ b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py @@ -248,7 +248,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool: if raise_on_failure: raise CircuitError( "Not enough qubits in the circuit, need at least " - "{}.".format(self.num_state_qubits + 1) + f"{self.num_state_qubits + 1}." ) return valid diff --git a/qiskit/circuit/library/data_preparation/state_preparation.py b/qiskit/circuit/library/data_preparation/state_preparation.py index 43e80ead8836..1d9ad7f7b082 100644 --- a/qiskit/circuit/library/data_preparation/state_preparation.py +++ b/qiskit/circuit/library/data_preparation/state_preparation.py @@ -154,8 +154,8 @@ def _define_from_int(self): # Raise if number of bits is greater than num_qubits if len(intstr) > self.num_qubits: raise QiskitError( - "StatePreparation integer has %s bits, but this exceeds the" - " number of qubits in the circuit, %s." % (len(intstr), self.num_qubits) + f"StatePreparation integer has {len(intstr)} bits, but this exceeds the" + f" number of qubits in the circuit, {self.num_qubits}." ) for qubit, bit in enumerate(intstr): @@ -212,9 +212,9 @@ def broadcast_arguments(self, qargs, cargs): if self.num_qubits != len(flat_qargs): raise QiskitError( - "StatePreparation parameter vector has %d elements, therefore expects %s " - "qubits. However, %s were provided." - % (2**self.num_qubits, self.num_qubits, len(flat_qargs)) + f"StatePreparation parameter vector has {2**self.num_qubits}" + f" elements, therefore expects {self.num_qubits} " + f"qubits. However, {len(flat_qargs)} were provided." ) yield flat_qargs, [] @@ -226,8 +226,8 @@ def validate_parameter(self, parameter): if parameter in ["0", "1", "+", "-", "l", "r"]: return parameter raise CircuitError( - "invalid param label {} for instruction {}. Label should be " - "0, 1, +, -, l, or r ".format(type(parameter), self.name) + f"invalid param label {type(parameter)} for instruction {self.name}. Label should be " + "0, 1, +, -, l, or r " ) # StatePreparation instruction parameter can be int, float, and complex. diff --git a/qiskit/circuit/library/graph_state.py b/qiskit/circuit/library/graph_state.py index ceefff7971db..89d1edb035ff 100644 --- a/qiskit/circuit/library/graph_state.py +++ b/qiskit/circuit/library/graph_state.py @@ -74,7 +74,7 @@ def __init__(self, adjacency_matrix: list | np.ndarray) -> None: raise CircuitError("The adjacency matrix must be symmetric.") num_qubits = len(adjacency_matrix) - circuit = QuantumCircuit(num_qubits, name="graph: %s" % (adjacency_matrix)) + circuit = QuantumCircuit(num_qubits, name=f"graph: {adjacency_matrix}") circuit.h(range(num_qubits)) for i in range(num_qubits): diff --git a/qiskit/circuit/library/hamiltonian_gate.py b/qiskit/circuit/library/hamiltonian_gate.py index 2997d01ed487..d920d7873876 100644 --- a/qiskit/circuit/library/hamiltonian_gate.py +++ b/qiskit/circuit/library/hamiltonian_gate.py @@ -103,8 +103,7 @@ def __array__(self, dtype=None, copy=None): time = float(self.params[1]) except TypeError as ex: raise TypeError( - "Unable to generate Unitary matrix for " - "unbound t parameter {}".format(self.params[1]) + f"Unable to generate Unitary matrix for unbound t parameter {self.params[1]}" ) from ex arr = scipy.linalg.expm(-1j * self.params[0] * time) dtype = complex if dtype is None else dtype diff --git a/qiskit/circuit/library/hidden_linear_function.py b/qiskit/circuit/library/hidden_linear_function.py index 1140f1866f08..b68fda7f8fc5 100644 --- a/qiskit/circuit/library/hidden_linear_function.py +++ b/qiskit/circuit/library/hidden_linear_function.py @@ -82,7 +82,7 @@ def __init__(self, adjacency_matrix: Union[List[List[int]], np.ndarray]) -> None raise CircuitError("The adjacency matrix must be symmetric.") num_qubits = len(adjacency_matrix) - circuit = QuantumCircuit(num_qubits, name="hlf: %s" % adjacency_matrix) + circuit = QuantumCircuit(num_qubits, name=f"hlf: {adjacency_matrix}") circuit.h(range(num_qubits)) for i in range(num_qubits): diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index 430edfd94f39..25f6c27bbe1b 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -441,9 +441,8 @@ def ordered_parameters(self, parameters: ParameterVector | list[Parameter]) -> N ): raise ValueError( "The length of ordered parameters must be equal to the number of " - "settable parameters in the circuit ({}), but is {}".format( - self.num_parameters_settable, len(parameters) - ) + f"settable parameters in the circuit ({self.num_parameters_settable})," + f" but is {len(parameters)}" ) self._ordered_parameters = parameters self._invalidate() diff --git a/qiskit/circuit/library/n_local/qaoa_ansatz.py b/qiskit/circuit/library/n_local/qaoa_ansatz.py index d62e12c4d941..43869c0c54c9 100644 --- a/qiskit/circuit/library/n_local/qaoa_ansatz.py +++ b/qiskit/circuit/library/n_local/qaoa_ansatz.py @@ -97,20 +97,18 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool: valid = False if raise_on_failure: raise ValueError( - "The number of qubits of the initial state {} does not match " - "the number of qubits of the cost operator {}".format( - self.initial_state.num_qubits, self.num_qubits - ) + f"The number of qubits of the initial state {self.initial_state.num_qubits}" + " does not match " + f"the number of qubits of the cost operator {self.num_qubits}" ) if self.mixer_operator is not None and self.mixer_operator.num_qubits != self.num_qubits: valid = False if raise_on_failure: raise ValueError( - "The number of qubits of the mixer {} does not match " - "the number of qubits of the cost operator {}".format( - self.mixer_operator.num_qubits, self.num_qubits - ) + f"The number of qubits of the mixer {self.mixer_operator.num_qubits}" + f" does not match " + f"the number of qubits of the cost operator {self.num_qubits}" ) return valid diff --git a/qiskit/circuit/library/overlap.py b/qiskit/circuit/library/overlap.py index 2db6a80eedcc..f6ae5fd6ebdd 100644 --- a/qiskit/circuit/library/overlap.py +++ b/qiskit/circuit/library/overlap.py @@ -112,8 +112,6 @@ def _check_unitary(circuit): for instruction in circuit.data: if not isinstance(instruction.operation, (Gate, Barrier)): raise CircuitError( - ( - "One or more instructions cannot be converted to" - ' a gate. "{}" is not a gate instruction' - ).format(instruction.operation.name) + "One or more instructions cannot be converted to" + f' a gate. "{instruction.operation.name}" is not a gate instruction' ) diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index 62c1e33b9628..0eef2518a85a 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -344,7 +344,7 @@ def _generate_gray_code(num_bits): result = [0] for i in range(num_bits): result += [x + 2**i for x in reversed(result)] - return [format(x, "0%sb" % num_bits) for x in result] + return [format(x, f"0{num_bits}b") for x in result] def _gray_code_chain(q, num_ctrl_qubits, gate): diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py index abe4e61adf63..825679f7d4f5 100644 --- a/qiskit/circuit/parameter.py +++ b/qiskit/circuit/parameter.py @@ -109,8 +109,8 @@ def subs(self, parameter_map: dict, allow_unknown_parameters: bool = False): if allow_unknown_parameters: return self raise CircuitError( - "Cannot bind Parameters ({}) not present in " - "expression.".format([str(p) for p in parameter_map]) + f"Cannot bind Parameters ({[str(p) for p in parameter_map]}) not present in " + "expression." ) @property diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index f881e09333d5..7f839677b904 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -140,7 +140,7 @@ def bind( raise ZeroDivisionError( "Binding provided for expression " "results in division by zero " - "(Expression: {}, Bindings: {}).".format(self, parameter_values) + f"(Expression: {self}, Bindings: {parameter_values})." ) return ParameterExpression(free_parameter_symbols, bound_symbol_expr) @@ -199,8 +199,8 @@ def _raise_if_passed_unknown_parameters(self, parameters): unknown_parameters = parameters - self.parameters if unknown_parameters: raise CircuitError( - "Cannot bind Parameters ({}) not present in " - "expression.".format([str(p) for p in unknown_parameters]) + f"Cannot bind Parameters ({[str(p) for p in unknown_parameters]}) not present in " + "expression." ) def _raise_if_passed_nan(self, parameter_values): @@ -404,8 +404,8 @@ def __complex__(self): except (TypeError, RuntimeError) as exc: if self.parameters: raise TypeError( - "ParameterExpression with unbound parameters ({}) " - "cannot be cast to a complex.".format(self.parameters) + f"ParameterExpression with unbound parameters ({self.parameters}) " + "cannot be cast to a complex." ) from None raise TypeError("could not cast expression to complex") from exc @@ -416,8 +416,8 @@ def __float__(self): except (TypeError, RuntimeError) as exc: if self.parameters: raise TypeError( - "ParameterExpression with unbound parameters ({}) " - "cannot be cast to a float.".format(self.parameters) + f"ParameterExpression with unbound parameters ({self.parameters}) " + "cannot be cast to a float." ) from None # In symengine, if an expression was complex at any time, its type is likely to have # stayed "complex" even when the imaginary part symbolically (i.e. exactly) @@ -436,8 +436,8 @@ def __int__(self): except RuntimeError as exc: if self.parameters: raise TypeError( - "ParameterExpression with unbound parameters ({}) " - "cannot be cast to an int.".format(self.parameters) + f"ParameterExpression with unbound parameters ({self.parameters}) " + "cannot be cast to an int." ) from None raise TypeError("could not cast expression to int") from exc diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index e3fbf40da683..010a91e3639e 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1067,8 +1067,9 @@ def __init__( if not valid_reg_size: raise CircuitError( - "Circuit args must be Registers or integers. (%s '%s' was " - "provided)" % ([type(reg).__name__ for reg in regs], regs) + "Circuit args must be Registers or integers. (" + f"{[type(reg).__name__ for reg in regs]} '{regs}' was " + "provided)" ) regs = tuple(int(reg) for reg in regs) # cast to int @@ -1659,7 +1660,7 @@ def power( raise CircuitError( "Cannot raise a parameterized circuit to a non-positive power " "or matrix-power, please bind the free parameters: " - "{}".format(self.parameters) + f"{self.parameters}" ) try: @@ -2957,14 +2958,14 @@ def add_register(self, *regs: Register | int | Sequence[Bit]) -> None: raise CircuitError( "QuantumCircuit parameters can be Registers or Integers." " If Integers, up to 2 arguments. QuantumCircuit was called" - " with %s." % (regs,) + f" with {(regs,)}." ) for register in regs: if isinstance(register, Register) and any( register.name == reg.name for reg in self.qregs + self.cregs ): - raise CircuitError('register name "%s" already exists' % register.name) + raise CircuitError(f'register name "{register.name}" already exists') if isinstance(register, AncillaRegister): for bit in register: @@ -3020,7 +3021,7 @@ def add_bits(self, bits: Iterable[Bit]) -> None: else: raise CircuitError( "Expected an instance of Qubit, Clbit, or " - "AncillaQubit, but was passed {}".format(bit) + f"AncillaQubit, but was passed {bit}" ) def find_bit(self, bit: Bit) -> BitLocations: diff --git a/qiskit/circuit/quantumregister.py b/qiskit/circuit/quantumregister.py index 2ae815b1d172..97d1392698e3 100644 --- a/qiskit/circuit/quantumregister.py +++ b/qiskit/circuit/quantumregister.py @@ -43,7 +43,7 @@ def __init__(self, register=None, index=None): super().__init__(register, index) else: raise CircuitError( - "Qubit needs a QuantumRegister and %s was provided" % type(register).__name__ + f"Qubit needs a QuantumRegister and {type(register).__name__} was provided" ) diff --git a/qiskit/circuit/register.py b/qiskit/circuit/register.py index e927d10e7360..39345705aaef 100644 --- a/qiskit/circuit/register.py +++ b/qiskit/circuit/register.py @@ -67,7 +67,7 @@ def __init__(self, size: int | None = None, name: str | None = None, bits=None): if (size, bits) == (None, None) or (size is not None and bits is not None): raise CircuitError( "Exactly one of the size or bits arguments can be " - "provided. Provided size=%s bits=%s." % (size, bits) + f"provided. Provided size={size} bits={bits}." ) # validate (or cast) size @@ -81,20 +81,18 @@ def __init__(self, size: int | None = None, name: str | None = None, bits=None): if not valid_size: raise CircuitError( - "Register size must be an integer. (%s '%s' was provided)" - % (type(size).__name__, size) + f"Register size must be an integer. ({type(size).__name__} '{size}' was provided)" ) size = int(size) # cast to int if size < 0: raise CircuitError( - "Register size must be non-negative (%s '%s' was provided)" - % (type(size).__name__, size) + f"Register size must be non-negative ({type(size).__name__} '{size}' was provided)" ) # validate (or cast) name if name is None: - name = "%s%i" % (self.prefix, next(self.instances_counter)) + name = f"{self.prefix}{next(self.instances_counter)}" else: try: name = str(name) @@ -108,7 +106,7 @@ def __init__(self, size: int | None = None, name: str | None = None, bits=None): self._size = size self._hash = hash((type(self), self._name, self._size)) - self._repr = "%s(%d, '%s')" % (self.__class__.__qualname__, self.size, self.name) + self._repr = f"{self.__class__.__qualname__}({self.size}, '{self.name}')" if bits is not None: # check duplicated bits if self._size != len(set(bits)): diff --git a/qiskit/circuit/tools/pi_check.py b/qiskit/circuit/tools/pi_check.py index d3614b747824..334b9683ae9b 100644 --- a/qiskit/circuit/tools/pi_check.py +++ b/qiskit/circuit/tools/pi_check.py @@ -104,9 +104,9 @@ def normalize(single_inpt): if power[0].shape[0]: if output == "qasm": if ndigits is None: - str_out = "{}".format(single_inpt) + str_out = str(single_inpt) else: - str_out = "{:.{}g}".format(single_inpt, ndigits) + str_out = f"{single_inpt:.{ndigits}g}" elif output == "latex": str_out = f"{neg_str}{pi}^{power[0][0] + 2}" elif output == "mpl": @@ -119,9 +119,9 @@ def normalize(single_inpt): # multiple or power of pi, since no fractions will exceed MAX_FRAC * pi if abs(single_inpt) >= (MAX_FRAC * np.pi): if ndigits is None: - str_out = "{}".format(single_inpt) + str_out = str(single_inpt) else: - str_out = "{:.{}g}".format(single_inpt, ndigits) + str_out = f"{single_inpt:.{ndigits}g}" return str_out # Fourth check is for fractions for 1*pi in the numer and any diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index a6c5212e2330..522e1c503ddf 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -34,7 +34,7 @@ def _log_assembly_time(start_time, end_time): - log_msg = "Total Assembly Time - %.5f (ms)" % ((end_time - start_time) * 1000) + log_msg = f"Total Assembly Time - {((end_time - start_time) * 1000):.5f} (ms)" logger.info(log_msg) @@ -311,8 +311,8 @@ def _parse_common_args( raise QiskitError("Argument 'shots' should be of type 'int'") elif max_shots and max_shots < shots: raise QiskitError( - "Number of shots specified: %s exceeds max_shots property of the " - "backend: %s." % (shots, max_shots) + f"Number of shots specified: {max_shots} exceeds max_shots property of the " + f"backend: {max_shots}." ) dynamic_reprate_enabled = getattr(backend_config, "dynamic_reprate_enabled", False) @@ -397,9 +397,8 @@ def _check_lo_freqs( raise QiskitError(f"Each element of {lo_type} LO range must be a 2d list.") if freq < freq_range[0] or freq > freq_range[1]: raise QiskitError( - "Qubit {} {} LO frequency is {}. The range is [{}, {}].".format( - i, lo_type, freq, freq_range[0], freq_range[1] - ) + f"Qubit {i} {lo_type} LO frequency is {freq}. " + f"The range is [{freq_range[0]}, {freq_range[1]}]." ) @@ -429,9 +428,8 @@ def _parse_pulse_args( if meas_level not in getattr(backend_config, "meas_levels", [MeasLevel.CLASSIFIED]): raise QiskitError( - ("meas_level = {} not supported for backend {}, only {} is supported").format( - meas_level, backend_config.backend_name, backend_config.meas_levels - ) + f"meas_level = {meas_level} not supported for backend " + f"{backend_config.backend_name}, only {backend_config.meas_levels} is supported" ) meas_map = meas_map or getattr(backend_config, "meas_map", None) @@ -522,14 +520,12 @@ def _parse_rep_delay( if rep_delay_range is not None and isinstance(rep_delay_range, list): if len(rep_delay_range) != 2: raise QiskitError( - "Backend rep_delay_range {} must be a list with two entries.".format( - rep_delay_range - ) + f"Backend rep_delay_range {rep_delay_range} must be a list with two entries." ) if not rep_delay_range[0] <= rep_delay <= rep_delay_range[1]: raise QiskitError( - "Supplied rep delay {} not in the supported " - "backend range {}".format(rep_delay, rep_delay_range) + f"Supplied rep delay {rep_delay} not in the supported " + f"backend range {rep_delay_range}" ) rep_delay = rep_delay * 1e6 # convert sec to μs diff --git a/qiskit/compiler/scheduler.py b/qiskit/compiler/scheduler.py index 0a30b07a49b1..f141902b7062 100644 --- a/qiskit/compiler/scheduler.py +++ b/qiskit/compiler/scheduler.py @@ -31,7 +31,7 @@ def _log_schedule_time(start_time, end_time): - log_msg = "Total Scheduling Time - %.5f (ms)" % ((end_time - start_time) * 1000) + log_msg = f"Total Scheduling Time - {((end_time - start_time) * 1000):.5f} (ms)" logger.info(log_msg) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 9dd316839a0f..183e260739ba 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -405,7 +405,7 @@ def _check_circuits_coupling_map(circuits, cmap, backend): def _log_transpile_time(start_time, end_time): - log_msg = "Total Transpile Time - %.5f (ms)" % ((end_time - start_time) * 1000) + log_msg = f"Total Transpile Time - {((end_time - start_time) * 1000):.5f} (ms)" logger.info(log_msg) @@ -476,7 +476,7 @@ def _parse_output_name(output_name, circuits): else: raise TranspilerError( "The parameter output_name should be a string or a" - "list of strings: %s was used." % type(output_name) + f"list of strings: {type(output_name)} was used." ) else: return [circuit.name for circuit in circuits] diff --git a/qiskit/converters/circuit_to_gate.py b/qiskit/converters/circuit_to_gate.py index 39eed1053eb1..c9f9ac6e1aff 100644 --- a/qiskit/converters/circuit_to_gate.py +++ b/qiskit/converters/circuit_to_gate.py @@ -64,10 +64,8 @@ def circuit_to_gate(circuit, parameter_map=None, equivalence_library=None, label for instruction in circuit.data: if not _check_is_gate(instruction.operation): raise QiskitError( - ( - "One or more instructions cannot be converted to" - ' a gate. "{}" is not a gate instruction' - ).format(instruction.operation.name) + "One or more instructions cannot be converted to" + f' a gate. "{instruction.operation.name}" is not a gate instruction' ) if parameter_map is None: @@ -77,10 +75,8 @@ def circuit_to_gate(circuit, parameter_map=None, equivalence_library=None, label if parameter_dict.keys() != circuit.parameters: raise QiskitError( - ( - "parameter_map should map all circuit parameters. " - "Circuit parameters: {}, parameter_map: {}" - ).format(circuit.parameters, parameter_dict) + "parameter_map should map all circuit parameters. " + f"Circuit parameters: {circuit.parameters}, parameter_map: {parameter_dict}" ) gate = Gate( diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py index 1a5907c3ec84..571e330eb0db 100644 --- a/qiskit/converters/circuit_to_instruction.py +++ b/qiskit/converters/circuit_to_instruction.py @@ -89,10 +89,8 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None if parameter_dict.keys() != circuit.parameters: raise QiskitError( - ( - "parameter_map should map all circuit parameters. " - "Circuit parameters: {}, parameter_map: {}" - ).format(circuit.parameters, parameter_dict) + "parameter_map should map all circuit parameters. " + f"Circuit parameters: {circuit.parameters}, parameter_map: {parameter_dict}" ) out_instruction = Instruction( diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 96562a37b151..686951f26fc2 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -271,7 +271,7 @@ def add_qubits(self, qubits): duplicate_qubits = set(self.qubits).intersection(qubits) if duplicate_qubits: - raise DAGCircuitError("duplicate qubits %s" % duplicate_qubits) + raise DAGCircuitError(f"duplicate qubits {duplicate_qubits}") for qubit in qubits: self.qubits.append(qubit) @@ -285,7 +285,7 @@ def add_clbits(self, clbits): duplicate_clbits = set(self.clbits).intersection(clbits) if duplicate_clbits: - raise DAGCircuitError("duplicate clbits %s" % duplicate_clbits) + raise DAGCircuitError(f"duplicate clbits {duplicate_clbits}") for clbit in clbits: self.clbits.append(clbit) @@ -297,7 +297,7 @@ def add_qreg(self, qreg): if not isinstance(qreg, QuantumRegister): raise DAGCircuitError("not a QuantumRegister instance.") if qreg.name in self.qregs: - raise DAGCircuitError("duplicate register %s" % qreg.name) + raise DAGCircuitError(f"duplicate register {qreg.name}") self.qregs[qreg.name] = qreg existing_qubits = set(self.qubits) for j in range(qreg.size): @@ -315,7 +315,7 @@ def add_creg(self, creg): if not isinstance(creg, ClassicalRegister): raise DAGCircuitError("not a ClassicalRegister instance.") if creg.name in self.cregs: - raise DAGCircuitError("duplicate register %s" % creg.name) + raise DAGCircuitError(f"duplicate register {creg.name}") self.cregs[creg.name] = creg existing_clbits = set(self.clbits) for j in range(creg.size): @@ -451,17 +451,17 @@ def remove_clbits(self, *clbits): """ if any(not isinstance(clbit, Clbit) for clbit in clbits): raise DAGCircuitError( - "clbits not of type Clbit: %s" % [b for b in clbits if not isinstance(b, Clbit)] + f"clbits not of type Clbit: {[b for b in clbits if not isinstance(b, Clbit)]}" ) clbits = set(clbits) unknown_clbits = clbits.difference(self.clbits) if unknown_clbits: - raise DAGCircuitError("clbits not in circuit: %s" % unknown_clbits) + raise DAGCircuitError(f"clbits not in circuit: {unknown_clbits}") busy_clbits = {bit for bit in clbits if not self._is_wire_idle(bit)} if busy_clbits: - raise DAGCircuitError("clbits not idle: %s" % busy_clbits) + raise DAGCircuitError(f"clbits not idle: {busy_clbits}") # remove any references to bits cregs_to_remove = {creg for creg in self.cregs.values() if not clbits.isdisjoint(creg)} @@ -487,13 +487,13 @@ def remove_cregs(self, *cregs): """ if any(not isinstance(creg, ClassicalRegister) for creg in cregs): raise DAGCircuitError( - "cregs not of type ClassicalRegister: %s" - % [r for r in cregs if not isinstance(r, ClassicalRegister)] + "cregs not of type ClassicalRegister: " + f"{[r for r in cregs if not isinstance(r, ClassicalRegister)]}" ) unknown_cregs = set(cregs).difference(self.cregs.values()) if unknown_cregs: - raise DAGCircuitError("cregs not in circuit: %s" % unknown_cregs) + raise DAGCircuitError(f"cregs not in circuit: {unknown_cregs}") for creg in cregs: del self.cregs[creg.name] @@ -517,17 +517,17 @@ def remove_qubits(self, *qubits): """ if any(not isinstance(qubit, Qubit) for qubit in qubits): raise DAGCircuitError( - "qubits not of type Qubit: %s" % [b for b in qubits if not isinstance(b, Qubit)] + f"qubits not of type Qubit: {[b for b in qubits if not isinstance(b, Qubit)]}" ) qubits = set(qubits) unknown_qubits = qubits.difference(self.qubits) if unknown_qubits: - raise DAGCircuitError("qubits not in circuit: %s" % unknown_qubits) + raise DAGCircuitError(f"qubits not in circuit: {unknown_qubits}") busy_qubits = {bit for bit in qubits if not self._is_wire_idle(bit)} if busy_qubits: - raise DAGCircuitError("qubits not idle: %s" % busy_qubits) + raise DAGCircuitError(f"qubits not idle: {busy_qubits}") # remove any references to bits qregs_to_remove = {qreg for qreg in self.qregs.values() if not qubits.isdisjoint(qreg)} @@ -553,13 +553,13 @@ def remove_qregs(self, *qregs): """ if any(not isinstance(qreg, QuantumRegister) for qreg in qregs): raise DAGCircuitError( - "qregs not of type QuantumRegister: %s" - % [r for r in qregs if not isinstance(r, QuantumRegister)] + f"qregs not of type QuantumRegister: " + f"{[r for r in qregs if not isinstance(r, QuantumRegister)]}" ) unknown_qregs = set(qregs).difference(self.qregs.values()) if unknown_qregs: - raise DAGCircuitError("qregs not in circuit: %s" % unknown_qregs) + raise DAGCircuitError(f"qregs not in circuit: {unknown_qregs}") for qreg in qregs: del self.qregs[qreg.name] @@ -581,13 +581,13 @@ def _is_wire_idle(self, wire): DAGCircuitError: the wire is not in the circuit. """ if wire not in self._wires: - raise DAGCircuitError("wire %s not in circuit" % wire) + raise DAGCircuitError(f"wire {wire} not in circuit") try: child = next(self.successors(self.input_map[wire])) except StopIteration as e: raise DAGCircuitError( - "Invalid dagcircuit input node %s has no output" % self.input_map[wire] + f"Invalid dagcircuit input node {self.input_map[wire]} has no output" ) from e return child is self.output_map[wire] @@ -950,12 +950,11 @@ def _reject_new_register(reg): # the mapped wire should already exist if m_wire not in dag.output_map: raise DAGCircuitError( - "wire %s[%d] not in self" % (m_wire.register.name, m_wire.index) + f"wire {m_wire.register.name}[{m_wire.index}] not in self" ) if nd.wire not in other._wires: raise DAGCircuitError( - "inconsistent wire type for %s[%d] in other" - % (nd.register.name, nd.wire.index) + f"inconsistent wire type for {nd.register.name}[{nd.wire.index}] in other" ) # If it's a Var wire, we already checked that it exists in the destination. elif isinstance(nd, DAGOutNode): @@ -974,7 +973,7 @@ def _reject_new_register(reg): op.target = variable_mapper.map_target(op.target) dag.apply_operation_back(op, m_qargs, m_cargs, check=False) else: - raise DAGCircuitError("bad node type %s" % type(nd)) + raise DAGCircuitError(f"bad node type {type(nd)}") if not inplace: return dag @@ -1632,10 +1631,10 @@ def substitute_node(self, node: DAGOpNode, op, inplace: bool = False, propagate_ if node.op.num_qubits != op.num_qubits or node.op.num_clbits != op.num_clbits: raise DAGCircuitError( - "Cannot replace node of width ({} qubits, {} clbits) with " - "operation of mismatched width ({} qubits, {} clbits).".format( - node.op.num_qubits, node.op.num_clbits, op.num_qubits, op.num_clbits - ) + f"Cannot replace node of width ({node.op.num_qubits} qubits, " + f"{node.op.num_clbits} clbits) with " + f"operation of mismatched width ({op.num_qubits} qubits, " + f"{op.num_clbits} clbits)." ) # This might include wires that are inherent to the node, like in its `condition` or @@ -1953,8 +1952,8 @@ def remove_op_node(self, node): """ if not isinstance(node, DAGOpNode): raise DAGCircuitError( - 'The method remove_op_node only works on DAGOpNodes. A "%s" ' - "node type was wrongly provided." % type(node) + f'The method remove_op_node only works on DAGOpNodes. A "{type(node)}" ' + "node type was wrongly provided." ) self._multi_graph.remove_node_retain_edges( @@ -2182,7 +2181,7 @@ def nodes_on_wire(self, wire, only_ops=False): current_node = self.input_map.get(wire, None) if not current_node: - raise DAGCircuitError("The given wire %s is not present in the circuit" % str(wire)) + raise DAGCircuitError(f"The given wire {str(wire)} is not present in the circuit") more_nodes = True while more_nodes: diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 4316c9471401..63b91f920631 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -187,7 +187,7 @@ def add_qubits(self, qubits): duplicate_qubits = set(self.qubits).intersection(qubits) if duplicate_qubits: - raise DAGDependencyError("duplicate qubits %s" % duplicate_qubits) + raise DAGDependencyError(f"duplicate qubits {duplicate_qubits}") self.qubits.extend(qubits) @@ -198,7 +198,7 @@ def add_clbits(self, clbits): duplicate_clbits = set(self.clbits).intersection(clbits) if duplicate_clbits: - raise DAGDependencyError("duplicate clbits %s" % duplicate_clbits) + raise DAGDependencyError(f"duplicate clbits {duplicate_clbits}") self.clbits.extend(clbits) @@ -207,7 +207,7 @@ def add_qreg(self, qreg): if not isinstance(qreg, QuantumRegister): raise DAGDependencyError("not a QuantumRegister instance.") if qreg.name in self.qregs: - raise DAGDependencyError("duplicate register %s" % qreg.name) + raise DAGDependencyError(f"duplicate register {qreg.name}") self.qregs[qreg.name] = qreg existing_qubits = set(self.qubits) for j in range(qreg.size): @@ -219,7 +219,7 @@ def add_creg(self, creg): if not isinstance(creg, ClassicalRegister): raise DAGDependencyError("not a ClassicalRegister instance.") if creg.name in self.cregs: - raise DAGDependencyError("duplicate register %s" % creg.name) + raise DAGDependencyError(f"duplicate register {creg.name}") self.cregs[creg.name] = creg existing_clbits = set(self.clbits) for j in range(creg.size): diff --git a/qiskit/dagcircuit/dagdependency_v2.py b/qiskit/dagcircuit/dagdependency_v2.py index cb5d447162cb..e50c47b24f90 100644 --- a/qiskit/dagcircuit/dagdependency_v2.py +++ b/qiskit/dagcircuit/dagdependency_v2.py @@ -247,7 +247,7 @@ def add_qubits(self, qubits): duplicate_qubits = set(self.qubits).intersection(qubits) if duplicate_qubits: - raise DAGDependencyError("duplicate qubits %s" % duplicate_qubits) + raise DAGDependencyError(f"duplicate qubits {duplicate_qubits}") for qubit in qubits: self.qubits.append(qubit) @@ -260,7 +260,7 @@ def add_clbits(self, clbits): duplicate_clbits = set(self.clbits).intersection(clbits) if duplicate_clbits: - raise DAGDependencyError("duplicate clbits %s" % duplicate_clbits) + raise DAGDependencyError(f"duplicate clbits {duplicate_clbits}") for clbit in clbits: self.clbits.append(clbit) @@ -271,7 +271,7 @@ def add_qreg(self, qreg): if not isinstance(qreg, QuantumRegister): raise DAGDependencyError("not a QuantumRegister instance.") if qreg.name in self.qregs: - raise DAGDependencyError("duplicate register %s" % qreg.name) + raise DAGDependencyError(f"duplicate register {qreg.name}") self.qregs[qreg.name] = qreg existing_qubits = set(self.qubits) for j in range(qreg.size): @@ -288,7 +288,7 @@ def add_creg(self, creg): if not isinstance(creg, ClassicalRegister): raise DAGDependencyError("not a ClassicalRegister instance.") if creg.name in self.cregs: - raise DAGDependencyError("duplicate register %s" % creg.name) + raise DAGDependencyError(f"duplicate register {creg.name}") self.cregs[creg.name] = creg existing_clbits = set(self.clbits) for j in range(creg.size): diff --git a/qiskit/dagcircuit/dagdepnode.py b/qiskit/dagcircuit/dagdepnode.py index fe63f57d3d4f..cc00db9725c0 100644 --- a/qiskit/dagcircuit/dagdepnode.py +++ b/qiskit/dagcircuit/dagdepnode.py @@ -83,7 +83,7 @@ def __init__( def op(self): """Returns the Instruction object corresponding to the op for the node, else None""" if not self.type or self.type != "op": - raise QiskitError("The node %s is not an op node" % (str(self))) + raise QiskitError(f"The node {str(self)} is not an op node") return self._op @op.setter diff --git a/qiskit/passmanager/flow_controllers.py b/qiskit/passmanager/flow_controllers.py index c7d952d048dd..dcfcba704587 100644 --- a/qiskit/passmanager/flow_controllers.py +++ b/qiskit/passmanager/flow_controllers.py @@ -84,7 +84,7 @@ def iter_tasks(self, state: PassManagerState) -> Generator[Task, PassManagerStat return # Remove stored tasks from the completed task collection for next loop state.workflow_status.completed_passes.difference_update(self.tasks) - raise PassManagerError("Maximum iteration reached. max_iteration=%i" % max_iteration) + raise PassManagerError(f"Maximum iteration reached. max_iteration={max_iteration}") class ConditionalController(BaseController): diff --git a/qiskit/passmanager/passmanager.py b/qiskit/passmanager/passmanager.py index 8d3a4e9aa693..99527bf584ee 100644 --- a/qiskit/passmanager/passmanager.py +++ b/qiskit/passmanager/passmanager.py @@ -130,7 +130,7 @@ def __add__(self, other): return new_passmanager except PassManagerError as ex: raise TypeError( - "unsupported operand type + for %s and %s" % (self.__class__, other.__class__) + f"unsupported operand type + for {self.__class__} and {other.__class__}" ) from ex @abstractmethod diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index 2e551cc311e8..ed9fd3fdbb87 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -95,7 +95,7 @@ def __init__(self, configuration, provider=None, **fields): if fields: for field in fields: if field not in self._options.data: - raise AttributeError("Options field %s is not valid for this backend" % field) + raise AttributeError(f"Options field {field} is not valid for this backend") self._options.update_config(**fields) @classmethod @@ -129,7 +129,7 @@ def set_options(self, **fields): """ for field in fields: if not hasattr(self._options, field): - raise AttributeError("Options field %s is not valid for this backend" % field) + raise AttributeError(f"Options field {field} is not valid for this backend") self._options.update_options(**fields) def configuration(self): @@ -352,7 +352,7 @@ def __init__( if fields: for field in fields: if field not in self._options.data: - raise AttributeError("Options field %s is not valid for this backend" % field) + raise AttributeError(f"Options field {field} is not valid for this backend") self._options.update_config(**fields) self.name = name """Name of the backend.""" @@ -598,7 +598,7 @@ def set_options(self, **fields): """ for field in fields: if not hasattr(self._options, field): - raise AttributeError("Options field %s is not valid for this backend" % field) + raise AttributeError(f"Options field {field} is not valid for this backend") self._options.update_options(**fields) @property diff --git a/qiskit/providers/basic_provider/basic_provider_tools.py b/qiskit/providers/basic_provider/basic_provider_tools.py index b2670cc0977f..786815dda534 100644 --- a/qiskit/providers/basic_provider/basic_provider_tools.py +++ b/qiskit/providers/basic_provider/basic_provider_tools.py @@ -66,7 +66,7 @@ def single_gate_matrix(gate: str, params: list[float] | None = None) -> np.ndarr if gate in SINGLE_QUBIT_GATES: gc = SINGLE_QUBIT_GATES[gate] else: - raise QiskitError("Gate is not a valid basis gate for this simulator: %s" % gate) + raise QiskitError(f"Gate is not a valid basis gate for this simulator: {gate}") return gc(*params).to_matrix() diff --git a/qiskit/providers/basic_provider/basic_simulator.py b/qiskit/providers/basic_provider/basic_simulator.py index 978e1dad56fd..9971bf36725c 100644 --- a/qiskit/providers/basic_provider/basic_simulator.py +++ b/qiskit/providers/basic_provider/basic_simulator.py @@ -208,7 +208,7 @@ def _build_basic_target(self) -> Target: target.add_instruction(UnitaryGate, name="unitary") else: raise BasicProviderError( - "Gate is not a valid basis gate for this simulator: %s" % name + f"Gate is not a valid basis gate for this simulator: {name}" ) return target @@ -531,7 +531,7 @@ def run( for key, value in backend_options.items(): if not hasattr(self.options, key): warnings.warn( - "Option %s is not used by this backend" % key, UserWarning, stacklevel=2 + f"Option {key} is not used by this backend", UserWarning, stacklevel=2 ) else: out_options[key] = value diff --git a/qiskit/providers/fake_provider/fake_backend.py b/qiskit/providers/fake_provider/fake_backend.py index d84aba46371d..4a638f315574 100644 --- a/qiskit/providers/fake_provider/fake_backend.py +++ b/qiskit/providers/fake_provider/fake_backend.py @@ -143,8 +143,8 @@ def run(self, run_input, **kwargs): pulse_job = False if pulse_job is None: raise QiskitError( - "Invalid input object %s, must be either a " - "QuantumCircuit, Schedule, or a list of either" % circuits + f"Invalid input object {circuits}, must be either a " + "QuantumCircuit, Schedule, or a list of either" ) if pulse_job: raise QiskitError("Pulse simulation is currently not supported for fake backends.") diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index e806c75ea3a5..1ac0484d775d 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -496,8 +496,8 @@ def run(self, run_input, **options): pulse_job = False if pulse_job is None: # submitted job is invalid raise QiskitError( - "Invalid input object %s, must be either a " - "QuantumCircuit, Schedule, or a list of either" % circuits + f"Invalid input object {circuits}, must be either a " + "QuantumCircuit, Schedule, or a list of either" ) if pulse_job: # pulse job raise QiskitError("Pulse simulation is currently not supported for V2 backends.") diff --git a/qiskit/providers/models/backendproperties.py b/qiskit/providers/models/backendproperties.py index 3b5b9c5e010b..332aac7c5edd 100644 --- a/qiskit/providers/models/backendproperties.py +++ b/qiskit/providers/models/backendproperties.py @@ -404,9 +404,9 @@ def qubit_property( if name is not None: result = result[name] except KeyError as ex: + formatted_name = "y '" + name + "'" if name else "ies" raise BackendPropertyError( - "Couldn't find the propert{name} for qubit " - "{qubit}.".format(name="y '" + name + "'" if name else "ies", qubit=qubit) + f"Couldn't find the propert{formatted_name} for qubit {qubit}." ) from ex return result diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 13becb1c956d..7c1864bad9ee 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -296,9 +296,4 @@ def __str__(self): meas_freqs = [freq / 1e9 for freq in self.meas_freq_est] qfreq = f"Qubit Frequencies [GHz]\n{qubit_freqs}" mfreq = f"Measurement Frequencies [GHz]\n{meas_freqs} " - return "<{name}({insts}{qfreq}\n{mfreq})>".format( - name=self.__class__.__name__, - insts=str(self.instruction_schedule_map), - qfreq=qfreq, - mfreq=mfreq, - ) + return f"<{self.__class__.__name__}({str(self.instruction_schedule_map)}{qfreq}\n{mfreq})>" diff --git a/qiskit/providers/options.py b/qiskit/providers/options.py index 8b2bffc52d84..fe4e7303a674 100644 --- a/qiskit/providers/options.py +++ b/qiskit/providers/options.py @@ -170,7 +170,7 @@ def __init__(self, **kwargs): def __repr__(self): items = (f"{k}={v!r}" for k, v in self._fields.items()) - return "{}({})".format(type(self).__name__, ", ".join(items)) + return f"{type(self).__name__}({', '.join(items)})" def __eq__(self, other): if isinstance(self, Options) and isinstance(other, Options): @@ -211,7 +211,7 @@ def set_validator(self, field, validator_value): """ if field not in self._fields: - raise KeyError("Field '%s' is not present in this options object" % field) + raise KeyError(f"Field '{field}' is not present in this options object") if isinstance(validator_value, tuple): if len(validator_value) != 2: raise ValueError( diff --git a/qiskit/pulse/configuration.py b/qiskit/pulse/configuration.py index 4668152973f2..1bfd1f13e2ee 100644 --- a/qiskit/pulse/configuration.py +++ b/qiskit/pulse/configuration.py @@ -55,11 +55,9 @@ def __init__(self, name: str | None = None, **params): self.params = params def __repr__(self): - return "{}({}{})".format( - self.__class__.__name__, - "'" + self.name + "', " or "", - ", ".join(f"{str(k)}={str(v)}" for k, v in self.params.items()), - ) + name_repr = "'" + self.name + "', " + params_repr = ", ".join(f"{str(k)}={str(v)}" for k, v in self.params.items()) + return f"{self.__class__.__name__}({name_repr}{params_repr})" def __eq__(self, other): if isinstance(other, Kernel): @@ -83,11 +81,9 @@ def __init__(self, name: str | None = None, **params): self.params = params def __repr__(self): - return "{}({}{})".format( - self.__class__.__name__, - "'" + self.name + "', " or "", - ", ".join(f"{str(k)}={str(v)}" for k, v in self.params.items()), - ) + name_repr = "'" + self.name + "', " or "" + params_repr = ", ".join(f"{str(k)}={str(v)}" for k, v in self.params.items()) + return f"{self.__class__.__name__}({name_repr}{params_repr})" def __eq__(self, other): if isinstance(other, Discriminator): @@ -184,7 +180,7 @@ def add_lo(self, channel: DriveChannel | MeasureChannel, freq: float): self.check_lo(channel, freq) self._m_lo_freq[channel] = freq else: - raise PulseError("Specified channel %s cannot be configured." % channel.name) + raise PulseError(f"Specified channel {channel.name} cannot be configured.") def add_lo_range( self, channel: DriveChannel | MeasureChannel, lo_range: LoRange | tuple[int, int] @@ -236,7 +232,7 @@ def channel_lo(self, channel: DriveChannel | MeasureChannel) -> float: if channel in self.meas_los: return self.meas_los[channel] - raise PulseError("Channel %s is not configured" % channel) + raise PulseError(f"Channel {channel} is not configured") @property def qubit_los(self) -> dict[DriveChannel, float]: diff --git a/qiskit/pulse/instruction_schedule_map.py b/qiskit/pulse/instruction_schedule_map.py index decda15c9947..afa71b6825a7 100644 --- a/qiskit/pulse/instruction_schedule_map.py +++ b/qiskit/pulse/instruction_schedule_map.py @@ -169,10 +169,8 @@ def assert_has( if not self.has(instruction, _to_tuple(qubits)): if instruction in self._map: raise PulseError( - "Operation '{inst}' exists, but is only defined for qubits " - "{qubits}.".format( - inst=instruction, qubits=self.qubits_with_instruction(instruction) - ) + f"Operation '{instruction}' exists, but is only defined for qubits " + f"{self.qubits_with_instruction(instruction)}." ) raise PulseError(f"Operation '{instruction}' is not defined for this system.") diff --git a/qiskit/pulse/instructions/acquire.py b/qiskit/pulse/instructions/acquire.py index 066163a79b0e..98fbf460c1b3 100644 --- a/qiskit/pulse/instructions/acquire.py +++ b/qiskit/pulse/instructions/acquire.py @@ -138,12 +138,11 @@ def is_parameterized(self) -> bool: return isinstance(self.duration, ParameterExpression) or super().is_parameterized() def __repr__(self) -> str: - return "{}({}{}{}{}{}{})".format( - self.__class__.__name__, - self.duration, - ", " + str(self.channel), - ", " + str(self.mem_slot) if self.mem_slot else "", - ", " + str(self.reg_slot) if self.reg_slot else "", - ", " + str(self.kernel) if self.kernel else "", - ", " + str(self.discriminator) if self.discriminator else "", + mem_slot_repr = str(self.mem_slot) if self.mem_slot else "" + reg_slot_repr = str(self.reg_slot) if self.reg_slot else "" + kernel_repr = str(self.kernel) if self.kernel else "" + discriminator_repr = str(self.discriminator) if self.discriminator else "" + return ( + f"{self.__class__.__name__}({self.duration}, {str(self.channel)}, " + f"{mem_slot_repr}, {reg_slot_repr}, {kernel_repr}, {discriminator_repr})" ) diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py index ece20545b504..61ebe67777f8 100644 --- a/qiskit/pulse/instructions/instruction.py +++ b/qiskit/pulse/instructions/instruction.py @@ -264,6 +264,5 @@ def __lshift__(self, time: int): def __repr__(self) -> str: operands = ", ".join(str(op) for op in self.operands) - return "{}({}{})".format( - self.__class__.__name__, operands, f", name='{self.name}'" if self.name else "" - ) + name_repr = f", name='{self.name}'" if self.name else "" + return f"{self.__class__.__name__}({operands}{name_repr})" diff --git a/qiskit/pulse/library/samplers/decorators.py b/qiskit/pulse/library/samplers/decorators.py index ac78fba85954..db6aabd7b1de 100644 --- a/qiskit/pulse/library/samplers/decorators.py +++ b/qiskit/pulse/library/samplers/decorators.py @@ -182,9 +182,9 @@ def _update_docstring(discretized_pulse: Callable, sampler_inst: Callable) -> Ca header, body = wrapped_docstring.split("\n", 1) body = textwrap.indent(body, " ") wrapped_docstring = header + body - updated_ds = """ - Discretized continuous pulse function: `{continuous_name}` using - sampler: `{sampler_name}`. + updated_ds = f""" + Discretized continuous pulse function: `{discretized_pulse.__name__}` using + sampler: `{sampler_inst.__name__}`. The first argument (time) of the continuous pulse function has been replaced with a discretized `duration` of type (int). @@ -198,12 +198,8 @@ def _update_docstring(discretized_pulse: Callable, sampler_inst: Callable) -> Ca Sampled continuous function: - {continuous_doc} - """.format( - continuous_name=discretized_pulse.__name__, - sampler_name=sampler_inst.__name__, - continuous_doc=wrapped_docstring, - ) + {wrapped_docstring} + """ discretized_pulse.__doc__ = updated_ds return discretized_pulse diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index b076bcf56cb0..33d428771b22 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -570,11 +570,8 @@ def __eq__(self, other: object) -> bool: def __repr__(self) -> str: param_repr = ", ".join(f"{p}={v}" for p, v in self.parameters.items()) - return "{}({}{})".format( - self._pulse_type, - param_repr, - f", name='{self.name}'" if self.name is not None else "", - ) + name_repr = f", name='{self.name}'" if self.name is not None else "" + return f"{self._pulse_type}({param_repr}{name_repr})" __hash__ = None diff --git a/qiskit/pulse/library/waveform.py b/qiskit/pulse/library/waveform.py index e9ad9bcbc717..ad852f226ac2 100644 --- a/qiskit/pulse/library/waveform.py +++ b/qiskit/pulse/library/waveform.py @@ -130,8 +130,5 @@ def __repr__(self) -> str: opt = np.get_printoptions() np.set_printoptions(threshold=50) np.set_printoptions(**opt) - return "{}({}{})".format( - self.__class__.__name__, - repr(self.samples), - f", name='{self.name}'" if self.name is not None else "", - ) + name_repr = f", name='{self.name}'" if self.name is not None else "" + return f"{self.__class__.__name__}({repr(self.samples)}{name_repr})" diff --git a/qiskit/pulse/macros.py b/qiskit/pulse/macros.py index 88414cfc7e9b..3a39932e5b10 100644 --- a/qiskit/pulse/macros.py +++ b/qiskit/pulse/macros.py @@ -135,10 +135,10 @@ def _measure_v1( default_sched = inst_map.get(measure_name, measure_group_qubits) except exceptions.PulseError as ex: raise exceptions.PulseError( - "We could not find a default measurement schedule called '{}'. " + f"We could not find a default measurement schedule called '{measure_name}'. " "Please provide another name using the 'measure_name' keyword " "argument. For assistance, the instructions which are defined are: " - "{}".format(measure_name, inst_map.instructions) + f"{inst_map.instructions}" ) from ex for time, inst in default_sched.instructions: if inst.channel.index not in qubits: @@ -203,10 +203,10 @@ def _measure_v2( schedule += _schedule_remapping_memory_slot(default_sched, qubit_mem_slots) except KeyError as ex: raise exceptions.PulseError( - "We could not find a default measurement schedule called '{}'. " + f"We could not find a default measurement schedule called '{measure_name}'. " "Please provide another name using the 'measure_name' keyword " "argument. For assistance, the instructions which are defined are: " - "{}".format(measure_name, target.instructions) + f"{target.instructions}" ) from ex return schedule diff --git a/qiskit/pulse/parser.py b/qiskit/pulse/parser.py index 8e31faebf77a..e9cd4917a7ce 100644 --- a/qiskit/pulse/parser.py +++ b/qiskit/pulse/parser.py @@ -124,13 +124,11 @@ def __call__(self, *args, **kwargs) -> complex | ast.Expression | PulseExpressio self._locals_dict[key] = val else: raise PulseError( - "%s got multiple values for argument '%s'" - % (self.__class__.__name__, key) + f"{self.__class__.__name__} got multiple values for argument '{key}'" ) else: raise PulseError( - "%s got an unexpected keyword argument '%s'" - % (self.__class__.__name__, key) + f"{self.__class__.__name__} got an unexpected keyword argument '{key}'" ) expr = self.visit(self._tree) @@ -139,7 +137,7 @@ def __call__(self, *args, **kwargs) -> complex | ast.Expression | PulseExpressio if self._partial_binding: return PulseExpression(expr, self._partial_binding) else: - raise PulseError("Parameters %s are not all bound." % self.params) + raise PulseError(f"Parameters {self.params} are not all bound.") return expr.body.value @staticmethod @@ -160,7 +158,7 @@ def _match_ops(opr: ast.AST, opr_dict: dict, *args) -> complex: for op_type, op_func in opr_dict.items(): if isinstance(opr, op_type): return op_func(*args) - raise PulseError("Operator %s is not supported." % opr.__class__.__name__) + raise PulseError(f"Operator {opr.__class__.__name__} is not supported.") def visit_Expression(self, node: ast.Expression) -> ast.Expression: """Evaluate children nodes of expression. @@ -273,7 +271,7 @@ def visit_Call(self, node: ast.Call) -> ast.Call | ast.Constant: node.args = [self.visit(arg) for arg in node.args] if all(isinstance(arg, ast.Constant) for arg in node.args): if node.func.id not in self._math_ops: - raise PulseError("Function %s is not supported." % node.func.id) + raise PulseError(f"Function {node.func.id} is not supported.") _args = [arg.value for arg in node.args] _val = self._math_ops[node.func.id](*_args) if not _val.imag: @@ -283,7 +281,7 @@ def visit_Call(self, node: ast.Call) -> ast.Call | ast.Constant: return node def generic_visit(self, node): - raise PulseError("Unsupported node: %s" % node.__class__.__name__) + raise PulseError(f"Unsupported node: {node.__class__.__name__}") def parse_string_expr(source: str, partial_binding: bool = False) -> PulseExpression: diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py index 5241da0c31d1..7ccd5053e6e0 100644 --- a/qiskit/pulse/schedule.py +++ b/qiskit/pulse/schedule.py @@ -553,17 +553,10 @@ def _add_timeslots(self, time: int, schedule: "ScheduleComponent") -> None: self._timeslots[channel].insert(index, interval) except PulseError as ex: raise PulseError( - "Schedule(name='{new}') cannot be inserted into Schedule(name='{old}') at " - "time {time} because its instruction on channel {ch} scheduled from time " - "{t0} to {tf} overlaps with an existing instruction." - "".format( - new=schedule.name or "", - old=self.name or "", - time=time, - ch=channel, - t0=interval[0], - tf=interval[1], - ) + f"Schedule(name='{schedule.name or ''}') cannot be inserted into " + f"Schedule(name='{self.name or ''}') at " + f"time {time} because its instruction on channel {channel} scheduled from time " + f"{interval[0]} to {interval[1]} overlaps with an existing instruction." ) from ex _check_nonnegative_timeslot(self._timeslots) @@ -598,10 +591,8 @@ def _remove_timeslots(self, time: int, schedule: "ScheduleComponent"): continue raise PulseError( - "Cannot find interval ({t0}, {tf}) to remove from " - "channel {ch} in Schedule(name='{name}').".format( - ch=channel, t0=interval[0], tf=interval[1], name=schedule.name - ) + f"Cannot find interval ({interval[0]}, {interval[1]}) to remove from " + f"channel {channel} in Schedule(name='{schedule.name}')." ) if not channel_timeslots: @@ -1615,8 +1606,9 @@ def __repr__(self) -> str: blocks = ", ".join([repr(instr) for instr in self.blocks[:50]]) if len(self.blocks) > 25: blocks += ", ..." - return '{}({}, name="{}", transform={})'.format( - self.__class__.__name__, blocks, name, repr(self.alignment_context) + return ( + f'{self.__class__.__name__}({blocks}, name="{name}",' + f" transform={repr(self.alignment_context)})" ) def __add__(self, other: "BlockComponent") -> "ScheduleBlock": diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index 569219777f20..5e383972c255 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -398,9 +398,7 @@ def align(self, schedule: Schedule) -> Schedule: _t_center = self.duration * self.func(ind + 1) _t0 = int(_t_center - 0.5 * child.duration) if _t0 < 0 or _t0 > self.duration: - raise PulseError( - "Invalid schedule position t=%d is specified at index=%d" % (_t0, ind) - ) + raise PulseError(f"Invalid schedule position t={_t0} is specified at index={ind}") aligned.insert(_t0, child, inplace=True) return aligned diff --git a/qiskit/pulse/utils.py b/qiskit/pulse/utils.py index ae87fbafadde..5f345917761e 100644 --- a/qiskit/pulse/utils.py +++ b/qiskit/pulse/utils.py @@ -108,10 +108,9 @@ def instruction_duration_validation(duration: int): """ if isinstance(duration, ParameterExpression): raise UnassignedDurationError( - "Instruction duration {} is not assigned. " + f"Instruction duration {repr(duration)} is not assigned. " "Please bind all durations to an integer value before playing in the Schedule, " "or use ScheduleBlock to align instructions with unassigned duration." - "".format(repr(duration)) ) if not isinstance(duration, (int, np.integer)) or duration < 0: diff --git a/qiskit/qasm2/export.py b/qiskit/qasm2/export.py index 9247c9233e09..46471fa087b2 100644 --- a/qiskit/qasm2/export.py +++ b/qiskit/qasm2/export.py @@ -157,7 +157,7 @@ def dumps(circuit: QuantumCircuit, /) -> str: _make_unique(_escape_name(reg.name, "reg_"), register_escaped_names) ] = reg bit_labels: dict[Qubit | Clbit, str] = { - bit: "%s[%d]" % (name, idx) + bit: f"{name}[{idx}]" for name, register in register_escaped_names.items() for (idx, bit) in enumerate(register) } @@ -244,18 +244,14 @@ def _instruction_call_site(operation): else: qasm2_call = operation.name if operation.params: - qasm2_call = "{}({})".format( - qasm2_call, - ",".join([pi_check(i, output="qasm", eps=1e-12) for i in operation.params]), - ) + params = ",".join([pi_check(i, output="qasm", eps=1e-12) for i in operation.params]) + qasm2_call = f"{qasm2_call}({params})" if operation.condition is not None: if not isinstance(operation.condition[0], ClassicalRegister): raise QASM2ExportError( "OpenQASM 2 can only condition on registers, but got '{operation.condition[0]}'" ) - qasm2_call = ( - "if(%s==%d) " % (operation.condition[0].name, operation.condition[1]) + qasm2_call - ) + qasm2_call = f"if({operation.condition[0].name}=={operation.condition[1]:d}) " + qasm2_call return qasm2_call diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index 80c332aaab4a..11374e9aca92 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -621,7 +621,7 @@ def get_channel(self, channel: str) -> channels.PulseChannel: elif prefix == channels.ControlChannel.prefix: return channels.ControlChannel(index) - raise QiskitError("Channel %s is not valid" % channel) + raise QiskitError(f"Channel {channel} is not valid") @staticmethod def disassemble_value(value_expr: Union[float, str]) -> Union[float, ParameterExpression]: @@ -827,9 +827,7 @@ def _convert_parametric_pulse( pulse_name = instruction.label except AttributeError: sorted_params = sorted(instruction.parameters.items(), key=lambda x: x[0]) - base_str = "{pulse}_{params}".format( - pulse=instruction.pulse_shape, params=str(sorted_params) - ) + base_str = f"{instruction.pulse_shape}_{str(sorted_params)}" short_pulse_id = hashlib.md5(base_str.encode("utf-8")).hexdigest()[:4] pulse_name = f"{instruction.pulse_shape}_{short_pulse_id}" params = dict(instruction.parameters) diff --git a/qiskit/qobj/pulse_qobj.py b/qiskit/qobj/pulse_qobj.py index e5f45b4d2acb..3552d83ada84 100644 --- a/qiskit/qobj/pulse_qobj.py +++ b/qiskit/qobj/pulse_qobj.py @@ -209,8 +209,8 @@ def __repr__(self): return out def __str__(self): - out = "Instruction: %s\n" % self.name - out += "\t\tt0: %s\n" % self.t0 + out = f"Instruction: {self.name}\n" + out += f"\t\tt0: {self.t0}\n" for attr in self._COMMON_ATTRS: if hasattr(self, attr): out += f"\t\t{attr}: {getattr(self, attr)}\n" @@ -434,10 +434,10 @@ def __str__(self): header = pprint.pformat(self.header.to_dict() or {}) else: header = "{}" - out += "Header:\n%s\n" % header - out += "Config:\n%s\n\n" % config + out += f"Header:\n{header}\n" + out += f"Config:\n{config}\n\n" for instruction in self.instructions: - out += "\t%s\n" % instruction + out += f"\t{instruction}\n" return out @classmethod @@ -567,23 +567,20 @@ def __init__(self, qobj_id, config, experiments, header=None): def __repr__(self): experiments_str = [repr(x) for x in self.experiments] experiments_repr = "[" + ", ".join(experiments_str) + "]" - out = "PulseQobj(qobj_id='{}', config={}, experiments={}, header={})".format( - self.qobj_id, - repr(self.config), - experiments_repr, - repr(self.header), + return ( + f"PulseQobj(qobj_id='{self.qobj_id}', config={repr(self.config)}, " + f"experiments={experiments_repr}, header={repr(self.header)})" ) - return out def __str__(self): - out = "Pulse Qobj: %s:\n" % self.qobj_id + out = f"Pulse Qobj: {self.qobj_id}:\n" config = pprint.pformat(self.config.to_dict()) - out += "Config: %s\n" % str(config) + out += f"Config: {str(config)}\n" header = pprint.pformat(self.header.to_dict()) - out += "Header: %s\n" % str(header) + out += f"Header: {str(header)}\n" out += "Experiments:\n" for experiment in self.experiments: - out += "%s" % str(experiment) + out += str(experiment) return out def to_dict(self): diff --git a/qiskit/qobj/qasm_qobj.py b/qiskit/qobj/qasm_qobj.py index 983da1dcfd3b..88d775b3b773 100644 --- a/qiskit/qobj/qasm_qobj.py +++ b/qiskit/qobj/qasm_qobj.py @@ -131,7 +131,7 @@ def to_dict(self): return out_dict def __repr__(self): - out = "QasmQobjInstruction(name='%s'" % self.name + out = f"QasmQobjInstruction(name='{self.name}'" for attr in [ "params", "qubits", @@ -155,7 +155,7 @@ def __repr__(self): return out def __str__(self): - out = "Instruction: %s\n" % self.name + out = f"Instruction: {self.name}\n" for attr in [ "params", "qubits", @@ -215,21 +215,19 @@ def __init__(self, config=None, header=None, instructions=None): def __repr__(self): instructions_str = [repr(x) for x in self.instructions] instructions_repr = "[" + ", ".join(instructions_str) + "]" - out = "QasmQobjExperiment(config={}, header={}, instructions={})".format( - repr(self.config), - repr(self.header), - instructions_repr, + return ( + f"QasmQobjExperiment(config={repr(self.config)}, header={repr(self.header)}," + f" instructions={instructions_repr})" ) - return out def __str__(self): out = "\nOpenQASM2 Experiment:\n" config = pprint.pformat(self.config.to_dict()) header = pprint.pformat(self.header.to_dict()) - out += "Header:\n%s\n" % header - out += "Config:\n%s\n\n" % config + out += f"Header:\n{header}\n" + out += f"Config:\n{config}\n\n" for instruction in self.instructions: - out += "\t%s\n" % instruction + out += f"\t{instruction}\n" return out def to_dict(self): @@ -568,23 +566,20 @@ def __init__(self, qobj_id=None, config=None, experiments=None, header=None): def __repr__(self): experiments_str = [repr(x) for x in self.experiments] experiments_repr = "[" + ", ".join(experiments_str) + "]" - out = "QasmQobj(qobj_id='{}', config={}, experiments={}, header={})".format( - self.qobj_id, - repr(self.config), - experiments_repr, - repr(self.header), + return ( + f"QasmQobj(qobj_id='{self.qobj_id}', config={repr(self.config)}," + f" experiments={experiments_repr}, header={repr(self.header)})" ) - return out def __str__(self): - out = "QASM Qobj: %s:\n" % self.qobj_id + out = f"QASM Qobj: {self.qobj_id}:\n" config = pprint.pformat(self.config.to_dict()) - out += "Config: %s\n" % str(config) + out += f"Config: {str(config)}\n" header = pprint.pformat(self.header.to_dict()) - out += "Header: %s\n" % str(header) + out += f"Header: {str(header)}\n" out += "Experiments:\n" for experiment in self.experiments: - out += "%s" % str(experiment) + out += str(experiment) return out def to_dict(self): diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index db53defbcfa7..0e2045d5be5d 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -128,7 +128,7 @@ def _read_registers_v4(file_obj, num_registers): ) ) name = file_obj.read(data.name_size).decode("utf8") - REGISTER_ARRAY_PACK = "!%sq" % data.size + REGISTER_ARRAY_PACK = f"!{data.size}q" bit_indices_raw = file_obj.read(struct.calcsize(REGISTER_ARRAY_PACK)) bit_indices = list(struct.unpack(REGISTER_ARRAY_PACK, bit_indices_raw)) if data.type.decode("utf8") == "q": @@ -148,7 +148,7 @@ def _read_registers(file_obj, num_registers): ) ) name = file_obj.read(data.name_size).decode("utf8") - REGISTER_ARRAY_PACK = "!%sI" % data.size + REGISTER_ARRAY_PACK = f"!{data.size}I" bit_indices_raw = file_obj.read(struct.calcsize(REGISTER_ARRAY_PACK)) bit_indices = list(struct.unpack(REGISTER_ARRAY_PACK, bit_indices_raw)) if data.type.decode("utf8") == "q": @@ -352,7 +352,7 @@ def _read_instruction( elif gate_name == "Clifford": gate_class = Clifford else: - raise AttributeError("Invalid instruction type: %s" % gate_name) + raise AttributeError(f"Invalid instruction type: {gate_name}") if instruction.label_size <= 0: label = None @@ -507,7 +507,7 @@ def _parse_custom_operation( if type_key == type_keys.CircuitInstruction.PAULI_EVOL_GATE: return definition - raise ValueError("Invalid custom instruction type '%s'" % type_str) + raise ValueError(f"Invalid custom instruction type '{type_str}'") def _read_pauli_evolution_gate(file_obj, version, vectors): @@ -1031,7 +1031,7 @@ def _write_registers(file_obj, in_circ_regs, full_bits): ) ) file_obj.write(reg_name) - REGISTER_ARRAY_PACK = "!%sq" % reg.size + REGISTER_ARRAY_PACK = f"!{reg.size}q" bit_indices = [] for bit in reg: bit_indices.append(bitmap.get(bit, -1)) diff --git a/qiskit/qpy/binary_io/value.py b/qiskit/qpy/binary_io/value.py index c9f0f9af4798..105d4364c07e 100644 --- a/qiskit/qpy/binary_io/value.py +++ b/qiskit/qpy/binary_io/value.py @@ -277,7 +277,7 @@ def _read_parameter_expression(file_obj): elif elem_key == type_keys.Value.PARAMETER_EXPRESSION: value = common.data_from_binary(binary_data, _read_parameter_expression) else: - raise exceptions.QpyError("Invalid parameter expression map type: %s" % elem_key) + raise exceptions.QpyError(f"Invalid parameter expression map type: {elem_key}") symbol_map[symbol] = value return ParameterExpression(symbol_map, expr_) @@ -311,7 +311,7 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine): elif symbol_key == type_keys.Value.PARAMETER_VECTOR: symbol = _read_parameter_vec(file_obj, vectors) else: - raise exceptions.QpyError("Invalid parameter expression map type: %s" % symbol_key) + raise exceptions.QpyError(f"Invalid parameter expression map type: {symbol_key}") elem_key = type_keys.Value(elem_data.type) binary_data = file_obj.read(elem_data.size) @@ -331,7 +331,7 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine): use_symengine=use_symengine, ) else: - raise exceptions.QpyError("Invalid parameter expression map type: %s" % elem_key) + raise exceptions.QpyError(f"Invalid parameter expression map type: {elem_key}") symbol_map[symbol] = value return ParameterExpression(symbol_map, expr_) diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index 34503dbab13b..d89117bc6a1c 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -304,10 +304,11 @@ def load( ): warnings.warn( "The qiskit version used to generate the provided QPY " - "file, %s, is newer than the current qiskit version %s. " + f"file, {'.'.join([str(x) for x in qiskit_version])}, " + f"is newer than the current qiskit version {__version__}. " "This may result in an error if the QPY file uses " "instructions not present in this current qiskit " - "version" % (".".join([str(x) for x in qiskit_version]), __version__) + "version" ) if data.qpy_version < 5: diff --git a/qiskit/quantum_info/operators/channel/quantum_channel.py b/qiskit/quantum_info/operators/channel/quantum_channel.py index 16df920e2e00..ff20feb5bf4f 100644 --- a/qiskit/quantum_info/operators/channel/quantum_channel.py +++ b/qiskit/quantum_info/operators/channel/quantum_channel.py @@ -66,12 +66,9 @@ def __init__( def __repr__(self): prefix = f"{self._channel_rep}(" pad = len(prefix) * " " - return "{}{},\n{}input_dims={}, output_dims={})".format( - prefix, - np.array2string(np.asarray(self.data), separator=", ", prefix=prefix), - pad, - self.input_dims(), - self.output_dims(), + return ( + f"{prefix}{np.array2string(np.asarray(self.data), separator=', ', prefix=prefix)}" + f",\n{pad}input_dims={self.input_dims()}, output_dims={self.output_dims()})" ) def __eq__(self, other: Self): diff --git a/qiskit/quantum_info/operators/channel/superop.py b/qiskit/quantum_info/operators/channel/superop.py index 19867696ec6a..f07652e22d79 100644 --- a/qiskit/quantum_info/operators/channel/superop.py +++ b/qiskit/quantum_info/operators/channel/superop.py @@ -355,8 +355,8 @@ def _append_instruction(self, obj, qargs=None): raise QiskitError(f"Cannot apply Instruction: {obj.name}") if not isinstance(obj.definition, QuantumCircuit): raise QiskitError( - "{} instruction definition is {}; " - "expected QuantumCircuit".format(obj.name, type(obj.definition)) + f"{obj.name} instruction definition is {type(obj.definition)}; " + "expected QuantumCircuit" ) qubit_indices = {bit: idx for idx, bit in enumerate(obj.definition.qubits)} for instruction in obj.definition.data: diff --git a/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py b/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py index 7104dced9df5..bfe76a2f3ca9 100644 --- a/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py +++ b/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py @@ -92,9 +92,7 @@ def _append_circuit(elem, circuit, qargs=None): raise QiskitError(f"Cannot apply Instruction: {gate.name}") if not isinstance(gate.definition, QuantumCircuit): raise QiskitError( - "{} instruction definition is {}; expected QuantumCircuit".format( - gate.name, type(gate.definition) - ) + f"{gate.name} instruction definition is {type(gate.definition)}; expected QuantumCircuit" ) flat_instr = gate.definition diff --git a/qiskit/quantum_info/operators/measures.py b/qiskit/quantum_info/operators/measures.py index 617e9f64b687..293c1236ed70 100644 --- a/qiskit/quantum_info/operators/measures.py +++ b/qiskit/quantum_info/operators/measures.py @@ -93,7 +93,7 @@ def process_fidelity( if channel.dim != target.dim: raise QiskitError( "Input quantum channel and target unitary must have the same " - "dimensions ({} != {}).".format(channel.dim, target.dim) + f"dimensions ({channel.dim} != {target.dim})." ) # Validate complete-positivity and trace-preserving diff --git a/qiskit/quantum_info/operators/op_shape.py b/qiskit/quantum_info/operators/op_shape.py index 4f95126ea148..42f05a8c53ab 100644 --- a/qiskit/quantum_info/operators/op_shape.py +++ b/qiskit/quantum_info/operators/op_shape.py @@ -193,7 +193,7 @@ def _validate(self, shape, raise_exception=False): if raise_exception: raise QiskitError( "Output dimensions do not match matrix shape " - "({} != {})".format(reduce(mul, self._dims_l), shape[0]) + f"({reduce(mul, self._dims_l)} != {shape[0]})" ) return False elif shape[0] != 2**self._num_qargs_l: @@ -207,7 +207,7 @@ def _validate(self, shape, raise_exception=False): if raise_exception: raise QiskitError( "Input dimensions do not match matrix shape " - "({} != {})".format(reduce(mul, self._dims_r), shape[1]) + f"({reduce(mul, self._dims_r)} != {shape[1]})" ) return False elif shape[1] != 2**self._num_qargs_r: @@ -430,7 +430,7 @@ def compose(self, other, qargs=None, front=False): if self._num_qargs_r != other._num_qargs_l or self._dims_r != other._dims_l: raise QiskitError( "Left and right compose dimensions don't match " - "({} != {})".format(self.dims_r(), other.dims_l()) + f"({self.dims_r()} != {other.dims_l()})" ) ret._dims_l = self._dims_l ret._dims_r = other._dims_r @@ -440,7 +440,7 @@ def compose(self, other, qargs=None, front=False): if self._num_qargs_l != other._num_qargs_r or self._dims_l != other._dims_r: raise QiskitError( "Left and right compose dimensions don't match " - "({} != {})".format(self.dims_l(), other.dims_r()) + f"({self.dims_l()} != {other.dims_r()})" ) ret._dims_l = other._dims_l ret._dims_r = self._dims_r @@ -453,15 +453,13 @@ def compose(self, other, qargs=None, front=False): ret._num_qargs_l = self._num_qargs_l if len(qargs) != other._num_qargs_l: raise QiskitError( - "Number of qargs does not match ({} != {})".format( - len(qargs), other._num_qargs_l - ) + f"Number of qargs does not match ({len(qargs)} != {other._num_qargs_l})" ) if self._dims_r or other._dims_r: if self.dims_r(qargs) != other.dims_l(): raise QiskitError( "Subsystem dimension do not match on specified qargs " - "{} != {}".format(self.dims_r(qargs), other.dims_l()) + f"{self.dims_r(qargs)} != {other.dims_l()}" ) dims_r = list(self.dims_r()) for i, dim in zip(qargs, other.dims_r()): @@ -475,15 +473,13 @@ def compose(self, other, qargs=None, front=False): ret._num_qargs_r = self._num_qargs_r if len(qargs) != other._num_qargs_r: raise QiskitError( - "Number of qargs does not match ({} != {})".format( - len(qargs), other._num_qargs_r - ) + f"Number of qargs does not match ({len(qargs)} != {other._num_qargs_r})" ) if self._dims_l or other._dims_l: if self.dims_l(qargs) != other.dims_r(): raise QiskitError( "Subsystem dimension do not match on specified qargs " - "{} != {}".format(self.dims_l(qargs), other.dims_r()) + f"{self.dims_l(qargs)} != {other.dims_r()}" ) dims_l = list(self.dims_l()) for i, dim in zip(qargs, other.dims_l()): @@ -508,26 +504,22 @@ def _validate_add(self, other, qargs=None): if self.dims_l(qargs) != other.dims_l(): raise QiskitError( "Cannot add shapes width different left " - "dimension on specified qargs {} != {}".format( - self.dims_l(qargs), other.dims_l() - ) + f"dimension on specified qargs {self.dims_l(qargs)} != {other.dims_l()}" ) if self.dims_r(qargs) != other.dims_r(): raise QiskitError( "Cannot add shapes width different total right " - "dimension on specified qargs{} != {}".format( - self.dims_r(qargs), other.dims_r() - ) + f"dimension on specified qargs{self.dims_r(qargs)} != {other.dims_r()}" ) elif self != other: if self._dim_l != other._dim_l: raise QiskitError( "Cannot add shapes width different total left " - "dimension {} != {}".format(self._dim_l, other._dim_l) + f"dimension {self._dim_l} != {other._dim_l}" ) if self._dim_r != other._dim_r: raise QiskitError( "Cannot add shapes width different total right " - "dimension {} != {}".format(self._dim_r, other._dim_r) + f"dimension {self._dim_r} != {other._dim_r}" ) return self diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py index 016e337f082c..a4e93f364809 100644 --- a/qiskit/quantum_info/operators/operator.py +++ b/qiskit/quantum_info/operators/operator.py @@ -128,12 +128,9 @@ def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED): def __repr__(self): prefix = "Operator(" pad = len(prefix) * " " - return "{}{},\n{}input_dims={}, output_dims={})".format( - prefix, - np.array2string(self.data, separator=", ", prefix=prefix), - pad, - self.input_dims(), - self.output_dims(), + return ( + f"{prefix}{np.array2string(self.data, separator=', ', prefix=prefix)},\n" + f"{pad}input_dims={self.input_dims()}, output_dims={self.output_dims()})" ) def __eq__(self, other): @@ -763,10 +760,8 @@ def _append_instruction(self, obj, qargs=None): raise QiskitError(f"Cannot apply Operation: {obj.name}") if not isinstance(obj.definition, QuantumCircuit): raise QiskitError( - 'Operation "{}" ' - "definition is {} but expected QuantumCircuit.".format( - obj.name, type(obj.definition) - ) + f'Operation "{obj.name}" ' + f"definition is {type(obj.definition)} but expected QuantumCircuit." ) if obj.definition.global_phase: dimension = 2**obj.num_qubits diff --git a/qiskit/quantum_info/operators/predicates.py b/qiskit/quantum_info/operators/predicates.py index 57b7df64f268..f432195cd571 100644 --- a/qiskit/quantum_info/operators/predicates.py +++ b/qiskit/quantum_info/operators/predicates.py @@ -22,6 +22,7 @@ def matrix_equal(mat1, mat2, ignore_phase=False, rtol=RTOL_DEFAULT, atol=ATOL_DEFAULT, props=None): + # pylint: disable-next=consider-using-f-string """Test if two arrays are equal. The final comparison is implemented using Numpy.allclose. See its diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py index e43eca4aff21..38e471f0b0a6 100644 --- a/qiskit/quantum_info/operators/symplectic/base_pauli.py +++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py @@ -215,12 +215,12 @@ def commutes(self, other: BasePauli, qargs: list | None = None) -> np.ndarray: if qargs is not None and len(qargs) != other.num_qubits: raise QiskitError( "Number of qubits of other Pauli does not match number of " - "qargs ({} != {}).".format(other.num_qubits, len(qargs)) + f"qargs ({other.num_qubits} != {len(qargs)})." ) if qargs is None and self.num_qubits != other.num_qubits: raise QiskitError( "Number of qubits of other Pauli does not match the current " - "Pauli ({} != {}).".format(other.num_qubits, self.num_qubits) + f"Pauli ({other.num_qubits} != {self.num_qubits})." ) if qargs is not None: inds = list(qargs) @@ -262,15 +262,12 @@ def evolve( # Check dimension if qargs is not None and len(qargs) != other.num_qubits: raise QiskitError( - "Incorrect number of qubits for Clifford circuit ({} != {}).".format( - other.num_qubits, len(qargs) - ) + f"Incorrect number of qubits for Clifford circuit ({other.num_qubits} != {len(qargs)})." ) if qargs is None and self.num_qubits != other.num_qubits: raise QiskitError( - "Incorrect number of qubits for Clifford circuit ({} != {}).".format( - other.num_qubits, self.num_qubits - ) + f"Incorrect number of qubits for Clifford circuit " + f"({other.num_qubits} != {self.num_qubits})." ) # Evolve via Pauli @@ -571,9 +568,8 @@ def _append_circuit(self, circuit, qargs=None): raise QiskitError(f"Cannot apply Instruction: {gate.name}") if not isinstance(gate.definition, QuantumCircuit): raise QiskitError( - "{} instruction definition is {}; expected QuantumCircuit".format( - gate.name, type(gate.definition) - ) + f"{gate.name} instruction definition is {type(gate.definition)};" + f" expected QuantumCircuit" ) circuit = gate.definition diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py index 8187c1ee41e6..867867eeb98a 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli.py +++ b/qiskit/quantum_info/operators/symplectic/pauli.py @@ -344,7 +344,7 @@ def delete(self, qubits: int | list) -> Pauli: if max(qubits) > self.num_qubits - 1: raise QiskitError( "Qubit index is larger than the number of qubits " - "({}>{}).".format(max(qubits), self.num_qubits - 1) + f"({max(qubits)}>{self.num_qubits - 1})." ) if len(qubits) == self.num_qubits: raise QiskitError("Cannot delete all qubits of Pauli") @@ -379,12 +379,12 @@ def insert(self, qubits: int | list, value: Pauli) -> Pauli: if len(qubits) != value.num_qubits: raise QiskitError( "Number of indices does not match number of qubits for " - "the inserted Pauli ({}!={})".format(len(qubits), value.num_qubits) + f"the inserted Pauli ({len(qubits)}!={value.num_qubits})" ) if max(qubits) > ret.num_qubits - 1: raise QiskitError( "Index is too larger for combined Pauli number of qubits " - "({}>{}).".format(max(qubits), ret.num_qubits - 1) + f"({max(qubits)}>{ret.num_qubits - 1})." ) # Qubit positions for original op self_qubits = [i for i in range(ret.num_qubits) if i not in qubits] diff --git a/qiskit/quantum_info/operators/symplectic/pauli_list.py b/qiskit/quantum_info/operators/symplectic/pauli_list.py index 3d348d236387..f2e408dd9bd2 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli_list.py +++ b/qiskit/quantum_info/operators/symplectic/pauli_list.py @@ -382,8 +382,8 @@ def delete(self, ind: int | list, qubit: bool = False) -> PauliList: if not qubit: if max(ind) >= len(self): raise QiskitError( - "Indices {} are not all less than the size" - " of the PauliList ({})".format(ind, len(self)) + f"Indices {ind} are not all less than the size" + f" of the PauliList ({len(self)})" ) z = np.delete(self._z, ind, axis=0) x = np.delete(self._x, ind, axis=0) @@ -394,8 +394,8 @@ def delete(self, ind: int | list, qubit: bool = False) -> PauliList: # Column (qubit) deletion if max(ind) >= self.num_qubits: raise QiskitError( - "Indices {} are not all less than the number of" - " qubits in the PauliList ({})".format(ind, self.num_qubits) + f"Indices {ind} are not all less than the number of" + f" qubits in the PauliList ({self.num_qubits})" ) z = np.delete(self._z, ind, axis=1) x = np.delete(self._x, ind, axis=1) @@ -432,8 +432,7 @@ def insert(self, ind: int, value: PauliList, qubit: bool = False) -> PauliList: if not qubit: if ind > size: raise QiskitError( - "Index {} is larger than the number of rows in the" - " PauliList ({}).".format(ind, size) + f"Index {ind} is larger than the number of rows in the" f" PauliList ({size})." ) base_z = np.insert(self._z, ind, value._z, axis=0) base_x = np.insert(self._x, ind, value._x, axis=0) @@ -443,8 +442,8 @@ def insert(self, ind: int, value: PauliList, qubit: bool = False) -> PauliList: # Column insertion if ind > self.num_qubits: raise QiskitError( - "Index {} is greater than number of qubits" - " in the PauliList ({})".format(ind, self.num_qubits) + f"Index {ind} is greater than number of qubits" + f" in the PauliList ({self.num_qubits})" ) if len(value) == 1: # Pad blocks to correct size @@ -461,7 +460,7 @@ def insert(self, ind: int, value: PauliList, qubit: bool = False) -> PauliList: raise QiskitError( "Input PauliList must have a single row, or" " the same number of rows as the Pauli Table" - " ({}).".format(size) + f" ({size})." ) # Build new array by blocks z = np.hstack([self.z[:, :ind], value_z, self.z[:, ind:]]) diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py index 6204189be443..91d8d82decae 100644 --- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py @@ -172,7 +172,7 @@ def __init__( if self._coeffs.shape != (self._pauli_list.size,): raise QiskitError( "coeff vector is incorrect shape for number" - " of Paulis {} != {}".format(self._coeffs.shape, self._pauli_list.size) + f" of Paulis {self._coeffs.shape} != {self._pauli_list.size}" ) # Initialize LinearOp super().__init__(num_qubits=self._pauli_list.num_qubits) @@ -186,11 +186,9 @@ def __array__(self, dtype=None, copy=None): def __repr__(self): prefix = "SparsePauliOp(" pad = len(prefix) * " " - return "{}{},\n{}coeffs={})".format( - prefix, - self.paulis.to_labels(), - pad, - np.array2string(self.coeffs, separator=", "), + return ( + f"{prefix}{self.paulis.to_labels()},\n{pad}" + f"coeffs={np.array2string(self.coeffs, separator=', ')})" ) def __eq__(self, other): diff --git a/qiskit/quantum_info/states/densitymatrix.py b/qiskit/quantum_info/states/densitymatrix.py index 1c66d8bcf5cf..7826e3f22fec 100644 --- a/qiskit/quantum_info/states/densitymatrix.py +++ b/qiskit/quantum_info/states/densitymatrix.py @@ -123,11 +123,9 @@ def __eq__(self, other): def __repr__(self): prefix = "DensityMatrix(" pad = len(prefix) * " " - return "{}{},\n{}dims={})".format( - prefix, - np.array2string(self._data, separator=", ", prefix=prefix), - pad, - self._op_shape.dims_l(), + return ( + f"{prefix}{np.array2string(self._data, separator=', ', prefix=prefix)},\n" + f"{pad}dims={self._op_shape.dims_l()})" ) @property @@ -771,9 +769,8 @@ def _append_instruction(self, other, qargs=None): raise QiskitError(f"Cannot apply Instruction: {other.name}") if not isinstance(other.definition, QuantumCircuit): raise QiskitError( - "{} instruction definition is {}; expected QuantumCircuit".format( - other.name, type(other.definition) - ) + f"{other.name} instruction definition is {type(other.definition)};" + f" expected QuantumCircuit" ) qubit_indices = {bit: idx for idx, bit in enumerate(other.definition.qubits)} for instruction in other.definition: diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py index df39ba42f915..7fa14eaac9da 100644 --- a/qiskit/quantum_info/states/statevector.py +++ b/qiskit/quantum_info/states/statevector.py @@ -117,11 +117,9 @@ def __eq__(self, other): def __repr__(self): prefix = "Statevector(" pad = len(prefix) * " " - return "{}{},\n{}dims={})".format( - prefix, - np.array2string(self._data, separator=", ", prefix=prefix), - pad, - self._op_shape.dims_l(), + return ( + f"{prefix}{np.array2string(self._data, separator=', ', prefix=prefix)},\n{pad}" + f"dims={self._op_shape.dims_l()})" ) @property @@ -940,9 +938,7 @@ def _evolve_instruction(statevec, obj, qargs=None): raise QiskitError(f"Cannot apply Instruction: {obj.name}") if not isinstance(obj.definition, QuantumCircuit): raise QiskitError( - "{} instruction definition is {}; expected QuantumCircuit".format( - obj.name, type(obj.definition) - ) + f"{obj.name} instruction definition is {type(obj.definition)}; expected QuantumCircuit" ) if obj.definition.global_phase: diff --git a/qiskit/result/counts.py b/qiskit/result/counts.py index 8b90ff0f0428..8168a3d21900 100644 --- a/qiskit/result/counts.py +++ b/qiskit/result/counts.py @@ -130,7 +130,7 @@ def most_frequent(self): max_values_counts = [x[0] for x in self.items() if x[1] == max_value] if len(max_values_counts) != 1: raise exceptions.QiskitError( - "Multiple values have the same maximum counts: %s" % ",".join(max_values_counts) + f"Multiple values have the same maximum counts: {','.join(max_values_counts)}" ) return max_values_counts[0] diff --git a/qiskit/result/mitigation/correlated_readout_mitigator.py b/qiskit/result/mitigation/correlated_readout_mitigator.py index 06cc89b4c52b..99e6f9ae4145 100644 --- a/qiskit/result/mitigation/correlated_readout_mitigator.py +++ b/qiskit/result/mitigation/correlated_readout_mitigator.py @@ -54,8 +54,8 @@ def __init__(self, assignment_matrix: np.ndarray, qubits: Optional[Iterable[int] else: if len(qubits) != matrix_qubits_num: raise QiskitError( - "The number of given qubits ({}) is different than the number of " - "qubits inferred from the matrices ({})".format(len(qubits), matrix_qubits_num) + f"The number of given qubits ({len(qubits)}) is different than the number of " + f"qubits inferred from the matrices ({matrix_qubits_num})" ) self._qubits = qubits self._num_qubits = len(self._qubits) diff --git a/qiskit/result/mitigation/local_readout_mitigator.py b/qiskit/result/mitigation/local_readout_mitigator.py index ad71911c2d75..197c3f00d9be 100644 --- a/qiskit/result/mitigation/local_readout_mitigator.py +++ b/qiskit/result/mitigation/local_readout_mitigator.py @@ -68,8 +68,8 @@ def __init__( else: if len(qubits) != len(assignment_matrices): raise QiskitError( - "The number of given qubits ({}) is different than the number of qubits " - "inferred from the matrices ({})".format(len(qubits), len(assignment_matrices)) + f"The number of given qubits ({len(qubits)}) is different than the number of qubits " + f"inferred from the matrices ({len(assignment_matrices)})" ) self._qubits = qubits self._num_qubits = len(self._qubits) diff --git a/qiskit/result/mitigation/utils.py b/qiskit/result/mitigation/utils.py index 26b5ee373488..823e2b69a6ca 100644 --- a/qiskit/result/mitigation/utils.py +++ b/qiskit/result/mitigation/utils.py @@ -120,9 +120,7 @@ def marganalize_counts( clbits_len = len(clbits) if not clbits is None else 0 if clbits_len not in (0, qubits_len): raise QiskitError( - "Num qubits ({}) does not match number of clbits ({}).".format( - qubits_len, clbits_len - ) + f"Num qubits ({qubits_len}) does not match number of clbits ({clbits_len})." ) counts = marginal_counts(counts, clbits) if clbits is None and qubits is not None: diff --git a/qiskit/result/models.py b/qiskit/result/models.py index 992810196713..83d9e4e78d5f 100644 --- a/qiskit/result/models.py +++ b/qiskit/result/models.py @@ -66,8 +66,7 @@ def __repr__(self): string_list = [] for field in self._data_attributes: string_list.append(f"{field}={getattr(self, field)}") - out = "ExperimentResultData(%s)" % ", ".join(string_list) - return out + return f"ExperimentResultData({', '.join(string_list)})" def to_dict(self): """Return a dictionary format representation of the ExperimentResultData @@ -157,23 +156,21 @@ def __init__( self._metadata.update(kwargs) def __repr__(self): - out = "ExperimentResult(shots={}, success={}, meas_level={}, data={}".format( - self.shots, - self.success, - self.meas_level, - self.data, + out = ( + f"ExperimentResult(shots={self.shots}, success={self.success}," + f" meas_level={self.meas_level}, data={self.data}" ) if hasattr(self, "header"): - out += ", header=%s" % self.header + out += f", header={self.header}" if hasattr(self, "status"): - out += ", status=%s" % self.status + out += f", status={self.status}" if hasattr(self, "seed"): - out += ", seed=%s" % self.seed + out += f", seed={self.seed}" if hasattr(self, "meas_return"): - out += ", meas_return=%s" % self.meas_return + out += f", meas_return={self.meas_return}" for key, value in self._metadata.items(): if isinstance(value, str): - value_str = "'%s'" % value + value_str = f"'{value}'" else: value_str = repr(value) out += f", {key}={value_str}" diff --git a/qiskit/result/result.py b/qiskit/result/result.py index c1792de56ae0..7df365785166 100644 --- a/qiskit/result/result.py +++ b/qiskit/result/result.py @@ -69,21 +69,14 @@ def __init__( def __repr__(self): out = ( - "Result(backend_name='%s', backend_version='%s', qobj_id='%s', " - "job_id='%s', success=%s, results=%s" - % ( - self.backend_name, - self.backend_version, - self.qobj_id, - self.job_id, - self.success, - self.results, - ) + f"Result(backend_name='{self.backend_name}', backend_version='{self.backend_version}'," + f" qobj_id='{self.qobj_id}', job_id='{self.job_id}', success={self.success}," + f" results={self.results}" ) out += f", date={self.date}, status={self.status}, header={self.header}" for key, value in self._metadata.items(): if isinstance(value, str): - value_str = "'%s'" % value + value_str = f"'{value}'" else: value_str = repr(value) out += f", {key}={value_str}" @@ -236,10 +229,10 @@ def get_memory(self, experiment=None): except KeyError as ex: raise QiskitError( - 'No memory for experiment "{}". ' + f'No memory for experiment "{repr(experiment)}". ' "Please verify that you either ran a measurement level 2 job " 'with the memory flag set, eg., "memory=True", ' - "or a measurement level 0/1 job.".format(repr(experiment)) + "or a measurement level 0/1 job." ) from ex def get_counts(self, experiment=None): @@ -377,14 +370,14 @@ def _get_experiment(self, key=None): ] if len(exp) == 0: - raise QiskitError('Data for experiment "%s" could not be found.' % key) + raise QiskitError(f'Data for experiment "{key}" could not be found.') if len(exp) == 1: exp = exp[0] else: warnings.warn( - 'Result object contained multiple results matching name "%s", ' + f'Result object contained multiple results matching name "{key}", ' "only first match will be returned. Use an integer index to " - "retrieve results for all entries." % key + "retrieve results for all entries." ) exp = exp[0] diff --git a/qiskit/scheduler/lowering.py b/qiskit/scheduler/lowering.py index fa622b205d3b..f0fb33957d9e 100644 --- a/qiskit/scheduler/lowering.py +++ b/qiskit/scheduler/lowering.py @@ -145,9 +145,9 @@ def get_measure_schedule(qubit_mem_slots: Dict[int, int]) -> CircuitPulseDef: elif isinstance(instruction.operation, Measure): if len(inst_qubits) != 1 and len(instruction.clbits) != 1: raise QiskitError( - "Qubit '{}' or classical bit '{}' errored because the " + f"Qubit '{inst_qubits}' or classical bit '{instruction.clbits}' errored because the " "circuit Measure instruction only takes one of " - "each.".format(inst_qubits, instruction.clbits) + "each." ) qubit_mem_slots[inst_qubits[0]] = clbit_indices[instruction.clbits[0]] else: diff --git a/qiskit/synthesis/linear/cnot_synth.py b/qiskit/synthesis/linear/cnot_synth.py index 5063577ed653..699523a7e75d 100644 --- a/qiskit/synthesis/linear/cnot_synth.py +++ b/qiskit/synthesis/linear/cnot_synth.py @@ -53,8 +53,7 @@ def synth_cnot_count_full_pmh( """ if not isinstance(state, (list, np.ndarray)): raise QiskitError( - "state should be of type list or numpy.ndarray, " - "but was of the type {}".format(type(state)) + f"state should be of type list or numpy.ndarray, but was of the type {type(state)}" ) state = np.array(state) # Synthesize lower triangular part diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py index 41ba75c6b237..26a5b52521b2 100644 --- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py +++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py @@ -116,7 +116,7 @@ def decompose_two_qubit_product_gate(special_unitary_matrix: np.ndarray): if deviation > 1.0e-13: raise QiskitError( "decompose_two_qubit_product_gate: decomposition failed: " - "deviation too large: {}".format(deviation) + f"deviation too large: {deviation}" ) return L, R, phase diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py index 614e166050e8..27f98da68e7c 100644 --- a/qiskit/transpiler/coupling.py +++ b/qiskit/transpiler/coupling.py @@ -101,7 +101,7 @@ def add_physical_qubit(self, physical_qubit): raise CouplingError("Physical qubits should be integers.") if physical_qubit in self.physical_qubits: raise CouplingError( - "The physical qubit %s is already in the coupling graph" % physical_qubit + f"The physical qubit {physical_qubit} is already in the coupling graph" ) self.graph.add_node(physical_qubit) self._dist_matrix = None # invalidate @@ -188,9 +188,9 @@ def distance(self, physical_qubit1, physical_qubit2): CouplingError: if the qubits do not exist in the CouplingMap """ if physical_qubit1 >= self.size(): - raise CouplingError("%s not in coupling graph" % physical_qubit1) + raise CouplingError(f"{physical_qubit1} not in coupling graph") if physical_qubit2 >= self.size(): - raise CouplingError("%s not in coupling graph" % physical_qubit2) + raise CouplingError(f"{physical_qubit2} not in coupling graph") self.compute_distance_matrix() res = self._dist_matrix[physical_qubit1, physical_qubit2] if res == math.inf: diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index 1bebc7b84dc8..4117e2987bb6 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -98,8 +98,8 @@ def order_based_on_type(value1, value2): virtual = value1 else: raise LayoutError( - "The map (%s -> %s) has to be a (Bit -> integer)" - " or the other way around." % (type(value1), type(value2)) + f"The map ({type(value1)} -> {type(value2)}) has to be a (Bit -> integer)" + " or the other way around." ) return virtual, physical @@ -137,7 +137,7 @@ def __delitem__(self, key): else: raise LayoutError( "The key to remove should be of the form" - " Qubit or integer) and %s was provided" % (type(key),) + f" Qubit or integer) and {type(key)} was provided" ) def __len__(self): diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index bcac1f5d4faf..936613744b84 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -302,9 +302,7 @@ def _replace_node(self, dag, node, instr_map): if len(node.op.params) != len(target_params): raise TranspilerError( "Translation num_params not equal to op num_params." - "Op: {} {} Translation: {}\n{}".format( - node.op.params, node.op.name, target_params, target_dag - ) + f"Op: {node.op.params} {node.op.name} Translation: {target_params}\n{target_dag}" ) if node.op.params: parameter_map = dict(zip(target_params, node.op.params)) diff --git a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py index 701e87dd9cd5..73e1d4ac5484 100644 --- a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py +++ b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py @@ -78,7 +78,7 @@ def run(self, dag): continue raise QiskitError( "Cannot unroll all 3q or more gates. " - "No rule to expand instruction %s." % node.op.name + f"No rule to expand instruction {node.op.name}." ) decomposition = circuit_to_dag(node.op.definition, copy_operations=False) decomposition = self.run(decomposition) # recursively unroll diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py index a54e4bfcb001..99bf95147ae2 100644 --- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py +++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py @@ -95,9 +95,9 @@ def run(self, dag): if unrolled is None: # opaque node raise QiskitError( - "Cannot unroll the circuit to the given basis, %s. " - "Instruction %s not found in equivalence library " - "and no rule found to expand." % (str(self._basis_gates), node.op.name) + f"Cannot unroll the circuit to the given basis, {str(self._basis_gates)}. " + f"Instruction {node.op.name} not found in equivalence library " + "and no rule found to expand." ) decomposition = circuit_to_dag(unrolled, copy_operations=False) diff --git a/qiskit/transpiler/passes/calibration/rzx_builder.py b/qiskit/transpiler/passes/calibration/rzx_builder.py index 4cb576a23bc9..c153c3eeef33 100644 --- a/qiskit/transpiler/passes/calibration/rzx_builder.py +++ b/qiskit/transpiler/passes/calibration/rzx_builder.py @@ -204,7 +204,7 @@ def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | Sche if cal_type in [CRCalType.ECR_CX_FORWARD, CRCalType.ECR_FORWARD]: xgate = self._inst_map.get("x", qubits[0]) with builder.build( - default_alignment="sequential", name="rzx(%.3f)" % theta + default_alignment="sequential", name=f"rzx({theta:.3f})" ) as rzx_theta_native: for cr_tone, comp_tone in zip(cr_tones, comp_tones): with builder.align_left(): @@ -230,7 +230,7 @@ def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | Sche builder.call(szt, name="szt") with builder.build( - default_alignment="sequential", name="rzx(%.3f)" % theta + default_alignment="sequential", name=f"rzx({theta:.3f})" ) as rzx_theta_flip: builder.call(hadamard, name="hadamard") for cr_tone, comp_tone in zip(cr_tones, comp_tones): @@ -297,7 +297,7 @@ def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | Sche # RZXCalibrationNoEcho only good for forward CR direction if cal_type in [CRCalType.ECR_CX_FORWARD, CRCalType.ECR_FORWARD]: - with builder.build(default_alignment="left", name="rzx(%.3f)" % theta) as rzx_theta: + with builder.build(default_alignment="left", name=f"rzx({theta:.3f})") as rzx_theta: stretched_dur = self.rescale_cr_inst(cr_tones[0], 2 * theta) self.rescale_cr_inst(comp_tones[0], 2 * theta) # Placeholder to make pulse gate work diff --git a/qiskit/transpiler/passes/optimization/inverse_cancellation.py b/qiskit/transpiler/passes/optimization/inverse_cancellation.py index 958f53ef057d..f5523432c26e 100644 --- a/qiskit/transpiler/passes/optimization/inverse_cancellation.py +++ b/qiskit/transpiler/passes/optimization/inverse_cancellation.py @@ -53,8 +53,8 @@ def __init__(self, gates_to_cancel: List[Union[Gate, Tuple[Gate, Gate]]]): ) else: raise TranspilerError( - "InverseCancellation pass does not take input type {}. Input must be" - " a Gate.".format(type(gates)) + f"InverseCancellation pass does not take input type {type(gates)}. Input must be" + " a Gate." ) self.self_inverse_gates = [] diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py index 9370fe7409f0..f8302b9232c0 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py @@ -308,7 +308,7 @@ def run(self, dag): if "u3" in self.basis: new_op = U3Gate(*right_parameters) else: - raise TranspilerError("It was not possible to use the basis %s" % self.basis) + raise TranspilerError(f"It was not possible to use the basis {self.basis}") dag.global_phase += right_global_phase diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 28ae67b321cb..acb23f39ab09 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -218,7 +218,7 @@ def run(self, dag): elif self.heuristic == "decay": heuristic = Heuristic.Decay else: - raise TranspilerError("Heuristic %s not recognized." % self.heuristic) + raise TranspilerError(f"Heuristic {self.heuristic} not recognized.") disjoint_utils.require_layout_isolated_to_component( dag, self.coupling_map if self.target is None else self.target ) diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index ec7ea8149137..3732802b770e 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -357,9 +357,7 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): # Give up if we fail again if not success_flag: - raise TranspilerError( - "swap mapper failed: " + "layer %d, sublayer %d" % (i, j) - ) + raise TranspilerError(f"swap mapper failed: layer {i}, sublayer {j}") # Update the record of qubit positions # for each inner iteration diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 150874a84c7d..668d10f32bcc 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -540,8 +540,8 @@ def _synthesize_op_using_plugins( if isinstance(plugin_specifier, str): if plugin_specifier not in hls_plugin_manager.method_names(op.name): raise TranspilerError( - "Specified method: %s not found in available plugins for %s" - % (plugin_specifier, op.name) + f"Specified method: {plugin_specifier} not found in available " + f"plugins for {op.name}" ) plugin_method = hls_plugin_manager.method(op.name, plugin_specifier) else: diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py index 61ddc71d131e..437718ec27b4 100644 --- a/qiskit/transpiler/passes/utils/check_map.py +++ b/qiskit/transpiler/passes/utils/check_map.py @@ -85,10 +85,8 @@ def _recurse(self, dag, wire_map) -> bool: and not dag.has_calibration_for(node) and (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) not in self.qargs ): - self.property_set["check_map_msg"] = "{}({}, {}) failed".format( - node.name, - wire_map[node.qargs[0]], - wire_map[node.qargs[1]], + self.property_set["check_map_msg"] = ( + f"{node.name}({wire_map[node.qargs[0]]}, {wire_map[node.qargs[1]]}) failed" ) return False return True diff --git a/qiskit/transpiler/passes/utils/error.py b/qiskit/transpiler/passes/utils/error.py index f2659ec052ff..44420582c1a3 100644 --- a/qiskit/transpiler/passes/utils/error.py +++ b/qiskit/transpiler/passes/utils/error.py @@ -43,7 +43,7 @@ def __init__(self, msg=None, action="raise"): if action in ["raise", "warn", "log"]: self.action = action else: - raise TranspilerError("Unknown action: %s" % action) + raise TranspilerError(f"Unknown action: {action}") def run(self, _): """Run the Error pass on `dag`.""" @@ -66,4 +66,4 @@ def run(self, _): logger = logging.getLogger(__name__) logger.info(msg) else: - raise TranspilerError("Unknown action: %s" % self.action) + raise TranspilerError(f"Unknown action: {self.action}") diff --git a/qiskit/transpiler/passes/utils/fixed_point.py b/qiskit/transpiler/passes/utils/fixed_point.py index fbef9d0a85ef..a85a7a8e6e7d 100644 --- a/qiskit/transpiler/passes/utils/fixed_point.py +++ b/qiskit/transpiler/passes/utils/fixed_point.py @@ -37,12 +37,12 @@ def __init__(self, property_to_check): def run(self, dag): """Run the FixedPoint pass on `dag`.""" current_value = self.property_set[self._property] - fixed_point_previous_property = "_fixed_point_previous_%s" % self._property + fixed_point_previous_property = f"_fixed_point_previous_{self._property}" if self.property_set[fixed_point_previous_property] is None: - self.property_set["%s_fixed_point" % self._property] = False + self.property_set[f"{self._property}_fixed_point"] = False else: fixed_point_reached = self.property_set[fixed_point_previous_property] == current_value - self.property_set["%s_fixed_point" % self._property] = fixed_point_reached + self.property_set[f"{self._property}_fixed_point"] = fixed_point_reached self.property_set[fixed_point_previous_property] = deepcopy(current_value) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 1b77e7dbd269..ec479f9e006b 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -518,7 +518,7 @@ def generate_translation_passmanager( ), ] else: - raise TranspilerError("Invalid translation method %s." % method) + raise TranspilerError(f"Invalid translation method {method}.") return PassManager(unroll) @@ -557,7 +557,7 @@ def generate_scheduling( try: scheduling.append(scheduler[scheduling_method](instruction_durations, target=target)) except KeyError as ex: - raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex + raise TranspilerError(f"Invalid scheduling method {scheduling_method}.") from ex elif instruction_durations: # No scheduling. But do unit conversion for delays. def _contains_delay(property_set): diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 53daa4ccbe65..8805deece50e 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -411,7 +411,7 @@ def add_instruction(self, instruction, properties=None, name=None): if properties is None: properties = {None: None} if instruction_name in self._gate_map: - raise AttributeError("Instruction %s is already in the target" % instruction_name) + raise AttributeError(f"Instruction {instruction_name} is already in the target") self._gate_name_map[instruction_name] = instruction if is_class: qargs_val = {None: None} @@ -1062,7 +1062,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): for qargs, properties in self._gate_map[two_q_gate].items(): if len(qargs) != 2: raise ValueError( - "Specified two_q_gate: %s is not a 2 qubit instruction" % two_q_gate + f"Specified two_q_gate: {two_q_gate} is not a 2 qubit instruction" ) coupling_graph.add_edge(*qargs, {two_q_gate: properties}) cmap = CouplingMap() diff --git a/qiskit/user_config.py b/qiskit/user_config.py index 73a68eb6bfd5..0ca52fc5c8c0 100644 --- a/qiskit/user_config.py +++ b/qiskit/user_config.py @@ -63,9 +63,9 @@ def read_config_file(self): if circuit_drawer: if circuit_drawer not in ["text", "mpl", "latex", "latex_source", "auto"]: raise exceptions.QiskitUserConfigError( - "%s is not a valid circuit drawer backend. Must be " + f"{circuit_drawer} is not a valid circuit drawer backend. Must be " "either 'text', 'mpl', 'latex', 'latex_source', or " - "'auto'." % circuit_drawer + "'auto'." ) self.settings["circuit_drawer"] = circuit_drawer @@ -96,8 +96,8 @@ def read_config_file(self): if circuit_mpl_style: if not isinstance(circuit_mpl_style, str): warn( - "%s is not a valid mpl circuit style. Must be " - "a text string. Will not load style." % circuit_mpl_style, + f"{circuit_mpl_style} is not a valid mpl circuit style. Must be " + "a text string. Will not load style.", UserWarning, 2, ) @@ -112,8 +112,8 @@ def read_config_file(self): for path in cpath_list: if not os.path.exists(os.path.expanduser(path)): warn( - "%s is not a valid circuit mpl style path." - " Correct the path in ~/.qiskit/settings.conf." % path, + f"{path} is not a valid circuit mpl style path." + " Correct the path in ~/.qiskit/settings.conf.", UserWarning, 2, ) diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py index a1672dc1676c..146de9d32dee 100644 --- a/qiskit/visualization/circuit/circuit_visualization.py +++ b/qiskit/visualization/circuit/circuit_visualization.py @@ -345,8 +345,8 @@ def check_clbit_in_inst(circuit, cregbundle): ) else: raise VisualizationError( - "Invalid output type %s selected. The only valid choices " - "are text, latex, latex_source, and mpl" % output + f"Invalid output type {output} selected. The only valid choices " + "are text, latex, latex_source, and mpl" ) if image and interactive: image.show() diff --git a/qiskit/visualization/circuit/latex.py b/qiskit/visualization/circuit/latex.py index 9341126bcd1a..4cb233277c0d 100644 --- a/qiskit/visualization/circuit/latex.py +++ b/qiskit/visualization/circuit/latex.py @@ -415,7 +415,7 @@ def _build_latex_array(self): cwire_list = [] if len(wire_list) == 1 and not node.cargs: - self._latex[wire_list[0]][column] = "\\gate{%s}" % gate_text + self._latex[wire_list[0]][column] = f"\\gate{{{gate_text}}}" elif isinstance(op, ControlledGate): num_cols_op = self._build_ctrl_gate(op, gate_text, wire_list, column) @@ -443,20 +443,20 @@ def _build_multi_gate(self, op, gate_text, wire_list, cwire_list, col): self._latex[wire_min][col] = ( f"\\multigate{{{wire_max - wire_min}}}{{{gate_text}}}_" + "<" * (len(str(wire_ind)) + 2) - + "{%s}" % wire_ind + + f"{{{wire_ind}}}" ) for wire in range(wire_min + 1, wire_max + 1): if wire < cwire_start: - ghost_box = "\\ghost{%s}" % gate_text + ghost_box = f"\\ghost{{{gate_text}}}" if wire in wire_list: wire_ind = wire_list.index(wire) else: - ghost_box = "\\cghost{%s}" % gate_text + ghost_box = f"\\cghost{{{gate_text}}}" if wire in cwire_list: wire_ind = cwire_list.index(wire) if wire in wire_list + cwire_list: self._latex[wire][col] = ( - ghost_box + "_" + "<" * (len(str(wire_ind)) + 2) + "{%s}" % wire_ind + ghost_box + "_" + "<" * (len(str(wire_ind)) + 2) + f"{{{wire_ind}}}" ) else: self._latex[wire][col] = ghost_box @@ -484,7 +484,7 @@ def _build_ctrl_gate(self, op, gate_text, wire_list, col): elif isinstance(op.base_gate, (U1Gate, PhaseGate)): num_cols_op = self._build_symmetric_gate(op, gate_text, wire_list, col) else: - self._latex[wireqargs[0]][col] = "\\gate{%s}" % gate_text + self._latex[wireqargs[0]][col] = f"\\gate{{{gate_text}}}" else: # Treat special cases of swap and rzz gates if isinstance(op.base_gate, (SwapGate, RZZGate)): @@ -527,7 +527,7 @@ def _build_symmetric_gate(self, op, gate_text, wire_list, col): ) self._latex[wire_last][col] = "\\control \\qw" # Put side text to the right between bottom wire in wire_list and the one above it - self._latex[wire_max - 1][col + 1] = "\\dstick{\\hspace{2.0em}%s} \\qw" % gate_text + self._latex[wire_max - 1][col + 1] = f"\\dstick{{\\hspace{{2.0em}}{gate_text}}} \\qw" return 4 # num_cols for side text gates def _build_measure(self, node, col): @@ -544,11 +544,9 @@ def _build_measure(self, node, col): idx_str = str(self._circuit.find_bit(node.cargs[0]).registers[0][1]) else: wire2 = self._wire_map[node.cargs[0]] - - self._latex[wire2][col] = "\\dstick{_{_{\\hspace{%sem}%s}}} \\cw \\ar @{<=} [-%s,0]" % ( - cond_offset, - idx_str, - str(wire2 - wire1), + self._latex[wire2][col] = ( + f"\\dstick{{_{{_{{\\hspace{{{cond_offset}em}}{idx_str}}}}}}} " + f"\\cw \\ar @{{<=}} [-{str(wire2 - wire1)},0]" ) else: wire2 = self._wire_map[node.cargs[0]] @@ -573,7 +571,7 @@ def _build_barrier(self, node, col): if node.op.label is not None: pos = indexes[0] label = node.op.label.replace(" ", "\\,") - self._latex[pos][col] = "\\cds{0}{^{\\mathrm{%s}}}" % label + self._latex[pos][col] = f"\\cds{{0}}{{^{{\\mathrm{{{label}}}}}}}" def _add_controls(self, wire_list, ctrlqargs, ctrl_state, col): """Add one or more controls to a gate""" @@ -615,11 +613,10 @@ def _add_condition(self, op, wire_list, col): ) gap = cwire - max(wire_list) control = "\\control" if op.condition[1] else "\\controlo" - self._latex[cwire][col] = f"{control}" + " \\cw^(%s){^{\\mathtt{%s}}} \\cwx[-%s]" % ( - meas_offset, - label, - str(gap), - ) + self._latex[cwire][ + col + ] = f"{control} \\cw^({meas_offset}){{^{{\\mathtt{{{label}}}}}}} \\cwx[-{str(gap)}]" + # If condition is a register and cregbundle is false else: # First sort the val_bits in the order of the register bits in the circuit diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 0076073fb8e4..9c4fa25309fc 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -371,7 +371,7 @@ def draw(self, filename=None, verbose=False): # Once the scaling factor has been determined, the global phase, register names # and numbers, wires, and gates are drawn if self._global_phase: - plt_mod.text(xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl")) + plt_mod.text(xl, yt, f"Global Phase: {pi_check(self._global_phase, output='mpl')}") self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data) self._draw_ops( self._nodes, diff --git a/qiskit/visualization/circuit/text.py b/qiskit/visualization/circuit/text.py index bec1ccf4a3ee..e9b7aa819e9b 100644 --- a/qiskit/visualization/circuit/text.py +++ b/qiskit/visualization/circuit/text.py @@ -759,7 +759,7 @@ def _repr_html_(self): "background: #fff0;" "line-height: 1.1;" 'font-family: "Courier New",Courier,monospace">' - "%s" % self.single_string() + f"{self.single_string()}" ) def __repr__(self): @@ -780,8 +780,9 @@ def single_string(self): ) except (UnicodeEncodeError, UnicodeDecodeError): warn( - "The encoding %s has a limited charset. Consider a different encoding in your " - "environment. UTF-8 is being used instead" % self.encoding, + f"The encoding {self.encoding} has a limited charset." + " Consider a different encoding in your " + "environment. UTF-8 is being used instead", RuntimeWarning, ) self.encoding = "utf-8" @@ -861,7 +862,7 @@ def lines(self, line_length=None): lines = [] if self.global_phase: - lines.append("global phase: %s" % pi_check(self.global_phase, ndigits=5)) + lines.append(f"global phase: {pi_check(self.global_phase, ndigits=5)}") for layer_group in layer_groups: wires = list(zip(*layer_group)) @@ -1168,7 +1169,7 @@ def add_connected_gate(node, gates, layer, current_cons, gate_wire_map): elif isinstance(op, RZZGate): # rzz - connection_label = "ZZ%s" % params + connection_label = f"ZZ{params}" gates = [Bullet(conditional=conditional), Bullet(conditional=conditional)] add_connected_gate(node, gates, layer, current_cons, gate_wire_map) @@ -1211,7 +1212,7 @@ def add_connected_gate(node, gates, layer, current_cons, gate_wire_map): add_connected_gate(node, gates, layer, current_cons, gate_wire_map) elif base_gate.name == "rzz": # crzz - connection_label = "ZZ%s" % params + connection_label = f"ZZ{params}" gates += [Bullet(conditional=conditional), Bullet(conditional=conditional)] elif len(rest) > 1: top_connect = "┴" if controlled_top else None diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index 73b9c30f6dc8..ad2fca6e9bcf 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -152,7 +152,7 @@ def node_attr_func(node): n["fillcolor"] = "lightblue" return n else: - raise VisualizationError("Unrecognized style %s for the dag_drawer." % style) + raise VisualizationError(f"Unrecognized style {style} for the dag_drawer.") edge_attr_func = None @@ -197,7 +197,7 @@ def node_attr_func(node): n["fillcolor"] = "red" return n else: - raise VisualizationError("Invalid style %s" % style) + raise VisualizationError(f"Invalid style {style}") def edge_attr_func(edge): e = {} diff --git a/qiskit/visualization/pulse_v2/core.py b/qiskit/visualization/pulse_v2/core.py index d60f2db030d0..20686f6fb4f6 100644 --- a/qiskit/visualization/pulse_v2/core.py +++ b/qiskit/visualization/pulse_v2/core.py @@ -220,7 +220,7 @@ def load_program( elif isinstance(program, (pulse.Waveform, pulse.SymbolicPulse)): self._waveform_loader(program) else: - raise VisualizationError("Data type %s is not supported." % type(program)) + raise VisualizationError(f"Data type {type(program)} is not supported.") # update time range self.set_time_range(0, program.duration, seconds=False) diff --git a/qiskit/visualization/pulse_v2/generators/frame.py b/qiskit/visualization/pulse_v2/generators/frame.py index 394f4b4aaf84..8b71b8596bb4 100644 --- a/qiskit/visualization/pulse_v2/generators/frame.py +++ b/qiskit/visualization/pulse_v2/generators/frame.py @@ -264,10 +264,9 @@ def gen_raw_operand_values_compact( freq_sci_notation = "0.0" else: abs_freq = np.abs(data.frame.freq) - freq_sci_notation = "{base:.1f}e{exp:d}".format( - base=data.frame.freq / (10 ** int(np.floor(np.log10(abs_freq)))), - exp=int(np.floor(np.log10(abs_freq))), - ) + base = data.frame.freq / (10 ** int(np.floor(np.log10(abs_freq)))) + exponent = int(np.floor(np.log10(abs_freq))) + freq_sci_notation = f"{base:.1f}e{exponent:d}" frame_info = f"{data.frame.phase:.2f}\n{freq_sci_notation}" text = drawings.TextData( diff --git a/qiskit/visualization/pulse_v2/generators/waveform.py b/qiskit/visualization/pulse_v2/generators/waveform.py index b0d90b895c7a..e770f271c454 100644 --- a/qiskit/visualization/pulse_v2/generators/waveform.py +++ b/qiskit/visualization/pulse_v2/generators/waveform.py @@ -203,11 +203,10 @@ def gen_ibmq_latex_waveform_name( if frac.numerator == 1: angle = rf"\pi/{frac.denominator:d}" else: - angle = r"{num:d}/{denom:d} \pi".format( - num=frac.numerator, denom=frac.denominator - ) + angle = rf"{frac.numerator:d}/{frac.denominator:d} \pi" else: # single qubit pulse + # pylint: disable-next=consider-using-f-string op_name = r"{{\rm {}}}".format(match_dict["op"]) angle_val = match_dict["angle"] if angle_val is None: @@ -217,9 +216,7 @@ def gen_ibmq_latex_waveform_name( if frac.numerator == 1: angle = rf"\pi/{frac.denominator:d}" else: - angle = r"{num:d}/{denom:d} \pi".format( - num=frac.numerator, denom=frac.denominator - ) + angle = rf"{frac.numerator:d}/{frac.denominator:d} \pi" latex_name = rf"{op_name}({sign}{angle})" else: latex_name = None @@ -490,7 +487,7 @@ def _draw_opaque_waveform( fill_objs.append(box_obj) # parameter name - func_repr = "{func}({params})".format(func=pulse_shape, params=", ".join(pnames)) + func_repr = f"{pulse_shape}({', '.join(pnames)})" text_style = { "zorder": formatter["layer.annotate"], @@ -630,8 +627,7 @@ def _parse_waveform( meta.update(acq_data) else: raise VisualizationError( - "Unsupported instruction {inst} by " - "filled envelope.".format(inst=inst.__class__.__name__) + f"Unsupported instruction {inst.__class__.__name__} by " "filled envelope." ) meta.update( diff --git a/qiskit/visualization/pulse_v2/layouts.py b/qiskit/visualization/pulse_v2/layouts.py index 6b39dceaf567..13b42e394e94 100644 --- a/qiskit/visualization/pulse_v2/layouts.py +++ b/qiskit/visualization/pulse_v2/layouts.py @@ -373,11 +373,7 @@ def detail_title(program: Union[pulse.Waveform, pulse.Schedule], device: DrawerB # add program duration dt = device.dt * 1e9 if device.dt else 1.0 - title_str.append( - "Duration: {dur:.1f} {unit}".format( - dur=program.duration * dt, unit="ns" if device.dt else "dt" - ) - ) + title_str.append(f"Duration: {program.duration * dt:.1f} {'ns' if device.dt else 'dt'}") # add device name if device.backend_name != "no-backend": diff --git a/qiskit/visualization/pulse_v2/plotters/matplotlib.py b/qiskit/visualization/pulse_v2/plotters/matplotlib.py index e92a34189994..1788a1254894 100644 --- a/qiskit/visualization/pulse_v2/plotters/matplotlib.py +++ b/qiskit/visualization/pulse_v2/plotters/matplotlib.py @@ -119,8 +119,7 @@ def draw(self): self.ax.add_patch(box) else: raise VisualizationError( - "Data {name} is not supported " - "by {plotter}".format(name=data, plotter=self.__class__.__name__) + f"Data {data} is not supported " f"by {self.__class__.__name__}" ) # axis break for pos in axis_config.axis_break_pos: diff --git a/qiskit/visualization/state_visualization.py b/qiskit/visualization/state_visualization.py index e862b0208e28..0e47a5fe6d72 100644 --- a/qiskit/visualization/state_visualization.py +++ b/qiskit/visualization/state_visualization.py @@ -971,10 +971,10 @@ def plot_state_qsphere( if show_state_phases: element_angle = (np.angle(state[i]) + (np.pi * 4)) % (np.pi * 2) if use_degrees: - element_text += "\n$%.1f^\\circ$" % (element_angle * 180 / np.pi) + element_text += f"\n${element_angle * 180 / np.pi:.1f}^\\circ$" else: element_angle = pi_check(element_angle, ndigits=3).replace("pi", "\\pi") - element_text += "\n$%s$" % (element_angle) + element_text += f"\n${element_angle}$" ax.text( xvalue_text, yvalue_text, @@ -1463,11 +1463,10 @@ def state_drawer(state, output=None, **drawer_args): return draw_func(state, **drawer_args) except KeyError as err: raise ValueError( - """'{}' is not a valid option for drawing {} objects. Please choose from: + f"""'{output}' is not a valid option for drawing {type(state).__name__} + objects. Please choose from: 'text', 'latex', 'latex_source', 'qsphere', 'hinton', - 'bloch', 'city' or 'paulivec'.""".format( - output, type(state).__name__ - ) + 'bloch', 'city' or 'paulivec'.""" ) from err diff --git a/qiskit/visualization/timeline/plotters/matplotlib.py b/qiskit/visualization/timeline/plotters/matplotlib.py index 126d0981feda..daae6fe2558f 100644 --- a/qiskit/visualization/timeline/plotters/matplotlib.py +++ b/qiskit/visualization/timeline/plotters/matplotlib.py @@ -132,8 +132,7 @@ def draw(self): else: raise VisualizationError( - "Data {name} is not supported by {plotter}" - "".format(name=data, plotter=self.__class__.__name__) + f"Data {data} is not supported by {self.__class__.__name__}" ) def _time_bucket_outline( diff --git a/test/benchmarks/circuit_construction.py b/test/benchmarks/circuit_construction.py index 71c079476cd8..6c6c8733d25f 100644 --- a/test/benchmarks/circuit_construction.py +++ b/test/benchmarks/circuit_construction.py @@ -52,7 +52,7 @@ def time_circuit_copy(self, _, __): def build_parameterized_circuit(width, gates, param_count): - params = [Parameter("param-%s" % x) for x in range(param_count)] + params = [Parameter(f"param-{x}") for x in range(param_count)] param_iter = itertools.cycle(params) qr = QuantumRegister(width) diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index 121ad7222f4e..c1ece0230d39 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -167,7 +167,7 @@ def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self): my_gate_inst2_id = id(circuit.data[-1].operation) circuit.append(my_gate_inst3, [qr[0]]) my_gate_inst3_id = id(circuit.data[-1].operation) - + # pylint: disable-next=consider-using-f-string expected_qasm = """OPENQASM 2.0; include "qelib1.inc"; gate my_gate q0 {{ h q0; }} diff --git a/test/python/circuit/test_circuit_registers.py b/test/python/circuit/test_circuit_registers.py index 3de2a451877b..7ef8751da0eb 100644 --- a/test/python/circuit/test_circuit_registers.py +++ b/test/python/circuit/test_circuit_registers.py @@ -97,7 +97,7 @@ def test_numpy_array_of_registers(self): """Test numpy array of Registers . See https://github.com/Qiskit/qiskit-terra/issues/1898 """ - qrs = [QuantumRegister(2, name="q%s" % i) for i in range(5)] + qrs = [QuantumRegister(2, name=f"q{i}") for i in range(5)] qreg_array = np.array([], dtype=object, ndmin=1) qreg_array = np.append(qreg_array, qrs) diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index 4ac69278fd44..dbda9262f15b 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -423,17 +423,15 @@ def test_repr_of_instructions(self): ins1 = Instruction("test_instruction", 3, 5, [0, 1, 2, 3]) self.assertEqual( repr(ins1), - "Instruction(name='{}', num_qubits={}, num_clbits={}, params={})".format( - ins1.name, ins1.num_qubits, ins1.num_clbits, ins1.params - ), + f"Instruction(name='{ins1.name}', num_qubits={ins1.num_qubits}, " + f"num_clbits={ins1.num_clbits}, params={ins1.params})", ) ins2 = random_circuit(num_qubits=4, depth=4, measure=True).to_instruction() self.assertEqual( repr(ins2), - "Instruction(name='{}', num_qubits={}, num_clbits={}, params={})".format( - ins2.name, ins2.num_qubits, ins2.num_clbits, ins2.params - ), + f"Instruction(name='{ins2.name}', num_qubits={ins2.num_qubits}, " + f"num_clbits={ins2.num_clbits}, params={ins2.params})", ) def test_instruction_condition_bits(self): diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index fd63057cff8d..feab3002f582 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -58,8 +58,8 @@ def raise_if_parameter_table_invalid(circuit): if circuit_parameters != table_parameters: raise CircuitError( "Circuit/ParameterTable Parameter mismatch. " - "Circuit parameters: {}. " - "Table parameters: {}.".format(circuit_parameters, table_parameters) + f"Circuit parameters: {circuit_parameters}. " + f"Table parameters: {table_parameters}." ) # Assert parameter locations in table are present in circuit. @@ -75,16 +75,15 @@ def raise_if_parameter_table_invalid(circuit): if not isinstance(instr.params[param_index], ParameterExpression): raise CircuitError( "ParameterTable instruction does not have a " - "ParameterExpression at param_index {}: {}." - "".format(param_index, instr) + f"ParameterExpression at param_index {param_index}: {instr}." ) if parameter not in instr.params[param_index].parameters: raise CircuitError( "ParameterTable instruction parameters does " "not match ParameterTable key. Instruction " - "parameters: {} ParameterTable key: {}." - "".format(instr.params[param_index].parameters, parameter) + f"parameters: {instr.params[param_index].parameters}" + f" ParameterTable key: {parameter}." ) # Assert circuit has no other parameter locations other than those in table. @@ -99,8 +98,8 @@ def raise_if_parameter_table_invalid(circuit): ): raise CircuitError( "Found parameterized instruction not " - "present in table. Instruction: {} " - "param_index: {}".format(instruction.operation, param_index) + f"present in table. Instruction: {instruction.operation} " + f"param_index: {param_index}" ) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 14033e522c62..0fcff29e5b40 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -83,8 +83,8 @@ def raise_if_dagcircuit_invalid(dag): ] if edges_outside_wires: raise DAGCircuitError( - "multi_graph contains one or more edges ({}) " - "not found in DAGCircuit.wires ({}).".format(edges_outside_wires, dag.wires) + f"multi_graph contains one or more edges ({edges_outside_wires}) " + f"not found in DAGCircuit.wires ({dag.wires})." ) # Every wire should have exactly one input node and one output node. @@ -134,9 +134,7 @@ def raise_if_dagcircuit_invalid(dag): all_bits = node_qubits | node_clbits | node_cond_bits assert in_wires == all_bits, f"In-edge wires {in_wires} != node bits {all_bits}" - assert out_wires == all_bits, "Out-edge wires {} != node bits {}".format( - out_wires, all_bits - ) + assert out_wires == all_bits, f"Out-edge wires {out_wires} != node bits {all_bits}" class TestDagRegisters(QiskitTestCase): diff --git a/test/python/providers/test_fake_backends.py b/test/python/providers/test_fake_backends.py index 101e35acc8e1..d5c5507b3b8e 100644 --- a/test/python/providers/test_fake_backends.py +++ b/test/python/providers/test_fake_backends.py @@ -105,7 +105,7 @@ def setUpClass(cls): ) def test_circuit_on_fake_backend_v2(self, backend, optimization_level): if not optionals.HAS_AER and backend.num_qubits > 20: - self.skipTest("Unable to run fake_backend %s without qiskit-aer" % backend.name) + self.skipTest(f"Unable to run fake_backend {backend.name} without qiskit-aer") job = backend.run( transpile( self.circuit, backend, seed_transpiler=42, optimization_level=optimization_level @@ -126,8 +126,7 @@ def test_circuit_on_fake_backend_v2(self, backend, optimization_level): def test_circuit_on_fake_backend(self, backend, optimization_level): if not optionals.HAS_AER and backend.configuration().num_qubits > 20: self.skipTest( - "Unable to run fake_backend %s without qiskit-aer" - % backend.configuration().backend_name + f"Unable to run fake_backend {backend.configuration().backend_name} without qiskit-aer" ) job = backend.run( transpile( @@ -202,7 +201,7 @@ def test_defaults_to_dict(self, backend): self.assertGreater(i, 1e6) self.assertGreater(i, 1e6) else: - self.skipTest("Backend %s does not have defaults" % backend) + self.skipTest(f"Backend {backend} does not have defaults") def test_delay_circuit(self): backend = Fake27QPulseV1() diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 3585efb9f646..043a9eca78b6 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -150,7 +150,7 @@ def test_append_1_qubit_gate(self): "sx", "sxdg", ): - with self.subTest(msg="append gate %s" % gate_name): + with self.subTest(msg=f"append gate {gate_name}"): cliff = Clifford([[1, 0], [0, 1]]) cliff = _append_operation(cliff, gate_name, [0]) value_table = cliff.tableau[:, :-1] @@ -170,7 +170,7 @@ def test_1_qubit_identity_relations(self): """Tests identity relations for 1-qubit gates""" for gate_name in ("x", "y", "z", "h"): - with self.subTest(msg="identity for gate %s" % gate_name): + with self.subTest(msg=f"identity for gate {gate_name}"): cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() cliff = _append_operation(cliff, gate_name, [0]) @@ -181,7 +181,7 @@ def test_1_qubit_identity_relations(self): inv_gates = ["sdg", "sinv", "w"] for gate_name, inv_gate in zip(gates, inv_gates): - with self.subTest(msg="identity for gate %s" % gate_name): + with self.subTest(msg=f"identity for gate {gate_name}"): cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() cliff = _append_operation(cliff, gate_name, [0]) @@ -203,7 +203,7 @@ def test_1_qubit_mult_relations(self): ] for rel in rels: - with self.subTest(msg="relation %s" % rel): + with self.subTest(msg=f"relation {rel}"): split_rel = rel.split() cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() @@ -227,7 +227,7 @@ def test_1_qubit_conj_relations(self): ] for rel in rels: - with self.subTest(msg="relation %s" % rel): + with self.subTest(msg=f"relation {rel}"): split_rel = rel.split() cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() diff --git a/test/python/quantum_info/states/test_densitymatrix.py b/test/python/quantum_info/states/test_densitymatrix.py index 4f5c728604b5..cf6ad3c35090 100644 --- a/test/python/quantum_info/states/test_densitymatrix.py +++ b/test/python/quantum_info/states/test_densitymatrix.py @@ -398,7 +398,7 @@ def test_to_dict(self): target = {} for i in range(2): for j in range(3): - key = "{1}{0}|{1}{0}".format(i, j) + key = f"{j}{i}|{j}{i}" target[key] = 2 * j + i + 1 self.assertDictAlmostEqual(target, rho.to_dict()) @@ -407,7 +407,7 @@ def test_to_dict(self): target = {} for i in range(2): for j in range(11): - key = "{1},{0}|{1},{0}".format(i, j) + key = f"{j},{i}|{j},{i}" target[key] = 2 * j + i + 1 self.assertDictAlmostEqual(target, vec.to_dict()) diff --git a/test/python/result/test_mitigators.py b/test/python/result/test_mitigators.py index d290fc8ed486..66662bb587e1 100644 --- a/test/python/result/test_mitigators.py +++ b/test/python/result/test_mitigators.py @@ -140,22 +140,16 @@ def test_mitigation_improvement(self): self.assertLess( mitigated_error, unmitigated_error * 0.8, - "Mitigator {} did not improve circuit {} measurements".format( - mitigator, circuit_name - ), + f"Mitigator {mitigator} did not improve circuit {circuit_name} measurements", ) mitigated_stddev_upper_bound = mitigated_quasi_probs._stddev_upper_bound max_unmitigated_stddev = max(unmitigated_stddev.values()) self.assertGreaterEqual( mitigated_stddev_upper_bound, max_unmitigated_stddev, - "Mitigator {} on circuit {} gave stddev upper bound {} " - "while unmitigated stddev maximum is {}".format( - mitigator, - circuit_name, - mitigated_stddev_upper_bound, - max_unmitigated_stddev, - ), + f"Mitigator {mitigator} on circuit {circuit_name} gave stddev upper bound " + f"{mitigated_stddev_upper_bound} while unmitigated stddev maximum is " + f"{max_unmitigated_stddev}", ) def test_expectation_improvement(self): @@ -190,22 +184,15 @@ def test_expectation_improvement(self): self.assertLess( mitigated_error, unmitigated_error, - "Mitigator {} did not improve circuit {} expectation computation for diagonal {} " - "ideal: {}, unmitigated: {} mitigated: {}".format( - mitigator, - circuit_name, - diagonal, - ideal_expectation, - unmitigated_expectation, - mitigated_expectation, - ), + f"Mitigator {mitigator} did not improve circuit {circuit_name} expectation " + f"computation for diagonal {diagonal} ideal: {ideal_expectation}, unmitigated:" + f" {unmitigated_expectation} mitigated: {mitigated_expectation}", ) self.assertGreaterEqual( mitigated_stddev, unmitigated_stddev, - "Mitigator {} did not increase circuit {} the standard deviation".format( - mitigator, circuit_name - ), + f"Mitigator {mitigator} did not increase circuit {circuit_name} the" + f" standard deviation", ) def test_clbits_parameter(self): @@ -228,7 +215,7 @@ def test_clbits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly marganalize for qubits 1,2".format(mitigator), + f"Mitigator {mitigator} did not correctly marganalize for qubits 1,2", ) mitigated_probs_02 = ( @@ -240,7 +227,7 @@ def test_clbits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly marganalize for qubits 0,2".format(mitigator), + f"Mitigator {mitigator} did not correctly marganalize for qubits 0,2", ) def test_qubits_parameter(self): @@ -264,7 +251,7 @@ def test_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 0, 1, 2".format(mitigator), + f"Mitigator {mitigator} did not correctly handle qubit order 0, 1, 2", ) mitigated_probs_210 = ( @@ -276,7 +263,7 @@ def test_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 2, 1, 0".format(mitigator), + f"Mitigator {mitigator} did not correctly handle qubit order 2, 1, 0", ) mitigated_probs_102 = ( @@ -288,7 +275,7 @@ def test_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 1, 0, 2".format(mitigator), + "Mitigator {mitigator} did not correctly handle qubit order 1, 0, 2", ) def test_repeated_qubits_parameter(self): @@ -311,7 +298,7 @@ def test_repeated_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 2,1,0".format(mitigator), + f"Mitigator {mitigator} did not correctly handle qubit order 2,1,0", ) # checking qubit order 2,1,0 should not "overwrite" the default 0,1,2 @@ -324,9 +311,8 @@ def test_repeated_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 0,1,2 (the expected default)".format( - mitigator - ), + f"Mitigator {mitigator} did not correctly handle qubit order 0,1,2 " + f"(the expected default)", ) def test_qubits_subset_parameter(self): @@ -350,7 +336,7 @@ def test_qubits_subset_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit subset".format(mitigator), + "Mitigator {mitigator} did not correctly handle qubit subset", ) mitigated_probs_6 = ( @@ -362,7 +348,7 @@ def test_qubits_subset_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit subset".format(mitigator), + f"Mitigator {mitigator} did not correctly handle qubit subset", ) diagonal = str2diag("ZZ") ideal_expectation = 0 @@ -373,7 +359,7 @@ def test_qubits_subset_parameter(self): self.assertLess( mitigated_error, 0.1, - "Mitigator {} did not improve circuit expectation".format(mitigator), + f"Mitigator {mitigator} did not improve circuit expectation", ) def test_from_backend(self): diff --git a/test/python/synthesis/aqc/fast_gradient/test_layer1q.py b/test/python/synthesis/aqc/fast_gradient/test_layer1q.py index 43b164c42257..d2c5108391ff 100644 --- a/test/python/synthesis/aqc/fast_gradient/test_layer1q.py +++ b/test/python/synthesis/aqc/fast_gradient/test_layer1q.py @@ -62,7 +62,7 @@ def test_layer1q_matrix(self): # T == P^t @ G @ P. err = tut.relative_error(t_mat, iden[perm].T @ g_mat @ iden[perm]) - self.assertLess(err, eps, "err = {:0.16f}".format(err)) + self.assertLess(err, eps, f"err = {err:0.16f}") max_rel_err = max(max_rel_err, err) # Multiplication by permutation matrix of the left can be @@ -79,8 +79,7 @@ def test_layer1q_matrix(self): self.assertTrue( err1 < eps and err2 < eps and err3 < eps and err4 < eps, - "err1 = {:f}, err2 = {:f}, " - "err3 = {:f}, err4 = {:f}".format(err1, err2, err3, err4), + f"err1 = {err1:f}, err2 = {err2:f}, " f"err3 = {err3:f}, err4 = {err4:f}", ) max_rel_err = max(max_rel_err, err1, err2, err3, err4) @@ -128,12 +127,12 @@ def test_pmatrix_class(self): alt_ttmtt = pmat.finalize(temp_mat=tmp1) err1 = tut.relative_error(alt_ttmtt, ttmtt) - self.assertLess(err1, _eps, "relative error: {:f}".format(err1)) + self.assertLess(err1, _eps, f"relative error: {err1:f}") prod = np.complex128(np.trace(ttmtt @ t4)) alt_prod = pmat.product_q1(layer=c4, tmp1=tmp1, tmp2=tmp2) err2 = abs(alt_prod - prod) / abs(prod) - self.assertLess(err2, _eps, "relative error: {:f}".format(err2)) + self.assertLess(err2, _eps, f"relative error: {err2:f}") max_rel_err = max(max_rel_err, err1, err2) diff --git a/test/python/synthesis/aqc/fast_gradient/test_layer2q.py b/test/python/synthesis/aqc/fast_gradient/test_layer2q.py index 9de1e13df2d0..8f5655d6057e 100644 --- a/test/python/synthesis/aqc/fast_gradient/test_layer2q.py +++ b/test/python/synthesis/aqc/fast_gradient/test_layer2q.py @@ -65,7 +65,7 @@ def test_layer2q_matrix(self): # T == P^t @ G @ P. err = tut.relative_error(t_mat, iden[perm].T @ g_mat @ iden[perm]) - self.assertLess(err, _eps, "err = {:0.16f}".format(err)) + self.assertLess(err, _eps, f"err = {err:0.16f}") max_rel_err = max(max_rel_err, err) # Multiplication by permutation matrix of the left can be @@ -82,8 +82,8 @@ def test_layer2q_matrix(self): self.assertTrue( err1 < _eps and err2 < _eps and err3 < _eps and err4 < _eps, - "err1 = {:f}, err2 = {:f}, " - "err3 = {:f}, err4 = {:f}".format(err1, err2, err3, err4), + f"err1 = {err1:f}, err2 = {err2:f}, " + f"err3 = {err3:f}, err4 = {err4:f}", ) max_rel_err = max(max_rel_err, err1, err2, err3, err4) @@ -136,12 +136,12 @@ def test_pmatrix_class(self): alt_ttmtt = pmat.finalize(temp_mat=tmp1) err1 = tut.relative_error(alt_ttmtt, ttmtt) - self.assertLess(err1, _eps, "relative error: {:f}".format(err1)) + self.assertLess(err1, _eps, f"relative error: {err1:f}") prod = np.complex128(np.trace(ttmtt @ t4)) alt_prod = pmat.product_q2(layer=c4, tmp1=tmp1, tmp2=tmp2) err2 = abs(alt_prod - prod) / abs(prod) - self.assertLess(err2, _eps, "relative error: {:f}".format(err2)) + self.assertLess(err2, _eps, f"relative error: {err2:f}") max_rel_err = max(max_rel_err, err1, err2) diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py index a879d5251f90..050df5a3fe1c 100644 --- a/test/python/synthesis/test_permutation_synthesis.py +++ b/test/python/synthesis/test_permutation_synthesis.py @@ -78,17 +78,13 @@ def test_invalid_permutations(self, width): pattern_out_of_range[0] = width with self.assertRaises(ValueError) as exc: _validate_permutation(pattern_out_of_range) - self.assertIn( - "input has length {0} and contains {0}".format(width), str(exc.exception) - ) + self.assertIn(f"input has length {width} and contains {width}", str(exc.exception)) pattern_duplicate = np.copy(pattern) pattern_duplicate[-1] = pattern[0] with self.assertRaises(ValueError) as exc: _validate_permutation(pattern_duplicate) - self.assertIn( - "input contains {} more than once".format(pattern[0]), str(exc.exception) - ) + self.assertIn(f"input contains {pattern[0]} more than once", str(exc.exception)) @data(4, 5, 10, 15, 20) def test_synth_permutation_basic(self, width): diff --git a/test/python/test_user_config.py b/test/python/test_user_config.py index ecc4ffaaa966..5b63462963c2 100644 --- a/test/python/test_user_config.py +++ b/test/python/test_user_config.py @@ -25,7 +25,7 @@ class TestUserConfig(QiskitTestCase): def setUp(self): super().setUp() - self.file_path = "test_%s.conf" % uuid4() + self.file_path = f"test_{uuid4()}.conf" def test_empty_file_read(self): config = user_config.UserConfig(self.file_path) diff --git a/test/python/transpiler/test_pass_scheduler.py b/test/python/transpiler/test_pass_scheduler.py index 6d6026d6148e..12ba81c78a6c 100644 --- a/test/python/transpiler/test_pass_scheduler.py +++ b/test/python/transpiler/test_pass_scheduler.py @@ -703,7 +703,7 @@ def assertPassLog(self, passmanager, list_of_passes): output_lines = self.output.readlines() pass_log_lines = [x for x in output_lines if x.startswith("Pass:")] for index, pass_name in enumerate(list_of_passes): - self.assertTrue(pass_log_lines[index].startswith("Pass: %s -" % pass_name)) + self.assertTrue(pass_log_lines[index].startswith(f"Pass: {pass_name} -")) def test_passes(self): """Dump passes in different FlowControllerLinear""" diff --git a/test/python/visualization/timeline/test_generators.py b/test/python/visualization/timeline/test_generators.py index 66afe3556b3d..5554248089ed 100644 --- a/test/python/visualization/timeline/test_generators.py +++ b/test/python/visualization/timeline/test_generators.py @@ -109,9 +109,7 @@ def test_gen_full_gate_name_with_finite_duration(self): self.assertListEqual(list(drawing_obj.yvals), [0.0]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "u3(0.00, 0.00, 0.00)[20]") - ref_latex = "{name}(0.00, 0.00, 0.00)[20]".format( - name=self.formatter["latex_symbol.gates"]["u3"] - ) + ref_latex = f"{self.formatter['latex_symbol.gates']['u3']}(0.00, 0.00, 0.00)[20]" self.assertEqual(drawing_obj.latex, ref_latex) ref_styles = { @@ -132,7 +130,7 @@ def test_gen_full_gate_name_with_zero_duration(self): self.assertListEqual(list(drawing_obj.yvals), [self.formatter["label_offset.frame_change"]]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "u1(0.00)") - ref_latex = "{name}(0.00)".format(name=self.formatter["latex_symbol.gates"]["u1"]) + ref_latex = f"{self.formatter['latex_symbol.gates']['u1']}(0.00)" self.assertEqual(drawing_obj.latex, ref_latex) ref_styles = { @@ -159,7 +157,7 @@ def test_gen_short_gate_name_with_finite_duration(self): self.assertListEqual(list(drawing_obj.yvals), [0.0]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "u3") - ref_latex = "{name}".format(name=self.formatter["latex_symbol.gates"]["u3"]) + ref_latex = f"{self.formatter['latex_symbol.gates']['u3']}" self.assertEqual(drawing_obj.latex, ref_latex) ref_styles = { @@ -180,7 +178,7 @@ def test_gen_short_gate_name_with_zero_duration(self): self.assertListEqual(list(drawing_obj.yvals), [self.formatter["label_offset.frame_change"]]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "u1") - ref_latex = "{name}".format(name=self.formatter["latex_symbol.gates"]["u1"]) + ref_latex = f"{self.formatter['latex_symbol.gates']['u1']}" self.assertEqual(drawing_obj.latex, ref_latex) ref_styles = { @@ -250,6 +248,7 @@ def test_gen_bit_name(self): self.assertListEqual(list(drawing_obj.yvals), [0]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "bar") + # pylint: disable-next=consider-using-f-string ref_latex = r"{{\rm {register}}}_{{{index}}}".format(register="q", index="0") self.assertEqual(drawing_obj.latex, ref_latex) diff --git a/test/randomized/test_transpiler_equivalence.py b/test/randomized/test_transpiler_equivalence.py index 2dde71a5d39d..04dced90dfaa 100644 --- a/test/randomized/test_transpiler_equivalence.py +++ b/test/randomized/test_transpiler_equivalence.py @@ -306,10 +306,9 @@ def equivalent_transpile(self, kwargs): count_differences = dicts_almost_equal(aer_counts, xpiled_aer_counts, 0.05 * shots) - assert ( - count_differences == "" - ), "Counts not equivalent: {}\nFailing QASM Input:\n{}\n\nFailing QASM Output:\n{}".format( - count_differences, qasm2.dumps(self.qc), qasm2.dumps(xpiled_qc) + assert count_differences == "", ( + f"Counts not equivalent: {count_differences}\nFailing QASM Input:\n" + f"{qasm2.dumps(self.qc)}\n\nFailing QASM Output:\n{qasm2.dumps(xpiled_qc)}" ) diff --git a/test/utils/base.py b/test/utils/base.py index 747be7f66b51..63a8bf4384f0 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -81,10 +81,10 @@ def setUp(self): self.addTypeEqualityFunc(QuantumCircuit, self.assertQuantumCircuitEqual) if self.__setup_called: raise ValueError( - "In File: %s\n" + f"In File: {(sys.modules[self.__class__.__module__].__file__,)}\n" "TestCase.setUp was already called. Do not explicitly call " "setUp from your tests. In your own setUp, use super to call " - "the base setUp." % (sys.modules[self.__class__.__module__].__file__,) + "the base setUp." ) self.__setup_called = True @@ -92,10 +92,10 @@ def tearDown(self): super().tearDown() if self.__teardown_called: raise ValueError( - "In File: %s\n" + f"In File: {(sys.modules[self.__class__.__module__].__file__,)}\n" "TestCase.tearDown was already called. Do not explicitly call " "tearDown from your tests. In your own tearDown, use super to " - "call the base tearDown." % (sys.modules[self.__class__.__module__].__file__,) + "call the base tearDown." ) self.__teardown_called = True @@ -305,10 +305,10 @@ def valid_comparison(value): if places is not None: if delta is not None: raise TypeError("specify delta or places not both") - msg_suffix = " within %s places" % places + msg_suffix = f" within {places} places" else: delta = delta or 1e-8 - msg_suffix = " within %s delta" % delta + msg_suffix = f" within {delta} delta" # Compare all keys in both dicts, populating error_msg. error_msg = "" diff --git a/test/visual/results.py b/test/visual/results.py index efaf09bbbbfa..76fde794b394 100644 --- a/test/visual/results.py +++ b/test/visual/results.py @@ -83,30 +83,30 @@ def _new_gray(size, color): @staticmethod def passed_result_html(result, reference, diff, title): """Creates the html for passing tests""" - ret = '
%s ' % title + ret = f'
{title} ' ret += "" - ret += '
' % result - ret += '' % reference - ret += '' % diff + ret += f'
' + ret += f'' + ret += f'' ret += "
" return ret @staticmethod def failed_result_html(result, reference, diff, title): """Creates the html for failing tests""" - ret = '
%s ' % title + ret = f'
{title} ' ret += "" - ret += '
' % result - ret += '' % reference - ret += '' % diff + ret += f'
' + ret += f'' + ret += f'' ret += "
" return ret @staticmethod def no_reference_html(result, title): """Creates the html for missing-reference tests""" - ret = '
%s ' % title - ret += '" % (name, fullpath_name, fullpath_reference) + f'Download this image' + f" to {fullpath_reference}" + " and add/push to the repo" ) ret += Results.no_reference_html(fullpath_name, title) ret += "" diff --git a/tools/build_standard_commutations.py b/tools/build_standard_commutations.py index 56c452b11ce0..0e1fcdf1797d 100644 --- a/tools/build_standard_commutations.py +++ b/tools/build_standard_commutations.py @@ -143,12 +143,14 @@ def _dump_commuting_dict_as_python( dir_str = "standard_gates_commutations = {\n" for k, v in commutations.items(): if not isinstance(v, dict): + # pylint: disable-next=consider-using-f-string dir_str += ' ("{}", "{}"): {},\n'.format(*k, v) else: + # pylint: disable-next=consider-using-f-string dir_str += ' ("{}", "{}"): {{\n'.format(*k) for entry_key, entry_val in v.items(): - dir_str += " {}: {},\n".format(entry_key, entry_val) + dir_str += f" {entry_key}: {entry_val},\n" dir_str += " },\n" dir_str += "}\n" diff --git a/tools/find_stray_release_notes.py b/tools/find_stray_release_notes.py index 7e04f5ecc320..d694e0d89b8f 100755 --- a/tools/find_stray_release_notes.py +++ b/tools/find_stray_release_notes.py @@ -49,7 +49,7 @@ def _main(): failed_files = [x for x in res if x is not None] if len(failed_files) > 0: for failed_file in failed_files: - sys.stderr.write("%s is not in the correct location.\n" % failed_file) + sys.stderr.write(f"{failed_file} is not in the correct location.\n") sys.exit(1) sys.exit(0) diff --git a/tools/verify_headers.py b/tools/verify_headers.py index 7bd7d2bad4e7..552372b7725a 100755 --- a/tools/verify_headers.py +++ b/tools/verify_headers.py @@ -88,18 +88,18 @@ def validate_header(file_path): break if file_path.endswith(".rs"): if "".join(lines[start : start + 2]) != header_rs: - return (file_path, False, "Header up to copyright line does not match: %s" % header) + return (file_path, False, f"Header up to copyright line does not match: {header}") if not copyright_line.search(lines[start + 2]): return (file_path, False, "Header copyright line not found") if "".join(lines[start + 3 : start + 11]) != apache_text_rs: - return (file_path, False, "Header apache text string doesn't match:\n %s" % apache_text) + return (file_path, False, f"Header apache text string doesn't match:\n {apache_text}") else: if "".join(lines[start : start + 2]) != header: - return (file_path, False, "Header up to copyright line does not match: %s" % header) + return (file_path, False, f"Header up to copyright line does not match: {header}") if not copyright_line.search(lines[start + 2]): return (file_path, False, "Header copyright line not found") if "".join(lines[start + 3 : start + 11]) != apache_text: - return (file_path, False, "Header apache text string doesn't match:\n %s" % apache_text) + return (file_path, False, f"Header apache text string doesn't match:\n {apache_text}") return (file_path, True, None) @@ -122,8 +122,8 @@ def _main(): failed_files = [x for x in res if x[1] is False] if len(failed_files) > 0: for failed_file in failed_files: - sys.stderr.write("%s failed header check because:\n" % failed_file[0]) - sys.stderr.write("%s\n\n" % failed_file[2]) + sys.stderr.write(f"{failed_file[0]} failed header check because:\n") + sys.stderr.write(f"{failed_file[2]}\n\n") sys.exit(1) sys.exit(0) From 0f513577b31c984520c8598a84b39149513ed115 Mon Sep 17 00:00:00 2001 From: Luis J Camargo Date: Wed, 19 Jun 2024 09:50:03 -0600 Subject: [PATCH 04/89] Spellcheck Done [Unitary Hack 2024] (#12501) * spell check iter1 * spell check iter 2 * Fix fmt * Update qiskit/_numpy_compat.py * Update qiskit/synthesis/evolution/product_formula.py * Update qiskit/synthesis/evolution/product_formula.py * Update releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml * undo some corrections --------- Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Co-authored-by: Luciano Bello Co-authored-by: Julien Gacon --- .binder/postBuild | 2 +- .github/workflows/backport.yml | 2 +- CONTRIBUTING.md | 4 +-- crates/README.md | 8 +++--- crates/accelerate/src/pauli_exp_val.rs | 8 +++--- crates/accelerate/src/sabre/sabre_dag.rs | 2 +- crates/accelerate/src/sparse_pauli_op.rs | 8 +++--- crates/accelerate/src/two_qubit_decompose.rs | 4 +-- crates/qasm2/src/expr.rs | 6 ++-- crates/qasm2/src/lex.rs | 6 ++-- crates/qasm2/src/parse.rs | 2 +- crates/qasm3/src/build.rs | 4 +-- crates/qasm3/src/circuit.rs | 2 +- crates/qasm3/src/expr.rs | 2 +- docs/conf.py | 4 +-- qiskit/_numpy_compat.py | 2 +- qiskit/circuit/__init__.py | 2 +- qiskit/circuit/_classical_resource_map.py | 10 +++---- qiskit/circuit/classical/expr/expr.py | 8 +++--- qiskit/circuit/classical/expr/visitors.py | 2 +- qiskit/circuit/classical/types/__init__.py | 2 +- qiskit/circuit/classical/types/types.py | 4 +-- qiskit/circuit/controlflow/_builder_utils.py | 2 +- qiskit/circuit/controlflow/builder.py | 8 +++--- qiskit/circuit/controlflow/if_else.py | 4 +-- qiskit/circuit/controlflow/switch_case.py | 2 +- qiskit/circuit/instruction.py | 6 ++-- .../arithmetic/linear_amplitude_function.py | 2 +- qiskit/circuit/library/n_local/n_local.py | 4 +-- qiskit/circuit/library/standard_gates/u.py | 2 +- qiskit/circuit/library/standard_gates/x.py | 2 +- qiskit/circuit/parameter.py | 2 +- qiskit/circuit/parameterexpression.py | 6 ++-- qiskit/circuit/quantumcircuit.py | 28 +++++++++---------- qiskit/circuit/random/utils.py | 7 +++-- qiskit/circuit/singleton.py | 6 ++-- qiskit/converters/circuit_to_instruction.py | 2 +- qiskit/dagcircuit/dagcircuit.py | 4 +-- qiskit/passmanager/passmanager.py | 4 +-- qiskit/primitives/base/base_estimator.py | 2 +- .../primitives/containers/bindings_array.py | 4 +-- qiskit/providers/backend.py | 2 +- qiskit/providers/options.py | 2 +- qiskit/qasm2/__init__.py | 14 +++++----- qiskit/qasm2/export.py | 2 +- qiskit/qasm2/parse.py | 2 +- qiskit/qasm3/ast.py | 2 +- qiskit/qasm3/exporter.py | 6 ++-- qiskit/qobj/converters/pulse_instruction.py | 4 +-- qiskit/qpy/binary_io/schedules.py | 2 +- qiskit/qpy/type_keys.py | 4 +-- .../operators/channel/transformations.py | 2 +- .../operators/dihedral/dihedral.py | 4 +-- qiskit/quantum_info/operators/measures.py | 2 +- .../operators/symplectic/clifford.py | 2 +- .../operators/symplectic/pauli_list.py | 2 +- .../operators/symplectic/random.py | 2 +- qiskit/quantum_info/states/stabilizerstate.py | 10 +++---- qiskit/result/counts.py | 2 +- .../clifford/clifford_decompose_layers.py | 2 +- .../cnotdihedral_decompose_full.py | 2 +- .../cnotdihedral_decompose_general.py | 2 +- .../discrete_basis/solovay_kitaev.py | 2 +- qiskit/synthesis/linear/linear_depth_lnn.py | 2 +- .../synthesis/linear_phase/cx_cz_depth_lnn.py | 6 ++-- qiskit/synthesis/linear_phase/cz_depth_lnn.py | 2 +- .../stabilizer/stabilizer_decompose.py | 2 +- .../two_qubit/two_qubit_decompose.py | 2 +- .../synthesis/unitary/aqc/cnot_structures.py | 2 +- qiskit/synthesis/unitary/qsd.py | 2 +- .../passes/basis/basis_translator.py | 4 +-- .../transpiler/passes/layout/sabre_layout.py | 4 +-- qiskit/transpiler/passes/layout/vf2_layout.py | 2 +- .../optimization/commutative_cancellation.py | 2 +- .../template_matching/backward_match.py | 2 +- .../template_substitution.py | 2 +- .../passes/routing/star_prerouting.py | 2 +- .../passes/scheduling/alignments/__init__.py | 4 +-- .../passes/scheduling/base_scheduler.py | 2 +- .../passes/scheduling/dynamical_decoupling.py | 2 +- .../passes/scheduling/padding/base_padding.py | 2 +- .../padding/dynamical_decoupling.py | 4 +-- .../passes/synthesis/unitary_synthesis.py | 2 +- .../transpiler/passes/utils/gate_direction.py | 2 +- qiskit/transpiler/passmanager.py | 2 +- qiskit/utils/classtools.py | 6 ++-- qiskit/utils/lazy_tester.py | 4 +-- qiskit/utils/optionals.py | 16 +++++------ qiskit/visualization/circuit/_utils.py | 2 +- qiskit/visualization/pulse_v2/events.py | 2 +- releasenotes/config.yaml | 4 +-- .../0.12/operator-dot-fd90e7e5ad99ff9b.yaml | 2 +- .../0.13/0.13.0-release-a92553cf72c203aa.yaml | 2 +- ...e-job-status-methods-3ab9646c5f5470a6.yaml | 2 +- ...efault-schedule-name-51ba198cf08978cd.yaml | 2 +- .../qinfo-operators-0193871295190bad.yaml | 8 +++--- .../0.13/qinfo-states-7f67e2432cf0c12c.yaml | 2 +- ...sition-visualization-a62d0d119569fa05.yaml | 2 +- .../parameter-conjugate-a16fd7ae0dc18ede.yaml | 2 +- .../delay-in-circuit-33f0d81783ac12ea.yaml | 2 +- ...n-setting-ctrl_state-2f9af3b9f0f7903f.yaml | 2 +- .../remove-dagnode-dict-32fa35479c0a8331.yaml | 2 +- .../add-schedule-block-c37527f3205b7b62.yaml | 2 +- ...asicaer-new-provider-ea7cf756df231c2b.yaml | 2 +- .../deprecate-schemas-424c29fbd35c90de.yaml | 2 +- .../notes/0.17/ecr-gate-45cfda1b84ac792c.yaml | 2 +- ...ircular-entanglement-0acf0195138b6aa2.yaml | 2 +- ...e-time-visualization-b5404ad875cbdae4.yaml | 2 +- .../0.17/issue-5751-1b6249f6263c9c30.yaml | 2 +- ...skit-version-wrapper-90cb7fcffeaafd6a.yaml | 4 +-- ...replace-pulse-drawer-f9f667c8f71e1e02.yaml | 2 +- .../0.18/add-pauli-list-5644d695f91de808.yaml | 2 +- ...nite-job-submissions-d6f6a583535ca798.yaml | 2 +- .../gates-in-basis-pass-337f6637e61919db.yaml | 2 +- ...measure_all-add_bits-8525317935197b90.yaml | 2 +- .../notes/0.19/mpl-bump-33a1240266e66508.yaml | 2 +- ...t-mitigation-classes-2ef175e232d791ae.yaml | 4 +-- ...nual-warning-filters-028646b73bb86860.yaml | 8 +++--- ...parse-pauli-internal-8226b4f57a61b982.yaml | 2 +- .../0.19/vf2layout-4cea88087c355769.yaml | 2 +- ...erances-z2symmetries-9c444a7b1237252e.yaml | 2 +- ...ion-alignment-passes-ef0f20d4f89f95f3.yaml | 8 +++--- ...t-preset-passmanager-db46513a24e79aa9.yaml | 2 +- .../marginal-memory-29d9d6586ae78590.yaml | 2 +- .../vf2-post-layout-f0213e2c7ebb645c.yaml | 2 +- ...plementation-details-09b0ead8b42cacda.yaml | 2 +- ...-entanglement-nlocal-38581e4ffb7a7c68.yaml | 2 +- ...-flow-representation-09520e2838f0657e.yaml | 2 +- ...ate-direction-target-a9f0acd0cf30ed66.yaml | 2 +- .../0.22/primitive-run-5d1afab3655330a6.yaml | 2 +- ...lic-pulse-subclasses-77314a1654521852.yaml | 4 +-- ...steppable-optimizers-9d9b48ba78bd58bb.yaml | 2 +- ...nsored-subset-fitter-bd28e6e6ec5bdaae.yaml | 2 +- ...ation-reorganisation-9e302239705c7842.yaml | 2 +- .../fix-qpy-loose-bits-5283dc4ad3823ce3.yaml | 4 +-- .../notes/0.23/fix_8897-2a90c4b0857c19c2.yaml | 2 +- .../0.23/initial_state-8e20b04fc2ec2f4b.yaml | 2 +- ...ize-1q-decomposition-cb9bb4651607b639.yaml | 2 +- ...l-custom-definitions-a1b839de199ca048.yaml | 4 +-- .../add-hls-plugins-038388970ad43c55.yaml | 2 +- ...-new-symbolic-pulses-4dc46ecaaa1ba928.yaml | 2 +- ...eprecate-bip-mapping-f0025c4c724e1ec8.yaml | 2 +- ...rcuit-data-operation-1b8326b1b089f10c.yaml | 2 +- ...tensoredop-to-matrix-6f22644f1bdb8b41.yaml | 2 +- ...es-for-pulse-scaling-8369eb584c6d8fe1.yaml | 2 +- ...sm2-exporter-rewrite-8993dd24f930b180.yaml | 2 +- .../qasm2-parser-rust-ecf6570e2d445a94.yaml | 2 +- ...ints-list-optimizers-033d7439f86bbb71.yaml | 2 +- ...-propagate-condition-898052b53edb1f17.yaml | 2 +- ...ter-parameter-rebind-3c799e74456469d9.yaml | 2 +- ...-mcrz-relative-phase-6ea81a369f8bda38.yaml | 2 +- .../notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml | 2 +- ...latten-nlocal-family-292b23b99947f3c9.yaml | 2 +- .../normalize-stateprep-e21972dce8695509.yaml | 2 +- .../0.25/qpy-layout-927ab34f2b47f4aa.yaml | 2 +- ...en-swapper-rustworkx-9e02c0ab67a59fe8.yaml | 2 +- .../dag-appenders-check-84d4ef20c1e20fd0.yaml | 2 +- ...deprecate-duplicates-a871f83bbbe1c96f.yaml | 2 +- ...-basis-gatedirection-bdffad3b47c1c532.yaml | 2 +- ...pr-rvalue-conditions-8b5d5f7c015658c0.yaml | 2 +- .../fix-parameter-hash-d22c270090ffc80e.yaml | 4 +-- ...-unscheduled-warning-873f7a24c6b51e2c.yaml | 6 ++-- .../0.45/qasm2-new-api-4e1e4803d6a5a175.yaml | 2 +- .../0.45/singletons-83782de8bd062cbc.yaml | 2 +- .../changes-on-upgrade-6fcd573269a8ebc5.yaml | 2 +- .../new-features-0.9-159645f977a139f7.yaml | 4 +-- ...move-opflow-qi-utils-3debd943c65b17da.yaml | 2 +- ...fix-qdrift-evolution-bceb9c4f182ab0f5.yaml | 2 +- .../1.1/star-prerouting-0998b59880c20cef.yaml | 2 +- ...r_probabilities_dict-e53f524d115bbcfc.yaml | 2 +- setup.py | 2 +- test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm | 2 +- .../classical/test_expr_constructors.py | 4 +-- .../circuit/classical/test_expr_properties.py | 2 +- test/python/circuit/library/test_diagonal.py | 2 +- test/python/circuit/library/test_qft.py | 2 +- .../circuit/test_circuit_load_from_qpy.py | 4 +-- .../python/circuit/test_circuit_operations.py | 2 +- test/python/circuit/test_circuit_vars.py | 2 +- .../circuit/test_control_flow_builders.py | 8 +++--- test/python/circuit/test_controlled_gate.py | 2 +- test/python/circuit/test_instructions.py | 2 +- test/python/circuit/test_parameters.py | 6 ++-- test/python/compiler/test_assembler.py | 6 ++-- test/python/compiler/test_transpiler.py | 6 ++-- .../converters/test_circuit_to_instruction.py | 4 +-- test/python/dagcircuit/test_collect_blocks.py | 2 +- test/python/dagcircuit/test_dagcircuit.py | 10 +++---- .../containers/test_observables_array.py | 2 +- test/python/primitives/test_estimator.py | 8 +++--- .../primitives/test_statevector_estimator.py | 2 +- .../pulse/test_instruction_schedule_map.py | 4 +-- test/python/pulse/test_reference.py | 6 ++-- test/python/qasm3/test_export.py | 8 +++--- test/python/qasm3/test_import.py | 2 +- .../operators/symplectic/test_pauli_list.py | 4 +-- .../symplectic/test_sparse_pauli_op.py | 6 ++-- test/python/result/test_mitigators.py | 4 +-- test/python/result/test_result.py | 2 +- .../test_clifford_decompose_layers.py | 4 +-- test/python/synthesis/test_cx_cz_synthesis.py | 2 +- test/python/synthesis/test_cz_synthesis.py | 2 +- .../synthesis/test_stabilizer_synthesis.py | 4 +-- test/python/synthesis/test_synthesis.py | 2 +- test/python/test_util.py | 4 +-- .../test_instruction_alignments.py | 2 +- .../python/transpiler/test_clifford_passes.py | 2 +- .../test_commutative_cancellation.py | 10 +++---- .../transpiler/test_consolidate_blocks.py | 2 +- .../test_full_ancilla_allocation.py | 2 +- test/python/transpiler/test_gate_direction.py | 4 +-- .../transpiler/test_high_level_synthesis.py | 4 +-- .../transpiler/test_instruction_alignments.py | 2 +- .../transpiler/test_preset_passmanagers.py | 4 +-- test/python/transpiler/test_sabre_layout.py | 2 +- test/python/transpiler/test_sabre_swap.py | 2 +- .../transpiler/test_template_matching.py | 4 +-- test/python/transpiler/test_token_swapper.py | 2 +- .../test_unitary_synthesis_plugin.py | 4 +-- test/python/utils/test_lazy_loaders.py | 2 +- .../visualization/test_circuit_text_drawer.py | 4 +-- test/qpy_compat/test_qpy.py | 2 +- test/utils/_canonical.py | 2 +- 223 files changed, 370 insertions(+), 369 deletions(-) diff --git a/.binder/postBuild b/.binder/postBuild index 9517953258f2..cb06527f280e 100644 --- a/.binder/postBuild +++ b/.binder/postBuild @@ -7,7 +7,7 @@ # - pylatexenc: for MPL drawer # - pillow: for image comparison # - appmode: jupyter extension for executing the notebook -# - seaborn: visualisation pacakge required for some graphs +# - seaborn: visualization pacakge required for some graphs pip install matplotlib pylatexenc pillow appmode seaborn pip install . diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index bcc86d63fcf5..88fd919e8ad6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,6 +1,6 @@ name: Backport metadata -# Mergify manages the opening of the backport PR, this workflow is just to extend its behaviour to +# Mergify manages the opening of the backport PR, this workflow is just to extend its behavior to # do useful things like copying across the tagged labels and milestone from the base PR. on: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4641c7878fc1..7076c1571b18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -532,7 +532,7 @@ we used in our CI systems more closely. ### Snapshot Testing for Visualizations -If you are working on code that makes changes to any matplotlib visualisations +If you are working on code that makes changes to any matplotlib visualizations you will need to check that your changes don't break any snapshot tests, and add new tests where necessary. You can do this as follows: @@ -543,7 +543,7 @@ the snapshot tests (note this may take some time to finish loading). 3. Each test result provides a set of 3 images (left: reference image, middle: your test result, right: differences). In the list of tests the passed tests are collapsed and failed tests are expanded. If a test fails, you will see a situation like this: Screenshot_2021-03-26_at_14 13 54 -4. Fix any broken tests. Working on code for one aspect of the visualisations +4. Fix any broken tests. Working on code for one aspect of the visualizations can sometimes result in minor changes elsewhere to spacing etc. In these cases you just need to update the reference images as follows: - download the mismatched images (link at top of Jupyter Notebook output) diff --git a/crates/README.md b/crates/README.md index cbe58afa07d1..d72247bc61de 100644 --- a/crates/README.md +++ b/crates/README.md @@ -29,11 +29,11 @@ This would be a particular problem for defining the circuit object and using it ## Developer notes -### Beware of initialisation order +### Beware of initialization order -The Qiskit C extension `qiskit._accelerate` needs to be initialised in a single go. -It is the lowest part of the Python package stack, so it cannot rely on importing other parts of the Python library at initialisation time (except for exceptions through PyO3's `import_exception!` mechanism). -This is because, unlike pure-Python modules, the initialisation of `_accelerate` cannot be done partially, and many components of Qiskit import their accelerators from `_accelerate`. +The Qiskit C extension `qiskit._accelerate` needs to be initialized in a single go. +It is the lowest part of the Python package stack, so it cannot rely on importing other parts of the Python library at initialization time (except for exceptions through PyO3's `import_exception!` mechanism). +This is because, unlike pure-Python modules, the initialization of `_accelerate` cannot be done partially, and many components of Qiskit import their accelerators from `_accelerate`. In general, this should not be too onerous a requirement, but if you violate it, you might see Rust panics on import, and PyO3 should wrap that up into an exception. You might be able to track down the Rust source of the import cycle by running the import with the environment variable `RUST_BACKTRACE=full`. diff --git a/crates/accelerate/src/pauli_exp_val.rs b/crates/accelerate/src/pauli_exp_val.rs index 29f741f6cf46..52a2fc07f81d 100644 --- a/crates/accelerate/src/pauli_exp_val.rs +++ b/crates/accelerate/src/pauli_exp_val.rs @@ -32,7 +32,7 @@ pub fn fast_sum_with_simd(simd: S, values: &[f64]) -> f64 { sum + tail.iter().sum::() } -/// Compute the pauli expectatation value of a statevector without x +/// Compute the pauli expectation value of a statevector without x #[pyfunction] #[pyo3(text_signature = "(data, num_qubits, z_mask, /)")] pub fn expval_pauli_no_x( @@ -63,7 +63,7 @@ pub fn expval_pauli_no_x( } } -/// Compute the pauli expectatation value of a statevector with x +/// Compute the pauli expectation value of a statevector with x #[pyfunction] #[pyo3(text_signature = "(data, num_qubits, z_mask, x_mask, phase, x_max, /)")] pub fn expval_pauli_with_x( @@ -121,7 +121,7 @@ pub fn expval_pauli_with_x( } } -/// Compute the pauli expectatation value of a density matrix without x +/// Compute the pauli expectation value of a density matrix without x #[pyfunction] #[pyo3(text_signature = "(data, num_qubits, z_mask, /)")] pub fn density_expval_pauli_no_x( @@ -153,7 +153,7 @@ pub fn density_expval_pauli_no_x( } } -/// Compute the pauli expectatation value of a density matrix with x +/// Compute the pauli expectation value of a density matrix with x #[pyfunction] #[pyo3(text_signature = "(data, num_qubits, z_mask, x_mask, phase, x_max, /)")] pub fn density_expval_pauli_with_x( diff --git a/crates/accelerate/src/sabre/sabre_dag.rs b/crates/accelerate/src/sabre/sabre_dag.rs index aa35a5d7942f..d783f3844629 100644 --- a/crates/accelerate/src/sabre/sabre_dag.rs +++ b/crates/accelerate/src/sabre/sabre_dag.rs @@ -27,7 +27,7 @@ pub struct DAGNode { } /// A DAG representation of the logical circuit to be routed. This represents the same dataflow -/// dependences as the Python-space [DAGCircuit], but without any information about _what_ the +/// dependencies as the Python-space [DAGCircuit], but without any information about _what_ the /// operations being performed are. Note that all the qubit references here are to "virtual" /// qubits, that is, the qubits are those specified by the user. This DAG does not need to be /// full-width on the hardware. diff --git a/crates/accelerate/src/sparse_pauli_op.rs b/crates/accelerate/src/sparse_pauli_op.rs index 808269d8ab90..e0c80f716161 100644 --- a/crates/accelerate/src/sparse_pauli_op.rs +++ b/crates/accelerate/src/sparse_pauli_op.rs @@ -421,7 +421,7 @@ fn decompose_dense_inner( ) { if num_qubits == 0 { // It would be safe to `return` here, but if it's unreachable then LLVM is allowed to - // optimise out this branch entirely in release mode, which is good for a ~2% speedup. + // optimize out this branch entirely in release mode, which is good for a ~2% speedup. unreachable!("should not call this with an empty operator") } // Base recursion case. @@ -529,7 +529,7 @@ fn to_matrix_dense_inner(paulis: &MatrixCompressedPaulis, parallel: bool) -> Vec out }; let write_row = |(i_row, row): (usize, &mut [Complex64])| { - // Doing the initialisation here means that when we're in parallel contexts, we do the + // 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)); @@ -721,7 +721,7 @@ macro_rules! impl_to_matrix_sparse { // The parallel overhead from splitting a subtask is fairly high (allocating and // potentially growing a couple of vecs), so we're trading off some of Rayon's ability - // to keep threads busy by subdivision with minimising overhead; we're setting the + // to keep threads busy by subdivision with minimizing overhead; we're setting the // chunk size such that the iterator will have as many elements as there are threads. let num_threads = rayon::current_num_threads(); let chunk_size = (side + num_threads - 1) / num_threads; @@ -738,7 +738,7 @@ macro_rules! impl_to_matrix_sparse { // Since we compressed the Paulis by summing equal elements, we're // lower-bounded on the number of elements per row by this value, up to // cancellations. This should be a reasonable trade-off between sometimes - // expandin the vector and overallocation. + // expanding the vector and overallocation. let mut values = Vec::::with_capacity(chunk_size * (num_ops + 1) / 2); let mut indices = Vec::<$int_ty>::with_capacity(chunk_size * (num_ops + 1) / 2); diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index f93eb2a8d99c..e8c572b04039 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -293,7 +293,7 @@ fn __num_basis_gates(basis_b: f64, basis_fidelity: f64, unitary: MatRef) -> c64::new(4.0 * c.cos(), 0.0), c64::new(4.0, 0.0), ]; - // The originial Python had `np.argmax`, which returns the lowest index in case two or more + // The original Python had `np.argmax`, which returns the lowest index in case two or more // values have a common maximum value. // `max_by` and `min_by` return the highest and lowest indices respectively, in case of ties. // So to reproduce `np.argmax`, we use `min_by` and switch the order of the @@ -587,7 +587,7 @@ impl TwoQubitWeylDecomposition { // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D P^T where // P ∈ SO(4), D is diagonal with unit-magnitude elements. // - // We can't use raw `eig` directly because it isn't guaranteed to give us real or othogonal + // We can't use raw `eig` directly because it isn't guaranteed to give us real or orthogonal // eigenvectors. Instead, since `M2` is complex-symmetric, // M2 = A + iB // for real-symmetric `A` and `B`, and as diff --git a/crates/qasm2/src/expr.rs b/crates/qasm2/src/expr.rs index d8a08080a95c..fe78b290e0f5 100644 --- a/crates/qasm2/src/expr.rs +++ b/crates/qasm2/src/expr.rs @@ -104,7 +104,7 @@ impl From for Op { } } -/// An atom of the operator-precendence expression parsing. This is a stripped-down version of the +/// An atom of the operator-precedence expression parsing. This is a stripped-down version of the /// [Token] and [TokenType] used in the main parser. We can use a data enum here because we do not /// need all the expressive flexibility in expecting and accepting many different token types as /// we do in the main parser; it does not significantly harm legibility to simply do @@ -233,7 +233,7 @@ fn binary_power(op: Op) -> (u8, u8) { /// A subparser used to do the operator-precedence part of the parsing for individual parameter /// expressions. The main parser creates a new instance of this struct for each expression it /// expects, and the instance lives only as long as is required to parse that expression, because -/// it takes temporary resposibility for the [TokenStream] that backs the main parser. +/// it takes temporary responsibility for the [TokenStream] that backs the main parser. pub struct ExprParser<'a> { pub tokens: &'a mut Vec, pub context: &'a mut TokenContext, @@ -504,7 +504,7 @@ impl<'a> ExprParser<'a> { // This deliberately parses an _integer_ token as a float, since all OpenQASM 2.0 // integers can be interpreted as floats, and doing that allows us to gracefully handle // cases where a huge float would overflow a `usize`. Never mind that in such a case, - // there's almost certainly precision loss from the floating-point representating + // there's almost certainly precision loss from the floating-point representing // having insufficient mantissa digits to faithfully represent the angle mod 2pi; // that's not our fault in the parser. TokenType::Real | TokenType::Integer => Ok(Some(Atom::Const(token.real(self.context)))), diff --git a/crates/qasm2/src/lex.rs b/crates/qasm2/src/lex.rs index f9f674cbc93e..551fd2b7af48 100644 --- a/crates/qasm2/src/lex.rs +++ b/crates/qasm2/src/lex.rs @@ -21,7 +21,7 @@ //! keyword; the spec technically says that any real number is valid, but in reality that leads to //! weirdness like `200.0e-2` being a valid version specifier. We do things with a custom //! context-dependent match after seeing an `OPENQASM` token, to avoid clashes with the general -//! real-number tokenisation. +//! real-number tokenization. use hashbrown::HashMap; use pyo3::prelude::PyResult; @@ -30,7 +30,7 @@ use std::path::Path; use crate::error::{message_generic, Position, QASM2ParseError}; -/// Tokenised version information data. This is more structured than the real number suggested by +/// Tokenized version information data. This is more structured than the real number suggested by /// the specification. #[derive(Clone, Debug)] pub struct Version { @@ -353,7 +353,7 @@ impl TokenStream { line_buffer: Vec::with_capacity(80), done: false, // The first line is numbered "1", and the first column is "0". The counts are - // initialised like this so the first call to `next_byte` can easily detect that it + // initialized like this so the first call to `next_byte` can easily detect that it // needs to extract the next line. line: 0, col: 0, diff --git a/crates/qasm2/src/parse.rs b/crates/qasm2/src/parse.rs index e4c749841124..f7eceb6aeef4 100644 --- a/crates/qasm2/src/parse.rs +++ b/crates/qasm2/src/parse.rs @@ -1630,7 +1630,7 @@ impl State { /// Update the parser state with the definition of a particular gate. This does not emit any /// bytecode because not all gate definitions need something passing to Python. For example, - /// the Python parser initialises its state including the built-in gates `U` and `CX`, and + /// the Python parser initializes its state including the built-in gates `U` and `CX`, and /// handles the `qelib1.inc` include specially as well. fn define_gate( &mut self, diff --git a/crates/qasm3/src/build.rs b/crates/qasm3/src/build.rs index 2f817187625d..f5cf2fd4efca 100644 --- a/crates/qasm3/src/build.rs +++ b/crates/qasm3/src/build.rs @@ -69,7 +69,7 @@ impl BuilderState { Err(QASM3ImporterError::new_err("cannot handle consts")) } else if decl.initializer().is_some() { Err(QASM3ImporterError::new_err( - "cannot handle initialised bits", + "cannot handle initialized bits", )) } else { self.add_clbit(py, name_id.clone()) @@ -80,7 +80,7 @@ impl BuilderState { Err(QASM3ImporterError::new_err("cannot handle consts")) } else if decl.initializer().is_some() { Err(QASM3ImporterError::new_err( - "cannot handle initialised registers", + "cannot handle initialized registers", )) } else { match dims { diff --git a/crates/qasm3/src/circuit.rs b/crates/qasm3/src/circuit.rs index 330805fa2f86..fdd92c43c0bc 100644 --- a/crates/qasm3/src/circuit.rs +++ b/crates/qasm3/src/circuit.rs @@ -281,7 +281,7 @@ impl PyCircuitModule { /// Circuit construction context object to provide an easier Rust-space interface for us to /// construct the Python :class:`.QuantumCircuit`. The idea of doing this from Rust space like /// this is that we might steadily be able to move more and more of it into being native Rust as -/// the Rust-space APIs around the internal circuit data stabilise. +/// the Rust-space APIs around the internal circuit data stabilize. pub struct PyCircuit(Py); impl PyCircuit { diff --git a/crates/qasm3/src/expr.rs b/crates/qasm3/src/expr.rs index e912aecdb875..64afe58991ca 100644 --- a/crates/qasm3/src/expr.rs +++ b/crates/qasm3/src/expr.rs @@ -71,7 +71,7 @@ fn eval_const_int(_py: Python, _ast_symbols: &SymbolTable, expr: &asg::TExpr) -> match expr.expression() { asg::Expr::Literal(asg::Literal::Int(lit)) => Ok(*lit.value() as isize), expr => Err(QASM3ImporterError::new_err(format!( - "unhandled expression type for constant-integer evaluatation: {:?}", + "unhandled expression type for constant-integer evaluation: {:?}", expr ))), } diff --git a/docs/conf.py b/docs/conf.py index b35f5ca64d66..f6bf2faa9a18 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -114,9 +114,9 @@ autosummary_generate = True autosummary_generate_overwrite = False -# The pulse library contains some names that differ only in capitalisation, during the changeover +# The pulse library contains some names that differ only in capitalization, during the changeover # surrounding SymbolPulse. Since these resolve to autosummary filenames that also differ only in -# capitalisation, this causes problems when the documentation is built on an OS/filesystem that is +# capitalization, this causes problems when the documentation is built on an OS/filesystem that is # enforcing case-insensitive semantics. This setting defines some custom names to prevent the clash # from happening. autosummary_filename_map = { diff --git a/qiskit/_numpy_compat.py b/qiskit/_numpy_compat.py index a6c06671c986..9b6b466fbc9c 100644 --- a/qiskit/_numpy_compat.py +++ b/qiskit/_numpy_compat.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Compatiblity helpers for the Numpy 1.x to 2.0 transition.""" +"""Compatibility helpers for the Numpy 1.x to 2.0 transition.""" import re import typing diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 43087760153e..65a88519a0de 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -270,7 +270,7 @@ * :class:`ContinueLoopOp`, to move immediately to the next iteration of the containing loop * :class:`ForLoopOp`, to loop over a fixed range of values * :class:`IfElseOp`, to conditionally enter one of two subcircuits - * :class:`SwitchCaseOp`, to conditionally enter one of many subcicuits + * :class:`SwitchCaseOp`, to conditionally enter one of many subcircuits * :class:`WhileLoopOp`, to repeat a subcircuit until a condition is falsified. :ref:`Circuits can include classical expressions that are evaluated in real time diff --git a/qiskit/circuit/_classical_resource_map.py b/qiskit/circuit/_classical_resource_map.py index bff7d9f80fec..ba42f15cddc9 100644 --- a/qiskit/circuit/_classical_resource_map.py +++ b/qiskit/circuit/_classical_resource_map.py @@ -31,7 +31,7 @@ class VariableMapper(expr.ExprVisitor[expr.Expr]): call its :meth:`map_condition`, :meth:`map_target` or :meth:`map_expr` methods as appropriate, which will return the new object that should be used. - If an ``add_register`` callable is given to the initialiser, the mapper will use it to attempt + If an ``add_register`` callable is given to the initializer, the mapper will use it to attempt to add new aliasing registers to the outer circuit object, if there is not already a suitable register for the mapping available in the circuit. If this parameter is not given, a ``ValueError`` will be raised instead. The given ``add_register`` callable may choose to raise @@ -73,12 +73,12 @@ def _map_register(self, theirs: ClassicalRegister) -> ClassicalRegister: def map_condition(self, condition, /, *, allow_reorder=False): """Map the given ``condition`` so that it only references variables in the destination - circuit (as given to this class on initialisation). + circuit (as given to this class on initialization). If ``allow_reorder`` is ``True``, then when a legacy condition (the two-tuple form) is made on a register that has a counterpart in the destination with all the same (mapped) bits but in a different order, then that register will be used and the value suitably modified to - make the equality condition work. This is maintaining legacy (tested) behaviour of + make the equality condition work. This is maintaining legacy (tested) behavior of :meth:`.DAGCircuit.compose`; nowhere else does this, and in general this would require *far* more complex classical rewriting than Terra needs to worry about in the full expression era. """ @@ -91,7 +91,7 @@ def map_condition(self, condition, /, *, allow_reorder=False): return (self.bit_map[target], value) if not allow_reorder: return (self._map_register(target), value) - # This is maintaining the legacy behaviour of `DAGCircuit.compose`. We don't attempt to + # This is maintaining the legacy behavior of `DAGCircuit.compose`. We don't attempt to # speed-up this lookup with a cache, since that would just make the more standard cases more # annoying to deal with. mapped_bits_order = [self.bit_map[bit] for bit in target] @@ -114,7 +114,7 @@ def map_condition(self, condition, /, *, allow_reorder=False): def map_target(self, target, /): """Map the real-time variables in a ``target`` of a :class:`.SwitchCaseOp` to the new - circuit, as defined in the ``circuit`` argument of the initialiser of this class.""" + circuit, as defined in the ``circuit`` argument of the initializer of this class.""" if isinstance(target, Clbit): return self.bit_map[target] if isinstance(target, ClassicalRegister): diff --git a/qiskit/circuit/classical/expr/expr.py b/qiskit/circuit/classical/expr/expr.py index 62b6829ce4a7..586b06ec9dbc 100644 --- a/qiskit/circuit/classical/expr/expr.py +++ b/qiskit/circuit/classical/expr/expr.py @@ -53,7 +53,7 @@ class Expr(abc.ABC): expressions, and it does not make sense to add more outside of Qiskit library code. All subclasses are responsible for setting their ``type`` attribute in their ``__init__``, and - should not call the parent initialiser.""" + should not call the parent initializer.""" __slots__ = ("type",) @@ -193,7 +193,7 @@ def __copy__(self): return self def __deepcopy__(self, memo): - # ... as are all my consituent parts. + # ... as are all my constituent parts. return self @@ -241,7 +241,7 @@ class Op(enum.Enum): # If adding opcodes, remember to add helper constructor functions in `constructors.py`. # The opcode integers should be considered a public interface; they are used by - # serialisation formats that may transfer data between different versions of Qiskit. + # serialization formats that may transfer data between different versions of Qiskit. BIT_NOT = 1 """Bitwise negation. ``~operand``.""" LOGIC_NOT = 2 @@ -309,7 +309,7 @@ class Op(enum.Enum): # If adding opcodes, remember to add helper constructor functions in `constructors.py` # The opcode integers should be considered a public interface; they are used by - # serialisation formats that may transfer data between different versions of Qiskit. + # serialization formats that may transfer data between different versions of Qiskit. BIT_AND = 1 """Bitwise "and". ``lhs & rhs``.""" BIT_OR = 2 diff --git a/qiskit/circuit/classical/expr/visitors.py b/qiskit/circuit/classical/expr/visitors.py index 744257714b79..be7e9311c377 100644 --- a/qiskit/circuit/classical/expr/visitors.py +++ b/qiskit/circuit/classical/expr/visitors.py @@ -29,7 +29,7 @@ class ExprVisitor(typing.Generic[_T_co]): """Base class for visitors to the :class:`Expr` tree. Subclasses should override whichever of - the ``visit_*`` methods that they are able to handle, and should be organised such that + the ``visit_*`` methods that they are able to handle, and should be organized such that non-existent methods will never be called.""" # The method names are self-explanatory and docstrings would just be noise. diff --git a/qiskit/circuit/classical/types/__init__.py b/qiskit/circuit/classical/types/__init__.py index 14365fd32a6f..ae38a0d97fb5 100644 --- a/qiskit/circuit/classical/types/__init__.py +++ b/qiskit/circuit/classical/types/__init__.py @@ -40,7 +40,7 @@ .. autoclass:: Bool .. autoclass:: Uint -Note that :class:`Uint` defines a family of types parametrised by their width; it is not one single +Note that :class:`Uint` defines a family of types parametrized by their width; it is not one single type, which may be slightly different to the 'classical' programming languages you are used to. diff --git a/qiskit/circuit/classical/types/types.py b/qiskit/circuit/classical/types/types.py index 04266aefd410..d20e7b5fd746 100644 --- a/qiskit/circuit/classical/types/types.py +++ b/qiskit/circuit/classical/types/types.py @@ -29,7 +29,7 @@ class _Singleton(type): - """Metaclass to make the child, which should take zero initialisation arguments, a singleton + """Metaclass to make the child, which should take zero initialization arguments, a singleton object.""" def _get_singleton_instance(cls): @@ -76,7 +76,7 @@ def __deepcopy__(self, _memo): def __setstate__(self, state): _dict, slots = state for slot, value in slots.items(): - # We need to overcome the type's enforcement of immutability post initialisation. + # We need to overcome the type's enforcement of immutability post initialization. super().__setattr__(slot, value) diff --git a/qiskit/circuit/controlflow/_builder_utils.py b/qiskit/circuit/controlflow/_builder_utils.py index bfb0d905387e..e80910aac3ed 100644 --- a/qiskit/circuit/controlflow/_builder_utils.py +++ b/qiskit/circuit/controlflow/_builder_utils.py @@ -127,7 +127,7 @@ def unify_circuit_resources(circuits: Iterable[QuantumCircuit]) -> Iterable[Quan This function will preferentially try to mutate its inputs if they share an ordering, but if not, it will rebuild two new circuits. This is to avoid coupling too tightly to the inner class; there is no real support for deleting or re-ordering bits within a :obj:`.QuantumCircuit` - context, and we don't want to rely on the *current* behaviour of the private APIs, since they + context, and we don't want to rely on the *current* behavior of the private APIs, since they are very liable to change. No matter the method used, circuits with unified bits and registers are returned. """ diff --git a/qiskit/circuit/controlflow/builder.py b/qiskit/circuit/controlflow/builder.py index bb0a30ea6af6..ab464a50ca67 100644 --- a/qiskit/circuit/controlflow/builder.py +++ b/qiskit/circuit/controlflow/builder.py @@ -13,7 +13,7 @@ """Builder types for the basic control-flow constructs.""" # This file is in circuit.controlflow rather than the root of circuit because the constructs here -# are only intended to be localised to constructing the control flow instructions. We anticipate +# are only intended to be localized to constructing the control flow instructions. We anticipate # having a far more complete builder of all circuits, with more classical control and creation, in # the future. @@ -206,7 +206,7 @@ class InstructionPlaceholder(Instruction, abc.ABC): When appending a placeholder instruction into a circuit scope, you should create the placeholder, and then ask it what resources it should be considered as using from the start by calling :meth:`.InstructionPlaceholder.placeholder_instructions`. This set will be a subset of - the final resources it asks for, but it is used for initialising resources that *must* be + the final resources it asks for, but it is used for initializing resources that *must* be supplied, such as the bits used in the conditions of placeholder ``if`` statements. .. warning:: @@ -360,7 +360,7 @@ def __init__( which use a classical register as their condition. allow_jumps: Whether this builder scope should allow ``break`` and ``continue`` statements within it. This is intended to help give sensible error messages when - dangerous behaviour is encountered, such as using ``break`` inside an ``if`` context + dangerous behavior is encountered, such as using ``break`` inside an ``if`` context manager that is not within a ``for`` manager. This can only be safe if the user is going to place the resulting :obj:`.QuantumCircuit` inside a :obj:`.ForLoopOp` that uses *exactly* the same set of resources. We cannot verify this from within the @@ -395,7 +395,7 @@ def clbits(self): def allow_jumps(self): """Whether this builder scope should allow ``break`` and ``continue`` statements within it. - This is intended to help give sensible error messages when dangerous behaviour is + This is intended to help give sensible error messages when dangerous behavior is encountered, such as using ``break`` inside an ``if`` context manager that is not within a ``for`` manager. This can only be safe if the user is going to place the resulting :obj:`.QuantumCircuit` inside a :obj:`.ForLoopOp` that uses *exactly* the same set of diff --git a/qiskit/circuit/controlflow/if_else.py b/qiskit/circuit/controlflow/if_else.py index 121d4c681f27..dd639c65f4b5 100644 --- a/qiskit/circuit/controlflow/if_else.py +++ b/qiskit/circuit/controlflow/if_else.py @@ -199,7 +199,7 @@ def __init__( super().__init__( "if_else", len(self.__resources.qubits), len(self.__resources.clbits), [], label=label ) - # Set the condition after super().__init__() has initialised it to None. + # Set the condition after super().__init__() has initialized it to None. self.condition = validate_condition(condition) def with_false_block(self, false_block: ControlFlowBuilderBlock) -> "IfElsePlaceholder": @@ -236,7 +236,7 @@ def registers(self): def _calculate_placeholder_resources(self) -> InstructionResources: """Get the placeholder resources (see :meth:`.placeholder_resources`). - This is a separate function because we use the resources during the initialisation to + This is a separate function because we use the resources during the initialization to determine how we should set our ``num_qubits`` and ``num_clbits``, so we implement the public version as a cache access for efficiency. """ diff --git a/qiskit/circuit/controlflow/switch_case.py b/qiskit/circuit/controlflow/switch_case.py index 446230c3c3cd..6df8c4ef62ab 100644 --- a/qiskit/circuit/controlflow/switch_case.py +++ b/qiskit/circuit/controlflow/switch_case.py @@ -94,7 +94,7 @@ def __init__( it's easier for things like `assign_parameters`, which need to touch each circuit object exactly once, to function.""" self._label_spec: List[Tuple[Union[int, Literal[CASE_DEFAULT]], ...]] = [] - """List of the normalised jump value specifiers. This is a list of tuples, where each tuple + """List of the normalized jump value specifiers. This is a list of tuples, where each tuple contains the values, and the indexing is the same as the values of `_case_map` and `_params`.""" self._params = [] diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index f53c5b9e9b3c..1b5fca3f738b 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -115,12 +115,12 @@ def base_class(self) -> Type[Instruction]: The "base class" of an instruction is the lowest class in its inheritance tree that the object should be considered entirely compatible with for _all_ circuit applications. This typically means that the subclass is defined purely to offer some sort of programmer - convenience over the base class, and the base class is the "true" class for a behavioural + convenience over the base class, and the base class is the "true" class for a behavioral perspective. In particular, you should *not* override :attr:`base_class` if you are defining a custom version of an instruction that will be implemented differently by - hardware, such as an alternative measurement strategy, or a version of a parametrised gate + hardware, such as an alternative measurement strategy, or a version of a parametrized gate with a particular set of parameters for the purposes of distinguishing it in a - :class:`.Target` from the full parametrised gate. + :class:`.Target` from the full parametrized gate. This is often exactly equivalent to ``type(obj)``, except in the case of singleton instances of standard-library instructions. These singleton instances are special subclasses of their diff --git a/qiskit/circuit/library/arithmetic/linear_amplitude_function.py b/qiskit/circuit/library/arithmetic/linear_amplitude_function.py index 0825f3f4e0a9..a79670eef654 100644 --- a/qiskit/circuit/library/arithmetic/linear_amplitude_function.py +++ b/qiskit/circuit/library/arithmetic/linear_amplitude_function.py @@ -119,7 +119,7 @@ def __init__( self._image = image self._rescaling_factor = rescaling_factor - # do rescalings + # do rescaling a, b = domain c, d = image diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index 25f6c27bbe1b..2a750195dab3 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -162,7 +162,7 @@ def __init__( self._bounds: list[tuple[float | None, float | None]] | None = None self._flatten = flatten - # During the build, if a subclass hasn't overridden our parametrisation methods, we can use + # During the build, if a subclass hasn't overridden our parametrization methods, we can use # a newer fast-path method to parametrise the rotation and entanglement blocks if internally # those are just simple stdlib gates that have been promoted to circuits. We don't # precalculate the fast-path layers themselves because there's far too much that can be @@ -1093,7 +1093,7 @@ def _stdlib_gate_from_simple_block(block: QuantumCircuit) -> _StdlibGateResult | return None instruction = block.data[0] # If the single instruction isn't a standard-library gate that spans the full width of the block - # in the correct order, we're not simple. If the gate isn't fully parametrised with pure, + # in the correct order, we're not simple. If the gate isn't fully parametrized with pure, # unique `Parameter` instances (expressions are too complex) that are in order, we're not # simple. if ( diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py index 3495bc180f08..07684097f8cc 100644 --- a/qiskit/circuit/library/standard_gates/u.py +++ b/qiskit/circuit/library/standard_gates/u.py @@ -183,7 +183,7 @@ def __setitem__(self, key, value): # Magic numbers: CUGate has 4 parameters, UGate has 3, with the last of CUGate's missing. if isinstance(key, slice): # We don't need to worry about the case of the slice being used to insert extra / remove - # elements because that would be "undefined behaviour" in a gate already, so we're + # elements because that would be "undefined behavior" in a gate already, so we're # within our rights to do anything at all. for i, base_key in enumerate(range(*key.indices(4))): if base_key < 0: diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 6e959b3e62cb..cd4a61963823 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -972,7 +972,7 @@ def __init__( _singleton_lookup_key = stdlib_singleton_key(num_ctrl_qubits=4) - # seems like open controls not hapening? + # seems like open controls not happening? def _define(self): """ gate c3sqrtx a,b,c,d diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py index 825679f7d4f5..c7a8228dd463 100644 --- a/qiskit/circuit/parameter.py +++ b/qiskit/circuit/parameter.py @@ -62,7 +62,7 @@ class Parameter(ParameterExpression): __slots__ = ("_uuid", "_hash") # This `__init__` does not call the super init, because we can't construct the - # `_parameter_symbols` dictionary we need to pass to it before we're entirely initialised + # `_parameter_symbols` dictionary we need to pass to it before we're entirely initialized # anyway, because `ParameterExpression` depends heavily on the structure of `Parameter`. def __init__( diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 7f839677b904..16b691480d26 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -48,7 +48,7 @@ def __init__(self, symbol_map: dict, expr): expr (sympy.Expr): Expression of :class:`sympy.Symbol` s. """ # NOTE: `Parameter.__init__` does not call up to this method, since this method is dependent - # on `Parameter` instances already being initialised enough to be hashable. If changing + # on `Parameter` instances already being initialized enough to be hashable. If changing # this method, check that `Parameter.__init__` and `__setstate__` are still valid. self._parameter_symbols = symbol_map self._parameter_keys = frozenset(p._hash_key() for p in self._parameter_symbols) @@ -421,8 +421,8 @@ def __float__(self): ) from None # In symengine, if an expression was complex at any time, its type is likely to have # stayed "complex" even when the imaginary part symbolically (i.e. exactly) - # cancelled out. Sympy tends to more aggressively recognise these as symbolically - # real. This second attempt at a cast is a way of unifying the behaviour to the + # cancelled out. Sympy tends to more aggressively recognize these as symbolically + # real. This second attempt at a cast is a way of unifying the behavior to the # more expected form for our users. cval = complex(self) if cval.imag == 0.0: diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 010a91e3639e..a88dfd43ea4b 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -111,7 +111,7 @@ # # If you're adding methods or attributes to `QuantumCircuit`, be sure to update the class docstring # to document them in a suitable place. The class is huge, so we do its documentation manually so -# it has at least some amount of organisational structure. +# it has at least some amount of organizational structure. class QuantumCircuit: @@ -369,7 +369,7 @@ class QuantumCircuit: ------------------------------- A :class:`.Bit` instance is, on its own, just a unique handle for circuits to use in their own - contexts. If you have got a :class:`.Bit` instance and a cirucit, just can find the contexts + contexts. If you have got a :class:`.Bit` instance and a circuit, just can find the contexts that the bit exists in using :meth:`find_bit`, such as its integer index in the circuit and any registers it is contained in. @@ -650,7 +650,7 @@ class QuantumCircuit: Finally, these methods apply particular generalized multiply controlled gates to the circuit, often with eager syntheses. They are listed in terms of the *base* gate they are controlling, - since their exact output is often a synthesised version of a gate. + since their exact output is often a synthesized version of a gate. =============================== ================================================= :class:`QuantumCircuit` method Base :mod:`qiskit.circuit.library` :class:`.Gate` @@ -2500,7 +2500,7 @@ def _append(self, instruction, qargs=(), cargs=(), *, _standard_gate: bool = Fal and the only reference to the circuit the instructions are being appended to is within that same function. In particular, it is not safe to call :meth:`QuantumCircuit._append` on a circuit that is received by a function argument. - This is because :meth:`.QuantumCircuit._append` will not recognise the scoping + This is because :meth:`.QuantumCircuit._append` will not recognize the scoping constructs of the control-flow builder interface. Args: @@ -2582,7 +2582,7 @@ def get_parameter(self, name: str, default: typing.Any = ...) -> Parameter: my_param = Parameter("my_param") - # Create a parametrised circuit. + # Create a parametrized circuit. qc = QuantumCircuit(1) qc.rx(my_param, 0) @@ -2798,8 +2798,8 @@ def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.V # two classical registers we measured into above. qc.add_var(my_var, expr.bit_and(cr1, cr2)) """ - # Validate the initialiser first to catch cases where the variable to be declared is being - # used in the initialiser. + # Validate the initializer first to catch cases where the variable to be declared is being + # used in the initializer. circuit_scope = self._current_scope() # Convenience method to widen Python integer literals to the right width during the initial # lift, if the type is already known via the variable. @@ -2823,7 +2823,7 @@ def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.V var = name_or_var circuit_scope.add_uninitialized_var(var) try: - # Store is responsible for ensuring the type safety of the initialisation. + # Store is responsible for ensuring the type safety of the initialization. store = Store(var, initial) except CircuitError: circuit_scope.remove_var(var) @@ -2853,7 +2853,7 @@ def add_uninitialized_var(self, var: expr.Var, /): # name, and to be a bit less ergonomic than `add_var` (i.e. not allowing the (name, type) # overload) to discourage people from using it when they should use `add_var`. # - # This function exists so that there is a method to emulate `copy_empty_like`'s behaviour of + # This function exists so that there is a method to emulate `copy_empty_like`'s behavior of # adding uninitialised variables, which there's no obvious way around. We need to be sure # that _some_ sort of handling of uninitialised variables is taken into account in our # structures, so that doesn't become a huge edge case, even though we make no assertions @@ -2887,7 +2887,7 @@ def add_capture(self, var: expr.Var): """ if self._control_flow_scopes: # Allow manual capturing. Not sure why it'd be useful, but there's a clear expected - # behaviour here. + # behavior here. self._control_flow_scopes[-1].use_var(var) return if self._vars_input: @@ -3656,7 +3656,7 @@ def copy_empty_like( if vars_mode == "alike": # Note that this causes the local variables to be uninitialised, because the stores are # not copied. This can leave the circuit in a potentially dangerous state for users if - # they don't re-add initialiser stores. + # they don't re-add initializer stores. cpy._vars_local = self._vars_local.copy() cpy._vars_input = self._vars_input.copy() cpy._vars_capture = self._vars_capture.copy() @@ -4061,7 +4061,7 @@ def global_phase(self, angle: ParameterValueType): angle (float, ParameterExpression): radians """ # If we're currently parametric, we need to throw away the references. This setter is - # called by some subclasses before the inner `_global_phase` is initialised. + # called by some subclasses before the inner `_global_phase` is initialized. if isinstance(getattr(self._data, "global_phase", None), ParameterExpression): self._parameters = None if isinstance(angle, ParameterExpression): @@ -4273,7 +4273,7 @@ def assign_parameters( # pylint: disable=missing-raises-doc target._increment_instances() target._name_update() - # Normalise the inputs into simple abstract interfaces, so we've dispatched the "iteration" + # Normalize the inputs into simple abstract interfaces, so we've dispatched the "iteration" # logic in one place at the start of the function. This lets us do things like calculate # and cache expensive properties for (e.g.) the sequence format only if they're used; for # many large, close-to-hardware circuits, we won't need the extra handling for @@ -5909,7 +5909,7 @@ def _pop_scope(self) -> ControlFlowBuilderBlock: """Finish a scope used in the control-flow builder interface, and return it to the caller. This should only be done by the control-flow context managers, since they naturally - synchronise the creation and deletion of stack elements.""" + synchronize the creation and deletion of stack elements.""" return self._control_flow_scopes.pop() def _peek_previous_instruction_in_scope(self) -> CircuitInstruction: diff --git a/qiskit/circuit/random/utils.py b/qiskit/circuit/random/utils.py index f27cbfbfca88..3bcdbeaef4ac 100644 --- a/qiskit/circuit/random/utils.py +++ b/qiskit/circuit/random/utils.py @@ -193,7 +193,8 @@ def random_circuit( # Apply arbitrary random operations in layers across all qubits. for layer_number in range(depth): # We generate all the randomness for the layer in one go, to avoid many separate calls to - # the randomisation routines, which can be fairly slow. + # the randomization routines, which can be fairly slow. + # This reliably draws too much randomness, but it's less expensive than looping over more # calls to the rng. After, trim it down by finding the point when we've used all the qubits. @@ -239,9 +240,9 @@ def random_circuit( if not gate_added_flag: break - # For efficiency in the Python loop, this uses Numpy vectorisation to pre-calculate the + # For efficiency in the Python loop, this uses Numpy vectorization to pre-calculate the # indices into the lists of qubits and parameters for every gate, and then suitably - # randomises those lists. + # randomizes those lists. q_indices = np.empty(len(gate_specs) + 1, dtype=np.int64) p_indices = np.empty(len(gate_specs) + 1, dtype=np.int64) q_indices[0] = p_indices[0] = 0 diff --git a/qiskit/circuit/singleton.py b/qiskit/circuit/singleton.py index bd689b6be103..874b979ff588 100644 --- a/qiskit/circuit/singleton.py +++ b/qiskit/circuit/singleton.py @@ -42,7 +42,7 @@ heart of Qiskit's data model for circuits. From a library-author perspective, the minimum that is needed to enhance a :class:`.Gate` or -:class:`~.circuit.Instruction` with this behaviour is to inherit from :class:`SingletonGate` +:class:`~.circuit.Instruction` with this behavior is to inherit from :class:`SingletonGate` (:class:`SingletonInstruction`) instead of :class:`.Gate` (:class:`~.circuit.Instruction`), and for the ``__init__`` method to have defaults for all of its arguments (these will be the state of the singleton instance). For example:: @@ -175,7 +175,7 @@ def _singleton_lookup_key(n=1, label=None): This section is primarily developer documentation for the code; none of the machinery described here is public, and it is not safe to inherit from any of it directly. -There are several moving parts to tackle here. The behaviour of having ``XGate()`` return some +There are several moving parts to tackle here. The behavior of having ``XGate()`` return some singleton object that is an (inexact) instance of :class:`.XGate` but *without* calling ``__init__`` requires us to override :class:`type.__call__ `. This means that :class:`.XGate` must have a metaclass that defines ``__call__`` to return the singleton instance. @@ -484,7 +484,7 @@ class they are providing overrides for has more lazy attributes or user-exposed instruction._define() # We use this `list` subclass that rejects all mutation rather than a simple `tuple` because # the `params` typing is specified as `list`. Various places in the library and beyond do - # `x.params.copy()` when they want to produce a version they own, which is good behaviour, + # `x.params.copy()` when they want to produce a version they own, which is good behavior, # and would fail if we switched to a `tuple`, which has no `copy` method. instruction._params = _frozenlist(instruction._params) return instruction diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py index 571e330eb0db..4d0570542b03 100644 --- a/qiskit/converters/circuit_to_instruction.py +++ b/qiskit/converters/circuit_to_instruction.py @@ -62,7 +62,7 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None if circuit.num_input_vars: # This could be supported by moving the `input` variables to be parameters of the - # instruction, but we don't really have a good reprssentation of that yet, so safer to + # instruction, but we don't really have a good representation of that yet, so safer to # forbid it. raise QiskitError("Circuits with 'input' variables cannot yet be converted to instructions") if circuit.num_captured_vars: diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 686951f26fc2..d14340a8cb9b 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1525,7 +1525,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit )[0] self._multi_graph.add_edge(pred._node_id, succ._node_id, contracted_var) - # Exlude any nodes from in_dag that are not a DAGOpNode or are on + # Exclude any nodes from in_dag that are not a DAGOpNode or are on # wires outside the set specified by the wires kwarg def filter_fn(node): if not isinstance(node, DAGOpNode): @@ -1615,7 +1615,7 @@ def substitute_node(self, node: DAGOpNode, op, inplace: bool = False, propagate_ be used. propagate_condition (bool): Optional, default True. If True, a condition on the ``node`` to be replaced will be applied to the new ``op``. This is the legacy - behaviour. If either node is a control-flow operation, this will be ignored. If + behavior. If either node is a control-flow operation, this will be ignored. If the ``op`` already has a condition, :exc:`.DAGCircuitError` is raised. Returns: diff --git a/qiskit/passmanager/passmanager.py b/qiskit/passmanager/passmanager.py index 99527bf584ee..85f422f181b9 100644 --- a/qiskit/passmanager/passmanager.py +++ b/qiskit/passmanager/passmanager.py @@ -225,7 +225,7 @@ def callback_func(**kwargs): in_programs = [in_programs] is_list = False - # If we're not going to run in parallel, we want to avoid spending time `dill` serialising + # If we're not going to run in parallel, we want to avoid spending time `dill` serializing # ourselves, since that can be quite expensive. if len(in_programs) == 1 or not should_run_in_parallel(num_processes): out = [ @@ -242,7 +242,7 @@ def callback_func(**kwargs): # Pass manager may contain callable and we need to serialize through dill rather than pickle. # See https://github.com/Qiskit/qiskit-terra/pull/3290 # Note that serialized object is deserialized as a different object. - # Thus, we can resue the same manager without state collision, without building it per thread. + # Thus, we can reuse the same manager without state collision, without building it per thread. return parallel_map( _run_workflow_in_new_process, values=in_programs, diff --git a/qiskit/primitives/base/base_estimator.py b/qiskit/primitives/base/base_estimator.py index 9191b4162d92..0a7c0ec86289 100644 --- a/qiskit/primitives/base/base_estimator.py +++ b/qiskit/primitives/base/base_estimator.py @@ -203,7 +203,7 @@ class BaseEstimatorV2(ABC): @staticmethod def _make_data_bin(_: EstimatorPub) -> type[DataBin]: - # this method is present for backwards compat. new primitive implementatinos + # this method is present for backwards compat. new primitive implementations # should avoid it. return DataBin diff --git a/qiskit/primitives/containers/bindings_array.py b/qiskit/primitives/containers/bindings_array.py index 6ab60f4771db..89730e5ce94c 100644 --- a/qiskit/primitives/containers/bindings_array.py +++ b/qiskit/primitives/containers/bindings_array.py @@ -95,7 +95,7 @@ def __init__( be inferred from the provided arrays. Ambiguity arises whenever the key of an entry of ``data`` contains only one parameter and the corresponding array's shape ends in a one. In this case, it can't be decided whether that one is an index over parameters, or whether - it should be encorporated in :attr:`~shape`. + it should be incorporated in :attr:`~shape`. Since :class:`~.Parameter` objects are only allowed to represent float values, this class casts all given values to float. If an incompatible dtype is given, such as complex @@ -131,7 +131,7 @@ class casts all given values to float. If an incompatible dtype is given, such a def __getitem__(self, args) -> BindingsArray: # because the parameters live on the last axis, we don't need to do anything special to - # accomodate them because there will always be an implicit slice(None, None, None) + # accommodate them because there will always be an implicit slice(None, None, None) # on all unspecified trailing dimensions # separately, we choose to not disallow args which touch the last dimension, even though it # would not be a particularly friendly way to chop parameters diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index ed9fd3fdbb87..931dbed479ec 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -86,7 +86,7 @@ def __init__(self, configuration, provider=None, **fields): .. This next bit is necessary just because autosummary generally won't summarise private - methods; changing that behaviour would have annoying knock-on effects through all the + methods; changing that behavior would have annoying knock-on effects through all the rest of the documentation, so instead we just hard-code the automethod directive. """ self._configuration = configuration diff --git a/qiskit/providers/options.py b/qiskit/providers/options.py index fe4e7303a674..4d716fb372bb 100644 --- a/qiskit/providers/options.py +++ b/qiskit/providers/options.py @@ -116,7 +116,7 @@ def __len__(self): def __setitem__(self, key, value): self.update_options(**{key: value}) - # backwards-compatibilty with Qiskit Experiments: + # backwards-compatibility with Qiskit Experiments: @property def __dict__(self): diff --git a/qiskit/qasm2/__init__.py b/qiskit/qasm2/__init__.py index 5a2f189c4102..f17fe29113e2 100644 --- a/qiskit/qasm2/__init__.py +++ b/qiskit/qasm2/__init__.py @@ -20,7 +20,7 @@ .. note:: - OpenQASM 2 is a simple language, and not suitable for general serialisation of Qiskit objects. + OpenQASM 2 is a simple language, and not suitable for general serialization of Qiskit objects. See :ref:`some discussion of alternatives below `, if that is what you are looking for. @@ -95,7 +95,7 @@ Exporting API ============= -Similar to other serialisation modules in Python, this module offers two public functions: +Similar to other serialization modules in Python, this module offers two public functions: :func:`dump` and :func:`dumps`, which take a :class:`.QuantumCircuit` and write out a representative OpenQASM 2 program to a file-like object or return a string, respectively. @@ -394,7 +394,7 @@ def add_one(x): :meth:`.QuantumCircuit.from_qasm_str` and :meth:`~.QuantumCircuit.from_qasm_file` used to make a few additions on top of the raw specification. Qiskit originally tried to use OpenQASM 2 as a sort of -serialisation format, and expanded its behaviour as Qiskit expanded. The new parser under all its +serialization format, and expanded its behavior as Qiskit expanded. The new parser under all its defaults implements the specification more strictly. In particular, in the legacy importers: @@ -445,11 +445,11 @@ def add_one(x): * the parsed grammar is effectively the same as :ref:`the strict mode of the new importers `. -You can emulate this behaviour in :func:`load` and :func:`loads` by setting `include_path` +You can emulate this behavior in :func:`load` and :func:`loads` by setting `include_path` appropriately (try inspecting the variable ``qiskit.__file__`` to find the installed location), and by passing a list of :class:`CustomInstruction` instances for each of the custom gates you care about. To make things easier we make three tuples available, which each contain one component of -a configuration that is equivalent to Qiskit's legacy converter behaviour. +a configuration that is equivalent to Qiskit's legacy converter behavior. .. py:data:: LEGACY_CUSTOM_INSTRUCTIONS @@ -473,7 +473,7 @@ def add_one(x): instruction, it does not matter how the gates are actually defined and used, the legacy importer will always attempt to output its custom objects for them. This can result in errors during the circuit construction, even after a successful parse. There is no way to emulate this buggy -behaviour with :mod:`qiskit.qasm2`; only an ``include "qelib1.inc";`` statement or the +behavior with :mod:`qiskit.qasm2`; only an ``include "qelib1.inc";`` statement or the `custom_instructions` argument can cause built-in Qiskit instructions to be used, and the signatures of these match each other. @@ -549,7 +549,7 @@ def add_one(x): def _normalize_path(path: Union[str, os.PathLike]) -> str: - """Normalise a given path into a path-like object that can be passed to Rust. + """Normalize a given path into a path-like object that can be passed to Rust. Ideally this would be something that we can convert to Rust's `OSString`, but in practice, Python uses `os.fsencode` to produce a `bytes` object, but this doesn't map especially well. diff --git a/qiskit/qasm2/export.py b/qiskit/qasm2/export.py index 46471fa087b2..3cf0d8942553 100644 --- a/qiskit/qasm2/export.py +++ b/qiskit/qasm2/export.py @@ -308,7 +308,7 @@ def _define_custom_operation(operation, gates_to_define): lib.U3Gate, } - # In known-good situations we want to use a manually parametrised object as the source of the + # In known-good situations we want to use a manually parametrized object as the source of the # definition, but still continue to return the given object as the call-site object. if operation.base_class in known_good_parameterized: parameterized_operation = type(operation)(*_FIXED_PARAMETERS[: len(operation.params)]) diff --git a/qiskit/qasm2/parse.py b/qiskit/qasm2/parse.py index 30c85843a361..a40270a99b8c 100644 --- a/qiskit/qasm2/parse.py +++ b/qiskit/qasm2/parse.py @@ -287,7 +287,7 @@ def from_bytecode(bytecode, custom_instructions: Iterable[CustomInstruction]): class _DefinedGate(Gate): """A gate object defined by a `gate` statement in an OpenQASM 2 program. This object lazily - binds its parameters to its definition, so it is only synthesised when required.""" + binds its parameters to its definition, so it is only synthesized when required.""" def __init__(self, name, num_qubits, params, gates, bytecode): self._gates = gates diff --git a/qiskit/qasm3/ast.py b/qiskit/qasm3/ast.py index 300c53900d43..0bae60144afc 100644 --- a/qiskit/qasm3/ast.py +++ b/qiskit/qasm3/ast.py @@ -317,7 +317,7 @@ def __init__(self, expression: Expression): class ClassicalDeclaration(Statement): - """Declaration of a classical type, optionally initialising it to a value.""" + """Declaration of a classical type, optionally initializing it to a value.""" def __init__(self, type_: ClassicalType, identifier: Identifier, initializer=None): self.type = type_ diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py index 78b992b17cf2..6d5344bcc255 100644 --- a/qiskit/qasm3/exporter.py +++ b/qiskit/qasm3/exporter.py @@ -467,7 +467,7 @@ def build_program(self): self.build_gate_definition(instruction) for instruction in gates_to_declare ] - # Early IBM runtime paramterisation uses unbound `Parameter` instances as `input` variables, + # Early IBM runtime parametrization uses unbound `Parameter` instances as `input` variables, # not the explicit realtime `Var` variables, so we need this explicit scan. self.hoist_global_parameter_declarations() # Qiskit's clbits and classical registers need to get mapped to implicit OQ3 variables, but @@ -681,7 +681,7 @@ def hoist_classical_register_declarations(self): doesn't involve the declaration of *new* bits or registers in inner scopes; only the :class:`.expr.Var` mechanism allows that. - The behaviour of this function depends on the setting ``allow_aliasing``. If this + The behavior of this function depends on the setting ``allow_aliasing``. If this is ``True``, then the output will be in the same form as the output of :meth:`.build_classical_declarations`, with the registers being aliases. If ``False``, it will instead return a :obj:`.ast.ClassicalDeclaration` for each classical register, and one @@ -942,7 +942,7 @@ def case(values, case_block): ), ] - # Handle the stabilised syntax. + # Handle the stabilized syntax. cases = [] default = None for values, block in instruction.operation.cases_specifier(): diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index 11374e9aca92..8f34ee0855ac 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -89,7 +89,7 @@ class InstructionToQobjConverter: The transfer layer format must be the text representation that coforms to the `OpenPulse specification`__. Extention to the OpenPulse can be achieved by subclassing this this with - extra methods corresponding to each augumented instruction. For example, + extra methods corresponding to each augmented instruction. For example, .. code-block:: python @@ -503,7 +503,7 @@ class QobjToInstructionConverter: The transfer layer format must be the text representation that coforms to the `OpenPulse specification`__. Extention to the OpenPulse can be achieved by subclassing this this with - extra methods corresponding to each augumented instruction. For example, + extra methods corresponding to each augmented instruction. For example, .. code-block:: python diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py index 1a3393b1e4c8..eae5e6f57ad9 100644 --- a/qiskit/qpy/binary_io/schedules.py +++ b/qiskit/qpy/binary_io/schedules.py @@ -522,7 +522,7 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen metadata_deserializer (JSONDecoder): An optional JSONDecoder class that will be used for the ``cls`` kwarg on the internal ``json.load`` call used to deserialize the JSON payload used for - the :attr:`.ScheduleBlock.metadata` attribute for a schdule block + the :attr:`.ScheduleBlock.metadata` attribute for a schedule block in the file-like object. If this is not specified the circuit metadata will be parsed as JSON with the stdlib ``json.load()`` function using the default ``JSONDecoder`` class. diff --git a/qiskit/qpy/type_keys.py b/qiskit/qpy/type_keys.py index 3ff6b4a35af3..60262440d033 100644 --- a/qiskit/qpy/type_keys.py +++ b/qiskit/qpy/type_keys.py @@ -159,7 +159,7 @@ class Condition(IntEnum): """Type keys for the ``conditional_key`` field of the INSTRUCTION struct.""" # This class is deliberately raw integers and not in terms of ASCII characters for backwards - # compatiblity in the form as an old Boolean value was expanded; `NONE` and `TWO_TUPLE` must + # compatibility in the form as an old Boolean value was expanded; `NONE` and `TWO_TUPLE` must # have the enumeration values 0 and 1. NONE = 0 @@ -276,7 +276,7 @@ class ScheduleInstruction(TypeKeyBase): REFERENCE = b"y" # 's' is reserved by ScheduleBlock, i.e. block can be nested as an element. - # Call instructon is not supported by QPY. + # Call instruction is not supported by QPY. # This instruction has been excluded from ScheduleBlock instructions with # qiskit-terra/#8005 and new instruction Reference will be added instead. # Call is only applied to Schedule which is not supported by QPY. diff --git a/qiskit/quantum_info/operators/channel/transformations.py b/qiskit/quantum_info/operators/channel/transformations.py index 18987e5e943c..8f429cad8cea 100644 --- a/qiskit/quantum_info/operators/channel/transformations.py +++ b/qiskit/quantum_info/operators/channel/transformations.py @@ -228,7 +228,7 @@ def _choi_to_kraus(data, input_dim, output_dim, atol=ATOL_DEFAULT): # This should be a call to la.eigh, but there is an OpenBlas # threading issue that is causing segfaults. # Need schur here since la.eig does not - # guarentee orthogonality in degenerate subspaces + # guarantee orthogonality in degenerate subspaces w, v = la.schur(data, output="complex") w = w.diagonal().real # Check eigenvalues are non-negative diff --git a/qiskit/quantum_info/operators/dihedral/dihedral.py b/qiskit/quantum_info/operators/dihedral/dihedral.py index 75b455410f49..bcd9f6b094ae 100644 --- a/qiskit/quantum_info/operators/dihedral/dihedral.py +++ b/qiskit/quantum_info/operators/dihedral/dihedral.py @@ -97,7 +97,7 @@ class CNOTDihedral(BaseOperator, AdjointMixin): with optimal number of two qubit gates*, `Quantum 4(369), 2020 `_ 2. Andrew W. Cross, Easwar Magesan, Lev S. Bishop, John A. Smolin and Jay M. Gambetta, - *Scalable randomised benchmarking of non-Clifford gates*, + *Scalable randomized benchmarking of non-Clifford gates*, npj Quantum Inf 2, 16012 (2016). """ @@ -325,7 +325,7 @@ def to_circuit(self): with optimal number of two qubit gates*, `Quantum 4(369), 2020 `_ 2. Andrew W. Cross, Easwar Magesan, Lev S. Bishop, John A. Smolin and Jay M. Gambetta, - *Scalable randomised benchmarking of non-Clifford gates*, + *Scalable randomized benchmarking of non-Clifford gates*, npj Quantum Inf 2, 16012 (2016). """ # pylint: disable=cyclic-import diff --git a/qiskit/quantum_info/operators/measures.py b/qiskit/quantum_info/operators/measures.py index 293c1236ed70..8b6350ab6fde 100644 --- a/qiskit/quantum_info/operators/measures.py +++ b/qiskit/quantum_info/operators/measures.py @@ -316,7 +316,7 @@ def cvx_bmat(mat_r, mat_i): iden = sparse.eye(dim_out) # Watrous uses row-vec convention for his Choi matrix while we use - # col-vec. It turns out row-vec convention is requried for CVXPY too + # col-vec. It turns out row-vec convention is required for CVXPY too # since the cvxpy.kron function must have a constant as its first argument. c_r = cvxpy.bmat([[cvxpy.kron(iden, r0_r), x_r], [x_r.T, cvxpy.kron(iden, r1_r)]]) c_i = cvxpy.bmat([[cvxpy.kron(iden, r0_i), x_i], [-x_i.T, cvxpy.kron(iden, r1_i)]]) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 9a5e8732ae68..435120dd531b 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -185,7 +185,7 @@ def __init__(self, data, validate=True, copy=True): isinstance(data, (list, np.ndarray)) and (data_asarray := np.asarray(data, dtype=bool)).ndim == 2 ): - # This little dance is to avoid Numpy 1/2 incompatiblities between the availability + # This little dance is to avoid Numpy 1/2 incompatibilities between the availability # and meaning of the 'copy' argument in 'array' and 'asarray', when the input needs # its dtype converting. 'asarray' prefers to return 'self' if possible in both. if copy and np.may_share_memory(data, data_asarray): diff --git a/qiskit/quantum_info/operators/symplectic/pauli_list.py b/qiskit/quantum_info/operators/symplectic/pauli_list.py index f2e408dd9bd2..2b6e5a8774cb 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli_list.py +++ b/qiskit/quantum_info/operators/symplectic/pauli_list.py @@ -646,7 +646,7 @@ def unique(self, return_index: bool = False, return_counts: bool = False) -> Pau index = index[sort_inds] unique = PauliList(BasePauli(self._z[index], self._x[index], self._phase[index])) - # Concatinate return tuples + # Concatenate return tuples ret = (unique,) if return_index: ret += (index,) diff --git a/qiskit/quantum_info/operators/symplectic/random.py b/qiskit/quantum_info/operators/symplectic/random.py index f9bd65ef9187..06b23ca29803 100644 --- a/qiskit/quantum_info/operators/symplectic/random.py +++ b/qiskit/quantum_info/operators/symplectic/random.py @@ -163,7 +163,7 @@ def _sample_qmallows(n, rng=None): if rng is None: rng = np.random.default_rng() - # Hadmard layer + # Hadamard layer had = np.zeros(n, dtype=bool) # Permutation layer diff --git a/qiskit/quantum_info/states/stabilizerstate.py b/qiskit/quantum_info/states/stabilizerstate.py index 4ae16c32bf54..16abb67f2237 100644 --- a/qiskit/quantum_info/states/stabilizerstate.py +++ b/qiskit/quantum_info/states/stabilizerstate.py @@ -297,7 +297,7 @@ def expectation_value(self, oper: Pauli, qargs: None | list = None) -> complex: # Otherwise pauli is (-1)^a prod_j S_j^b_j for Clifford stabilizers # If pauli anti-commutes with D_j then b_j = 1. - # Multiply pauli by stabilizers with anti-commuting destabilizers + # Multiply pauli by stabilizers with anti-commuting destabilisers pauli_z = (pauli.z).copy() # Make a copy of pauli.z for p in range(num_qubits): # Check if destabilizer anti-commutes @@ -646,7 +646,7 @@ def _rowsum_nondeterministic(clifford, accum, row): @staticmethod def _rowsum_deterministic(clifford, aux_pauli, row): - """Updating an auxilary Pauli aux_pauli in the + """Updating an auxiliary Pauli aux_pauli in the deterministic rowsum calculation. The StabilizerState itself is not updated.""" @@ -680,8 +680,8 @@ def _get_probabilities( Args: qubits (range): range of qubits outcome (list[str]): outcome being built - outcome_prob (float): probabilitiy of the outcome - probs (dict[str, float]): holds the outcomes and probabilitiy results + outcome_prob (float): probability of the outcome + probs (dict[str, float]): holds the outcomes and probability results outcome_bitstring (str): target outcome to measure which reduces measurements, None if not targeting a specific target """ @@ -694,7 +694,7 @@ def _get_probabilities( if outcome[i] == "X": # Retrieve the qubit for the current measurement qubit = qubits[(len(qubits) - i - 1)] - # Determine if the probabilitiy is deterministic + # Determine if the probability is deterministic if not any(ret.clifford.stab_x[:, qubit]): single_qubit_outcome: np.int64 = ret._measure_and_update(qubit, 0) if outcome_bitstring is None or ( diff --git a/qiskit/result/counts.py b/qiskit/result/counts.py index 8168a3d21900..b34aa2373fb3 100644 --- a/qiskit/result/counts.py +++ b/qiskit/result/counts.py @@ -101,7 +101,7 @@ def __init__(self, data, time_taken=None, creg_sizes=None, memory_slots=None): else: raise TypeError( "Invalid input key type %s, must be either an int " - "key or string key with hexademical value or bit string" + "key or string key with hexadecimal value or bit string" ) header = {} self.creg_sizes = creg_sizes diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 2fc9ca5bdb29..21bea89b6575 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -412,7 +412,7 @@ def _calc_pauli_diff(cliff, cliff_target): def synth_clifford_depth_lnn(cliff): - """Synthesis of a :class:`.Clifford` into layers for linear-nearest neighbour connectivity. + """Synthesis of a :class:`.Clifford` into layers for linear-nearest neighbor connectivity. The depth of the synthesized n-qubit circuit is bounded by :math:`7n+2`, which is not optimal. It should be replaced by a better algorithm that provides depth bounded by :math:`7n-4` [3]. diff --git a/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_full.py b/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_full.py index 8131458b2d36..ae56f9926da5 100644 --- a/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_full.py +++ b/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_full.py @@ -40,7 +40,7 @@ def synth_cnotdihedral_full(elem: CNOTDihedral) -> QuantumCircuit: with optimal number of two qubit gates*, `Quantum 4(369), 2020 `_ 2. Andrew W. Cross, Easwar Magesan, Lev S. Bishop, John A. Smolin and Jay M. Gambetta, - *Scalable randomised benchmarking of non-Clifford gates*, + *Scalable randomized benchmarking of non-Clifford gates*, npj Quantum Inf 2, 16012 (2016). """ diff --git a/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_general.py b/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_general.py index 83c63026a21c..bedc5c735f0b 100644 --- a/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_general.py +++ b/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_general.py @@ -38,7 +38,7 @@ def synth_cnotdihedral_general(elem: CNOTDihedral) -> QuantumCircuit: References: 1. Andrew W. Cross, Easwar Magesan, Lev S. Bishop, John A. Smolin and Jay M. Gambetta, - *Scalable randomised benchmarking of non-Clifford gates*, + *Scalable randomized benchmarking of non-Clifford gates*, npj Quantum Inf 2, 16012 (2016). """ diff --git a/qiskit/synthesis/discrete_basis/solovay_kitaev.py b/qiskit/synthesis/discrete_basis/solovay_kitaev.py index e1db47beaeff..2e5cfeafcecd 100644 --- a/qiskit/synthesis/discrete_basis/solovay_kitaev.py +++ b/qiskit/synthesis/discrete_basis/solovay_kitaev.py @@ -109,7 +109,7 @@ def run( gate_matrix_su2 = GateSequence.from_matrix(z * gate_matrix) global_phase = np.arctan2(np.imag(z), np.real(z)) - # get the decompositon as GateSequence type + # get the decomposition as GateSequence type decomposition = self._recurse(gate_matrix_su2, recursion_degree, check_input=check_input) # simplify diff --git a/qiskit/synthesis/linear/linear_depth_lnn.py b/qiskit/synthesis/linear/linear_depth_lnn.py index 2d544f37ef94..2811b755fa42 100644 --- a/qiskit/synthesis/linear/linear_depth_lnn.py +++ b/qiskit/synthesis/linear/linear_depth_lnn.py @@ -210,7 +210,7 @@ def _north_west_to_identity(n, mat): def _optimize_cx_circ_depth_5n_line(mat): # Optimize CX circuit in depth bounded by 5n for LNN connectivity. # The algorithm [1] has two steps: - # a) transform the originl matrix to a north-west matrix (m2nw), + # a) transform the original matrix to a north-west matrix (m2nw), # b) transform the north-west matrix to identity (nw2id). # # A square n-by-n matrix A is called north-west if A[i][j]=0 for all i+j>=n diff --git a/qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py b/qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py index 23f24e07eab3..c0956ea3bc7c 100644 --- a/qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py +++ b/qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py @@ -39,7 +39,7 @@ def _initialize_phase_schedule(mat_z): """ Given a CZ layer (represented as an n*n CZ matrix Mz) - Return a scheudle of phase gates implementing Mz in a SWAP-only netwrok + Return a schedule of phase gates implementing Mz in a SWAP-only netwrok (c.f. Alg 1, [2]) """ n = len(mat_z) @@ -173,7 +173,7 @@ def _apply_phase_to_nw_circuit(n, phase_schedule, seq, swap_plus): of exactly n layers of boxes, each being either a SWAP or a SWAP+. That is, each northwest diagonalization circuit can be uniquely represented by which of its n(n-1)/2 boxes are SWAP+ and which are SWAP. - Return a QuantumCircuit that computes the phase scheudle S inside CX + Return a QuantumCircuit that computes the phase schedule S inside CX """ cir = QuantumCircuit(n) @@ -217,7 +217,7 @@ def _apply_phase_to_nw_circuit(n, phase_schedule, seq, swap_plus): def synth_cx_cz_depth_line_my(mat_x: np.ndarray, mat_z: np.ndarray) -> QuantumCircuit: """ - Joint synthesis of a -CZ-CX- circuit for linear nearest neighbour (LNN) connectivity, + Joint synthesis of a -CZ-CX- circuit for linear nearest neighbor (LNN) connectivity, with 2-qubit depth at most 5n, based on Maslov and Yang. This method computes the CZ circuit inside the CX circuit via phase gate insertions. diff --git a/qiskit/synthesis/linear_phase/cz_depth_lnn.py b/qiskit/synthesis/linear_phase/cz_depth_lnn.py index 6dc7db5d619b..7a195f0caf96 100644 --- a/qiskit/synthesis/linear_phase/cz_depth_lnn.py +++ b/qiskit/synthesis/linear_phase/cz_depth_lnn.py @@ -119,7 +119,7 @@ def _create_patterns(n): def synth_cz_depth_line_mr(mat: np.ndarray) -> QuantumCircuit: - r"""Synthesis of a CZ circuit for linear nearest neighbour (LNN) connectivity, + r"""Synthesis of a CZ circuit for linear nearest neighbor (LNN) connectivity, based on Maslov and Roetteler. Note that this method *reverts* the order of qubits in the circuit, diff --git a/qiskit/synthesis/stabilizer/stabilizer_decompose.py b/qiskit/synthesis/stabilizer/stabilizer_decompose.py index c43747105d04..ecdc1b3257ef 100644 --- a/qiskit/synthesis/stabilizer/stabilizer_decompose.py +++ b/qiskit/synthesis/stabilizer/stabilizer_decompose.py @@ -166,7 +166,7 @@ def _calc_pauli_diff_stabilizer(cliff, cliff_target): def synth_stabilizer_depth_lnn(stab: StabilizerState) -> QuantumCircuit: - """Synthesis of an n-qubit stabilizer state for linear-nearest neighbour connectivity, + """Synthesis of an n-qubit stabilizer state for linear-nearest neighbor connectivity, in 2-qubit depth :math:`2n+2` and two distinct CX layers, using :class:`.CXGate`\\ s and phase gates (:class:`.SGate`, :class:`.SdgGate` or :class:`.ZGate`). diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py index 26a5b52521b2..3269797827e2 100644 --- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py +++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py @@ -782,7 +782,7 @@ def __call__(self, mat): # This weird duplicated lazy structure is for backwards compatibility; Qiskit has historically # always made ``two_qubit_cnot_decompose`` available publicly immediately on import, but it's quite -# expensive to construct, and we want to defer the obejct's creation until it's actually used. We +# expensive to construct, and we want to defer the object's creation until it's actually used. We # only need to pass through the public methods that take `self` as a parameter. Using `__getattr__` # doesn't work because it is only called if the normal resolution methods fail. Using # `__getattribute__` is too messy for a simple one-off use object. diff --git a/qiskit/synthesis/unitary/aqc/cnot_structures.py b/qiskit/synthesis/unitary/aqc/cnot_structures.py index 8659f0c2c343..978b1fc84e66 100644 --- a/qiskit/synthesis/unitary/aqc/cnot_structures.py +++ b/qiskit/synthesis/unitary/aqc/cnot_structures.py @@ -133,7 +133,7 @@ def _get_connectivity(num_qubits: int, connectivity: str) -> dict: links = {i: list(range(num_qubits)) for i in range(num_qubits)} elif connectivity == "line": - # Every qubit is connected to its immediate neighbours only. + # Every qubit is connected to its immediate neighbors only. links = {i: [i - 1, i, i + 1] for i in range(1, num_qubits - 1)} # first qubit diff --git a/qiskit/synthesis/unitary/qsd.py b/qiskit/synthesis/unitary/qsd.py index 80a8afc1311b..525daa3caf15 100644 --- a/qiskit/synthesis/unitary/qsd.py +++ b/qiskit/synthesis/unitary/qsd.py @@ -269,7 +269,7 @@ def _apply_a2(circ): # rolling over diagonals ind2 = None # lint for ind1, ind2 in zip(ind2q[0:-1:], ind2q[1::]): - # get neigboring 2q gates separated by controls + # get neighboring 2q gates separated by controls instr1 = ccirc.data[ind1] mat1 = Operator(instr1.operation).data instr2 = ccirc.data[ind2] diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 936613744b84..f2e752dd94f5 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -466,7 +466,7 @@ def discover_vertex(self, v, score): score, ) self._basis_transforms.append((gate.name, gate.num_qubits, rule.params, rule.circuit)) - # we can stop the search if we have found all gates in the original ciruit. + # we can stop the search if we have found all gates in the original circuit. if not self._source_gates_remain: # if we start from source gates and apply `basis_transforms` in reverse order, we'll end # up with gates in the target basis. Note though that `basis_transforms` may include @@ -548,7 +548,7 @@ def _basis_search(equiv_lib, source_basis, target_basis): if not source_basis: return [] - # This is only neccessary since gates in target basis are currently reported by + # This is only necessary since gates in target basis are currently reported by # their names and we need to have in addition the number of qubits they act on. target_basis_keys = [key for key in equiv_lib.keys() if key.name in target_basis] diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 31609b878683..2fb9a1890bd5 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -144,7 +144,7 @@ def __init__( with the ``routing_pass`` argument and an error will be raised if both are used. layout_trials (int): The number of random seed trials to run - layout with. When > 1 the trial that resuls in the output with + layout with. When > 1 the trial that results in the output with the fewest swap gates will be selected. If this is not specified (and ``routing_pass`` is not set) then the number of local physical CPUs will be used as the default value. This option is @@ -420,7 +420,7 @@ def _inner_run(self, dag, coupling_map, starting_layouts=None): ) def _ancilla_allocation_no_pass_manager(self, dag): - """Run the ancilla-allocation and -enlargment passes on the DAG chained onto our + """Run the ancilla-allocation and -enlargement passes on the DAG chained onto our ``property_set``, skipping the DAG-to-circuit conversion cost of using a ``PassManager``.""" ancilla_pass = FullAncillaAllocation(self.coupling_map) ancilla_pass.property_set = self.property_set diff --git a/qiskit/transpiler/passes/layout/vf2_layout.py b/qiskit/transpiler/passes/layout/vf2_layout.py index 626f8f2b0fa3..2e799ffa4d95 100644 --- a/qiskit/transpiler/passes/layout/vf2_layout.py +++ b/qiskit/transpiler/passes/layout/vf2_layout.py @@ -195,7 +195,7 @@ def mapping_to_layout(layout_mapping): if len(cm_graph) == len(im_graph): chosen_layout = mapping_to_layout(layout_mapping) break - # If there is no error map avilable we can just skip the scoring stage as there + # If there is no error map available we can just skip the scoring stage as there # is nothing to score with, so any match is the best we can find. if self.avg_error_map is None: chosen_layout = mapping_to_layout(layout_mapping) diff --git a/qiskit/transpiler/passes/optimization/commutative_cancellation.py b/qiskit/transpiler/passes/optimization/commutative_cancellation.py index 68d40f3650a2..4c6c487a0ea3 100644 --- a/qiskit/transpiler/passes/optimization/commutative_cancellation.py +++ b/qiskit/transpiler/passes/optimization/commutative_cancellation.py @@ -75,7 +75,7 @@ def run(self, dag): var_z_gate = None z_var_gates = [gate for gate in dag.count_ops().keys() if gate in self._var_z_map] if z_var_gates: - # priortize z gates in circuit + # prioritize z gates in circuit var_z_gate = self._var_z_map[next(iter(z_var_gates))] else: z_var_gates = [gate for gate in self.basis if gate in self._var_z_map] diff --git a/qiskit/transpiler/passes/optimization/template_matching/backward_match.py b/qiskit/transpiler/passes/optimization/template_matching/backward_match.py index a4b11a33de2d..d194d1cbbddf 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/backward_match.py +++ b/qiskit/transpiler/passes/optimization/template_matching/backward_match.py @@ -622,7 +622,7 @@ def run_backward_match(self): ) self.matching_list.append_scenario(new_matching_scenario) - # Third option: if blocking the succesors breaks a match, we consider + # Third option: if blocking the successors breaks a match, we consider # also the possibility to block all predecessors (push the gate to the left). if broken_matches and all(global_broken): diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 06c5186d284c..44689894176a 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -507,7 +507,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): to_native_symbolic = lambda x: x circuit_params, template_params = [], [] - # Set of all parameter names that are present in the circuits to be optimised. + # Set of all parameter names that are present in the circuits to be optimized. circuit_params_set = set() template_dag_dep = copy.deepcopy(self.template_dag_dep) diff --git a/qiskit/transpiler/passes/routing/star_prerouting.py b/qiskit/transpiler/passes/routing/star_prerouting.py index b79a298ad595..3679e8bfb8e3 100644 --- a/qiskit/transpiler/passes/routing/star_prerouting.py +++ b/qiskit/transpiler/passes/routing/star_prerouting.py @@ -260,7 +260,7 @@ def run(self, dag): # star block by a linear sequence of gates new_dag, qubit_mapping = self.star_preroute(dag, star_blocks, processing_order) - # Fix output permuation -- copied from ElidePermutations + # Fix output permutation -- copied from ElidePermutations input_qubit_mapping = {qubit: index for index, qubit in enumerate(dag.qubits)} self.property_set["original_layout"] = Layout(input_qubit_mapping) if self.property_set["original_qubit_indices"] is None: diff --git a/qiskit/transpiler/passes/scheduling/alignments/__init__.py b/qiskit/transpiler/passes/scheduling/alignments/__init__.py index 513144937ab5..8478f241c267 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/__init__.py +++ b/qiskit/transpiler/passes/scheduling/alignments/__init__.py @@ -44,7 +44,7 @@ multiple of this value. Violation of this constraint may result in the backend execution failure. - In most of the senarios, the scheduled start time of ``DAGOpNode`` corresponds to the + In most of the scenarios, the scheduled start time of ``DAGOpNode`` corresponds to the start time of the underlying pulse instruction composing the node operation. However, this assumption can be intentionally broken by defining a pulse gate, i.e. calibration, with the schedule involving pre-buffer, i.e. some random pulse delay @@ -62,7 +62,7 @@ This value is reported by ``timing_constraints["granularity"]`` in the backend configuration in units of dt. This is the constraint for a single pulse :class:`Play` instruction that may constitute your pulse gate. - The length of waveform samples should be multipel of this constraint value. + The length of waveform samples should be multiple of this constraint value. Violation of this constraint may result in failue in backend execution. Minimum pulse length constraint diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 4085844a4709..e9076c5c637b 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -68,7 +68,7 @@ class BaseSchedulerTransform(TransformationPass): However, such optimization should be done by another pass, otherwise scheduling may break topological ordering of the original circuit. - Realistic control flow scheduling respecting for microarcitecture + Realistic control flow scheduling respecting for microarchitecture In the dispersive QND readout scheme, qubit is measured with microwave stimulus to qubit (Q) followed by resonator ring-down (depopulation). This microwave signal is recorded diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index 12f4bc515b29..7be0e838ebff 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -130,7 +130,7 @@ def __init__( will be used [d/2, d, d, ..., d, d, d/2]. skip_reset_qubits (bool): if True, does not insert DD on idle periods that immediately follow initialized/reset qubits (as - qubits in the ground state are less susceptile to decoherence). + qubits in the ground state are less susceptible to decoherence). target (Target): The :class:`~.Target` representing the target backend, if both ``durations`` and this are specified then this argument will take precedence and ``durations`` will be ignored. diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index a90f0c339ced..4ce17e7bc261 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -202,7 +202,7 @@ def _apply_scheduled_op( ): """Add new operation to DAG with scheduled information. - This is identical to apply_operation_back + updating the node_start_time propety. + This is identical to apply_operation_back + updating the node_start_time property. Args: dag: DAG circuit on which the sequence is applied. diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index 806e001f2bda..45333de009bd 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -128,7 +128,7 @@ def __init__( will be used [d/2, d, d, ..., d, d, d/2]. skip_reset_qubits: If True, does not insert DD on idle periods that immediately follow initialized/reset qubits - (as qubits in the ground state are less susceptile to decoherence). + (as qubits in the ground state are less susceptible to decoherence). pulse_alignment: The hardware constraints for gate timing allocation. This is usually provided from ``backend.configuration().timing_constraints``. If provided, the delay length, i.e. ``spacing``, is implicitly adjusted to @@ -311,7 +311,7 @@ def _pad( # slack = 992 dt - 4 x 160 dt = 352 dt # # unconstraind sequence: 44dt-X1-88dt-Y2-88dt-X3-88dt-Y4-44dt - # constraind sequence : 32dt-X1-80dt-Y2-80dt-X3-80dt-Y4-32dt + extra slack 48 dt + # constrained sequence : 32dt-X1-80dt-Y2-80dt-X3-80dt-Y4-32dt + extra slack 48 dt # # Now we evenly split extra slack into start and end of the sequence. # The distributed slack should be multiple of 16. diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index a30411d16a93..7db48d6d1395 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -414,7 +414,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if self.method == "default": # If the method is the default, we only need to evaluate one set of keyword arguments. # To simplify later logic, and avoid cases where static analysis might complain that we - # haven't initialised the "default" handler, we rebind the names so they point to the + # haven't initialized the "default" handler, we rebind the names so they point to the # same object as the chosen method. default_method = plugin_method default_kwargs = plugin_kwargs diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 98b471f6f7f0..79493ae8ad25 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -67,7 +67,7 @@ class GateDirection(TransformationPass): └──────┘ └───┘└──────┘└───┘ This pass assumes that the positions of the qubits in the :attr:`.DAGCircuit.qubits` attribute - are the physical qubit indicies. For example if ``dag.qubits[0]`` is qubit 0 in the + are the physical qubit indices. For example if ``dag.qubits[0]`` is qubit 0 in the :class:`.CouplingMap` or :class:`.Target`. """ diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index f590a1510bda..c905d6142144 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -338,7 +338,7 @@ def __init__(self, stages: Iterable[str] | None = None, **kwargs) -> None: "scheduling", ] self._validate_stages(stages) - # Set through parent class since `__setattr__` requieres `expanded_stages` to be defined + # Set through parent class since `__setattr__` requires `expanded_stages` to be defined super().__setattr__("_stages", tuple(stages)) super().__setattr__("_expanded_stages", tuple(self._generate_expanded_stages())) super().__init__() diff --git a/qiskit/utils/classtools.py b/qiskit/utils/classtools.py index 1e58b1ad2b91..7dae35d1349e 100644 --- a/qiskit/utils/classtools.py +++ b/qiskit/utils/classtools.py @@ -31,7 +31,7 @@ class _lift_to_method: # pylint: disable=invalid-name returned unchanged if so, otherwise it is turned into the default implementation for functions, which makes them bindable to instances. - Python-space functions and lambdas already have this behaviour, but builtins like ``print`` + Python-space functions and lambdas already have this behavior, but builtins like ``print`` don't; using this class allows us to do:: wrap_method(MyClass, "maybe_mutates_arguments", before=print, after=print) @@ -49,7 +49,7 @@ def __new__(cls, method): def __init__(self, method): if method is self: - # Prevent double-initialisation if we are passed an instance of this object to lift. + # Prevent double-initialization if we are passed an instance of this object to lift. return self._method = method @@ -118,7 +118,7 @@ def out(*args, **kwargs): def wrap_method(cls: Type, name: str, *, before: Callable = None, after: Callable = None): - """Wrap the functionality the instance- or class method ``cls.name`` with additional behaviour + """Wrap the functionality the instance- or class method ``cls.name`` with additional behavior ``before`` and ``after``. This mutates ``cls``, replacing the attribute ``name`` with the new functionality. This is diff --git a/qiskit/utils/lazy_tester.py b/qiskit/utils/lazy_tester.py index f2c4c3803150..58c5931fd5ea 100644 --- a/qiskit/utils/lazy_tester.py +++ b/qiskit/utils/lazy_tester.py @@ -174,7 +174,7 @@ def require_in_instance(self, feature_or_class: str) -> Callable[[Type], Type]: def require_in_instance(self, feature_or_class): """A class decorator that requires the dependency is available when the class is - initialised. This decorator can be used even if the class does not define an ``__init__`` + initialized. This decorator can be used even if the class does not define an ``__init__`` method. Args: @@ -186,7 +186,7 @@ def require_in_instance(self, feature_or_class): Returns: Callable: a class decorator that ensures that the wrapped feature is present if the - class is initialised. + class is initialized. """ if isinstance(feature_or_class, str): feature = feature_or_class diff --git a/qiskit/utils/optionals.py b/qiskit/utils/optionals.py index f2b1e56e1120..f2e6c860faaa 100644 --- a/qiskit/utils/optionals.py +++ b/qiskit/utils/optionals.py @@ -79,7 +79,7 @@ * - .. py:data:: HAS_IPYTHON - If `the IPython kernel `__ is available, certain additional - visualisations and line magics are made available. + visualizations and line magics are made available. * - .. py:data:: HAS_IPYWIDGETS - Monitoring widgets for jobs running on external backends can be provided if `ipywidgets @@ -94,7 +94,7 @@ interactivity features. * - .. py:data:: HAS_MATPLOTLIB - - Qiskit provides several visualisation tools in the :mod:`.visualization` module. + - Qiskit provides several visualization tools in the :mod:`.visualization` module. Almost all of these are built using `Matplotlib `__, which must be installed in order to use them. @@ -116,7 +116,7 @@ :class:`.DAGCircuit` in certain modes. * - .. py:data:: HAS_PYDOT - - For some graph visualisations, Qiskit uses `pydot `__ as an + - For some graph visualizations, Qiskit uses `pydot `__ as an interface to GraphViz (see :data:`HAS_GRAPHVIZ`). * - .. py:data:: HAS_PYGMENTS @@ -134,7 +134,7 @@ `__. * - .. py:data:: HAS_SEABORN - - Qiskit provides several visualisation tools in the :mod:`.visualization` module. Some + - Qiskit provides several visualization tools in the :mod:`.visualization` module. Some of these are built using `Seaborn `__, which must be installed in order to use them. @@ -179,16 +179,16 @@ :widths: 25 75 * - .. py:data:: HAS_GRAPHVIZ - - For some graph visualisations, Qiskit uses the `GraphViz `__ - visualisation tool via its ``pydot`` interface (see :data:`HAS_PYDOT`). + - For some graph visualizations, Qiskit uses the `GraphViz `__ + visualization tool via its ``pydot`` interface (see :data:`HAS_PYDOT`). * - .. py:data:: HAS_PDFLATEX - - Visualisation tools that use LaTeX in their output, such as the circuit drawers, require + - Visualization tools that use LaTeX in their output, such as the circuit drawers, require ``pdflatex`` to be available. You will generally need to ensure that you have a working LaTeX installation available, and the ``qcircuit.tex`` package. * - .. py:data:: HAS_PDFTOCAIRO - - Visualisation tools that convert LaTeX-generated files into rasterised images use the + - Visualization tools that convert LaTeX-generated files into rasterized images use the ``pdftocairo`` tool. This is part of the `Poppler suite of PDF tools `__. diff --git a/qiskit/visualization/circuit/_utils.py b/qiskit/visualization/circuit/_utils.py index c14bb3d46c29..ca29794b9626 100644 --- a/qiskit/visualization/circuit/_utils.py +++ b/qiskit/visualization/circuit/_utils.py @@ -112,7 +112,7 @@ def get_gate_ctrl_text(op, drawer, style=None, calibrations=None): gate_text = gate_text.replace("-", "\\mbox{-}") ctrl_text = f"$\\mathrm{{{ctrl_text}}}$" - # Only captitalize internally-created gate or instruction names + # Only capitalize internally-created gate or instruction names elif ( (gate_text == op.name and op_type not in (Gate, Instruction)) or (gate_text == base_name and base_type not in (Gate, Instruction)) diff --git a/qiskit/visualization/pulse_v2/events.py b/qiskit/visualization/pulse_v2/events.py index 4bb59cd86260..74da24b1db25 100644 --- a/qiskit/visualization/pulse_v2/events.py +++ b/qiskit/visualization/pulse_v2/events.py @@ -196,7 +196,7 @@ def get_waveforms(self) -> Iterator[PulseInstruction]: def get_frame_changes(self) -> Iterator[PulseInstruction]: """Return frame change type instructions with total frame change amount.""" - # TODO parse parametrised FCs correctly + # TODO parse parametrized FCs correctly sorted_frame_changes = sorted(self._frames.items(), key=lambda x: x[0]) diff --git a/releasenotes/config.yaml b/releasenotes/config.yaml index bea33ef99a1e..0c621662ca84 100644 --- a/releasenotes/config.yaml +++ b/releasenotes/config.yaml @@ -87,7 +87,7 @@ template: | New features related to the qiskit.qpy module. features_quantum_info: - | - New features releated to the qiskit.quantum_info module. + New features related to the qiskit.quantum_info module. features_synthesis: - | New features related to the qiskit.synthesis module. @@ -178,7 +178,7 @@ template: | Deprecations related to the qiskit.qpy module. deprecations_quantum_info: - | - Deprecations releated to the qiskit.quantum_info module. + Deprecations related to the qiskit.quantum_info module. deprecations_synthesis: - | Deprecations related to the qiskit.synthesis module. diff --git a/releasenotes/notes/0.12/operator-dot-fd90e7e5ad99ff9b.yaml b/releasenotes/notes/0.12/operator-dot-fd90e7e5ad99ff9b.yaml index 9dd4a241a09d..0e778de9665a 100644 --- a/releasenotes/notes/0.12/operator-dot-fd90e7e5ad99ff9b.yaml +++ b/releasenotes/notes/0.12/operator-dot-fd90e7e5ad99ff9b.yaml @@ -22,4 +22,4 @@ upgrade: from the right hand side of the operation if the left does not have ``__mul__`` defined) implements scalar multiplication (i.e. :meth:`qiskit.quantum_info.Operator.multiply`). Previously both methods - implemented scalar multiplciation. + implemented scalar multiplication. diff --git a/releasenotes/notes/0.13/0.13.0-release-a92553cf72c203aa.yaml b/releasenotes/notes/0.13/0.13.0-release-a92553cf72c203aa.yaml index 30561a9aadc8..71fe512aa778 100644 --- a/releasenotes/notes/0.13/0.13.0-release-a92553cf72c203aa.yaml +++ b/releasenotes/notes/0.13/0.13.0-release-a92553cf72c203aa.yaml @@ -8,7 +8,7 @@ prelude: | structure behind all operations to be based on `retworkx `_ for greatly improved performance. Circuit transpilation speed in the 0.13.0 release should - be significanlty faster than in previous releases. + be significantly faster than in previous releases. There has been a significant simplification to the style in which Pulse instructions are built. Now, ``Command`` s are deprecated and a unified diff --git a/releasenotes/notes/0.13/add-base-job-status-methods-3ab9646c5f5470a6.yaml b/releasenotes/notes/0.13/add-base-job-status-methods-3ab9646c5f5470a6.yaml index 59956851a81f..96fb649b07dd 100644 --- a/releasenotes/notes/0.13/add-base-job-status-methods-3ab9646c5f5470a6.yaml +++ b/releasenotes/notes/0.13/add-base-job-status-methods-3ab9646c5f5470a6.yaml @@ -8,4 +8,4 @@ features: * :meth:`~qiskit.providers.BaseJob.cancelled` * :meth:`~qiskit.providers.BaseJob.in_final_state` - These methods are used to check wheter a job is in a given job status. + These methods are used to check whether a job is in a given job status. diff --git a/releasenotes/notes/0.13/default-schedule-name-51ba198cf08978cd.yaml b/releasenotes/notes/0.13/default-schedule-name-51ba198cf08978cd.yaml index d8df9b53fdc0..24b00a262e0f 100644 --- a/releasenotes/notes/0.13/default-schedule-name-51ba198cf08978cd.yaml +++ b/releasenotes/notes/0.13/default-schedule-name-51ba198cf08978cd.yaml @@ -2,6 +2,6 @@ fixes: - | Fixes a case in :meth:`qiskit.result.Result.get_counts`, where the results - for an expirement could not be referenced if the experiment was initialized + for an experiment could not be referenced if the experiment was initialized as a Schedule without a name. Fixes `#2753 `_ diff --git a/releasenotes/notes/0.13/qinfo-operators-0193871295190bad.yaml b/releasenotes/notes/0.13/qinfo-operators-0193871295190bad.yaml index 8384df8d205f..ba4f07afd895 100644 --- a/releasenotes/notes/0.13/qinfo-operators-0193871295190bad.yaml +++ b/releasenotes/notes/0.13/qinfo-operators-0193871295190bad.yaml @@ -11,7 +11,7 @@ features: the number of two-qubit gates. - | Adds :class:`qiskit.quantum_info.SparsePauliOp` operator class. This is an - efficient representaiton of an N-qubit matrix that is sparse in the Pauli + efficient representation of an N-qubit matrix that is sparse in the Pauli basis and uses a :class:`qiskit.quantum_info.PauliTable` and vector of complex coefficients for its data structure. @@ -23,7 +23,7 @@ features: Numpy arrays or :class:`~qiskit.quantum_info.Operator` objects can be converted to a :class:`~qiskit.quantum_info.SparsePauliOp` using the `:class:`~qiskit.quantum_info.SparsePauliOp.from_operator` method. - :class:`~qiskit.quantum_info.SparsePauliOp` can be convered to a sparse + :class:`~qiskit.quantum_info.SparsePauliOp` can be converted to a sparse csr_matrix or dense Numpy array using the :class:`~qiskit.quantum_info.SparsePauliOp.to_matrix` method, or to an :class:`~qiskit.quantum_info.Operator` object using the @@ -54,7 +54,7 @@ features: :meth:`~qiskit.quantum_info.PauliTable.tensor`) between each element of the first table, with each element of the second table. - * Addition of two tables acts as list concatination of the terms in each + * Addition of two tables acts as list concatenation of the terms in each table (``+``). * Pauli tables can be sorted by lexicographic (tensor product) order or @@ -148,7 +148,7 @@ upgrade: n_qubits = 10 ham = ScalarOp(2 ** n_qubits, coeff=0) - # Add 2-body nearest neighbour terms + # Add 2-body nearest neighbor terms for j in range(n_qubits - 1): ham = ham + ZZ([j, j+1]) - | diff --git a/releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml b/releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml index 9a8a84530832..df465a05c439 100644 --- a/releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml +++ b/releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml @@ -175,7 +175,7 @@ deprecations: The ``add``, ``subtract``, and ``multiply`` methods of the :class:`qiskit.quantum_info.Statevector` and :class:`qiskit.quantum_info.DensityMatrix` classes are deprecated and will - be removed in a future release. Instead you shoulde use ``+``, ``-``, ``*`` + be removed in a future release. Instead you should use ``+``, ``-``, ``*`` binary operators instead. - | Deprecates :meth:`qiskit.quantum_info.Statevector.to_counts`, diff --git a/releasenotes/notes/0.13/quibit-transition-visualization-a62d0d119569fa05.yaml b/releasenotes/notes/0.13/quibit-transition-visualization-a62d0d119569fa05.yaml index 12c69ef47c51..21e14b6ef548 100644 --- a/releasenotes/notes/0.13/quibit-transition-visualization-a62d0d119569fa05.yaml +++ b/releasenotes/notes/0.13/quibit-transition-visualization-a62d0d119569fa05.yaml @@ -6,7 +6,7 @@ features: single qubit gate transitions has been added. It takes in a single qubit circuit and returns an animation of qubit state transitions on a Bloch sphere. To use this function you must have installed - the dependencies for and configured globally a matplotlib animtion + the dependencies for and configured globally a matplotlib animation writer. You can refer to the `matplotlib documentation `_ for more details on this. However, in the default case simply ensuring diff --git a/releasenotes/notes/0.15/parameter-conjugate-a16fd7ae0dc18ede.yaml b/releasenotes/notes/0.15/parameter-conjugate-a16fd7ae0dc18ede.yaml index e30386b5dfd1..3fbb8558afce 100644 --- a/releasenotes/notes/0.15/parameter-conjugate-a16fd7ae0dc18ede.yaml +++ b/releasenotes/notes/0.15/parameter-conjugate-a16fd7ae0dc18ede.yaml @@ -5,4 +5,4 @@ features: been added to the :class:`~qiskit.circuit.ParameterExpression` class. This enables calling ``numpy.conj()`` without raising an error. Since a :class:`~qiskit.circuit.ParameterExpression` object is real, it will - return itself. This behaviour is analogous to Python floats/ints. + return itself. This behavior is analogous to Python floats/ints. diff --git a/releasenotes/notes/0.16/delay-in-circuit-33f0d81783ac12ea.yaml b/releasenotes/notes/0.16/delay-in-circuit-33f0d81783ac12ea.yaml index a9ffc4d508df..bc60745c71d3 100644 --- a/releasenotes/notes/0.16/delay-in-circuit-33f0d81783ac12ea.yaml +++ b/releasenotes/notes/0.16/delay-in-circuit-33f0d81783ac12ea.yaml @@ -43,7 +43,7 @@ features: of scheduled circuits. - | - A new fuction :func:`qiskit.compiler.sequence` has been also added so that + A new function :func:`qiskit.compiler.sequence` has been also added so that we can convert a scheduled circuit into a :class:`~qiskit.pulse.Schedule` to make it executable on a pulse-enabled backend. diff --git a/releasenotes/notes/0.16/fix-bug-in-controlled-unitary-when-setting-ctrl_state-2f9af3b9f0f7903f.yaml b/releasenotes/notes/0.16/fix-bug-in-controlled-unitary-when-setting-ctrl_state-2f9af3b9f0f7903f.yaml index 9b441e252c17..158550d9a09a 100644 --- a/releasenotes/notes/0.16/fix-bug-in-controlled-unitary-when-setting-ctrl_state-2f9af3b9f0f7903f.yaml +++ b/releasenotes/notes/0.16/fix-bug-in-controlled-unitary-when-setting-ctrl_state-2f9af3b9f0f7903f.yaml @@ -6,4 +6,4 @@ fixes: in the creation of the matrix for the controlled unitary and again when calling the :meth:`~qiskit.circuit.ControlledGate.definition` method of the :class:`qiskit.circuit.ControlledGate` class. This would give the - appearence that setting ``ctrl_state`` had no effect. + appearance that setting ``ctrl_state`` had no effect. diff --git a/releasenotes/notes/0.16/remove-dagnode-dict-32fa35479c0a8331.yaml b/releasenotes/notes/0.16/remove-dagnode-dict-32fa35479c0a8331.yaml index a0b69d5333c3..42f696351557 100644 --- a/releasenotes/notes/0.16/remove-dagnode-dict-32fa35479c0a8331.yaml +++ b/releasenotes/notes/0.16/remove-dagnode-dict-32fa35479c0a8331.yaml @@ -3,7 +3,7 @@ upgrade: - | The previously deprecated support for passing in a dictionary as the first positional argument to :class:`~qiskit.dagcircuit.DAGNode` constructor - has been removed. Using a dictonary for the first positional argument + has been removed. Using a dictionary for the first positional argument was deprecated in the 0.13.0 release. To create a :class:`~qiskit.dagcircuit.DAGNode` object now you should directly pass the attributes as kwargs on the constructor. diff --git a/releasenotes/notes/0.17/add-schedule-block-c37527f3205b7b62.yaml b/releasenotes/notes/0.17/add-schedule-block-c37527f3205b7b62.yaml index 0cde632a36b6..fc317417747a 100644 --- a/releasenotes/notes/0.17/add-schedule-block-c37527f3205b7b62.yaml +++ b/releasenotes/notes/0.17/add-schedule-block-c37527f3205b7b62.yaml @@ -49,7 +49,7 @@ deprecations: constructing parameterized pulse programs. - | The :attr:`~qiskit.pulse.channels.Channel.parameters` attribute for - the following clasess: + the following classes: * :py:class:`~qiskit.pulse.channels.Channel` * :py:class:`~qiskit.pulse.instructions.Instruction`. diff --git a/releasenotes/notes/0.17/basicaer-new-provider-ea7cf756df231c2b.yaml b/releasenotes/notes/0.17/basicaer-new-provider-ea7cf756df231c2b.yaml index 85b6a4cd37b7..a688665aea53 100644 --- a/releasenotes/notes/0.17/basicaer-new-provider-ea7cf756df231c2b.yaml +++ b/releasenotes/notes/0.17/basicaer-new-provider-ea7cf756df231c2b.yaml @@ -34,7 +34,7 @@ upgrade: until the simulation finishes executing. If you want to restore the previous async behavior you'll need to wrap the :meth:`~qiskit.providers.basicaer.QasmSimulatorPy.run` with something that - will run in a seperate thread or process like ``futures.ThreadPoolExecutor`` + will run in a separate thread or process like ``futures.ThreadPoolExecutor`` or ``futures.ProcessPoolExecutor``. - | The ``allow_sample_measuring`` option for the diff --git a/releasenotes/notes/0.17/deprecate-schemas-424c29fbd35c90de.yaml b/releasenotes/notes/0.17/deprecate-schemas-424c29fbd35c90de.yaml index dd9b8052e5a9..d627ecfe47c8 100644 --- a/releasenotes/notes/0.17/deprecate-schemas-424c29fbd35c90de.yaml +++ b/releasenotes/notes/0.17/deprecate-schemas-424c29fbd35c90de.yaml @@ -11,7 +11,7 @@ deprecations: deprecation warning). The schema files have been moved to the `Qiskit/ibmq-schemas `__ repository and those should be treated as the canonical versions of the - API schemas. Moving forward only those schemas will recieve updates and + API schemas. Moving forward only those schemas will receive updates and will be used as the source of truth for the schemas. If you were relying on the schemas bundled in qiskit-terra you should update to use that repository instead. diff --git a/releasenotes/notes/0.17/ecr-gate-45cfda1b84ac792c.yaml b/releasenotes/notes/0.17/ecr-gate-45cfda1b84ac792c.yaml index 5ddd2771072e..42aeec0b513b 100644 --- a/releasenotes/notes/0.17/ecr-gate-45cfda1b84ac792c.yaml +++ b/releasenotes/notes/0.17/ecr-gate-45cfda1b84ac792c.yaml @@ -25,7 +25,7 @@ features: - | Two new transpiler passess, :class:`~qiskit.transpiler.GateDirection` and class:`qiskit.transpiler.CheckGateDirection`, were added to the - :mod:`qiskit.transpiler.passes` module. These new passes are inteded to + :mod:`qiskit.transpiler.passes` module. These new passes are intended to be more general replacements for :class:`~qiskit.transpiler.passes.CXDirection` and :class:`~qiskit.transpiler.passes.CheckCXDirection` (which are both now diff --git a/releasenotes/notes/0.17/fix-nlocal-circular-entanglement-0acf0195138b6aa2.yaml b/releasenotes/notes/0.17/fix-nlocal-circular-entanglement-0acf0195138b6aa2.yaml index 1dcd19829554..4adadd4a7c8d 100644 --- a/releasenotes/notes/0.17/fix-nlocal-circular-entanglement-0acf0195138b6aa2.yaml +++ b/releasenotes/notes/0.17/fix-nlocal-circular-entanglement-0acf0195138b6aa2.yaml @@ -5,6 +5,6 @@ fixes: :class:`qiskit.circuit.library.NLocal` circuit class for the edge case where the circuit has the same size as the entanglement block (e.g. a two-qubit circuit and CZ entanglement gates). In this case there should only be one entanglement - gate, but there was accidentially added a second one in the inverse direction as the + gate, but there was accidentally added a second one in the inverse direction as the first. Fixed `Qiskit/qiskit-aqua#1452 `__ diff --git a/releasenotes/notes/0.17/idle-time-visualization-b5404ad875cbdae4.yaml b/releasenotes/notes/0.17/idle-time-visualization-b5404ad875cbdae4.yaml index e306e5a1558e..824a95563080 100644 --- a/releasenotes/notes/0.17/idle-time-visualization-b5404ad875cbdae4.yaml +++ b/releasenotes/notes/0.17/idle-time-visualization-b5404ad875cbdae4.yaml @@ -2,7 +2,7 @@ fixes: - | Fixed an issue with the :func:`qiskit.visualization.timeline_drawer` - function where classical bits were inproperly handled. + function where classical bits were improperly handled. Fixed `#5361 `__ - | Fixed an issue in the :func:`qiskit.visualization.circuit_drawer` function diff --git a/releasenotes/notes/0.17/issue-5751-1b6249f6263c9c30.yaml b/releasenotes/notes/0.17/issue-5751-1b6249f6263c9c30.yaml index e5553896c76d..d7a8d21f974d 100644 --- a/releasenotes/notes/0.17/issue-5751-1b6249f6263c9c30.yaml +++ b/releasenotes/notes/0.17/issue-5751-1b6249f6263c9c30.yaml @@ -17,5 +17,5 @@ features: the :class:`~qiskit.transpiler.passes.TemplateOptimization` pass with the :py:class:`qiskit.transpiler.passes.RZXCalibrationBuilder` pass to automatically find and replace gate sequences, such as - ``CNOT - P(theta) - CNOT``, with more efficent circuits based on + ``CNOT - P(theta) - CNOT``, with more efficient circuits based on :class:`qiskit.circuit.library.RZXGate` with a calibration. diff --git a/releasenotes/notes/0.17/qiskit-version-wrapper-90cb7fcffeaafd6a.yaml b/releasenotes/notes/0.17/qiskit-version-wrapper-90cb7fcffeaafd6a.yaml index 0bc3fbf78fff..f30cea6b27f8 100644 --- a/releasenotes/notes/0.17/qiskit-version-wrapper-90cb7fcffeaafd6a.yaml +++ b/releasenotes/notes/0.17/qiskit-version-wrapper-90cb7fcffeaafd6a.yaml @@ -13,7 +13,7 @@ upgrade: this change. - | The ``qiskit.execute`` module has been renamed to - :mod:`qiskit.execute_function`. This was necessary to avoid a potentical + :mod:`qiskit.execute_function`. This was necessary to avoid a potential name conflict between the :func:`~qiskit.execute_function.execute` function which is re-exported as ``qiskit.execute``. ``qiskit.execute`` the function in some situations could conflict with ``qiskit.execute`` the module which @@ -30,7 +30,7 @@ upgrade: been renamed to ``qiskit.compiler.transpiler``, ``qiskit.compiler.assembler``, ``qiskit.compiler.scheduler``, and ``qiskit.compiler.sequence`` respectively. This was necessary to avoid a - potentical name conflict between the modules and the re-exported function + potential name conflict between the modules and the re-exported function paths :func:`qiskit.compiler.transpile`, :func:`qiskit.compiler.assemble`, :func:`qiskit.compiler.schedule`, and :func:`qiskit.compiler.sequence`. In some situations this name conflict between the module path and diff --git a/releasenotes/notes/0.17/replace-pulse-drawer-f9f667c8f71e1e02.yaml b/releasenotes/notes/0.17/replace-pulse-drawer-f9f667c8f71e1e02.yaml index ef5a733d2a02..a5a75974ed11 100644 --- a/releasenotes/notes/0.17/replace-pulse-drawer-f9f667c8f71e1e02.yaml +++ b/releasenotes/notes/0.17/replace-pulse-drawer-f9f667c8f71e1e02.yaml @@ -15,7 +15,7 @@ features: * Specifying ``axis`` objects for plotting to allow further extension of generated plots, i.e., for publication manipulations. - New stylesheets can take callback functions that dynamically modify the apperance of + New stylesheets can take callback functions that dynamically modify the appearance of the output image, for example, reassembling a collection of channels, showing details of instructions, updating appearance of pulse envelopes, etc... You can create custom callback functions and feed them into a stylesheet instance to diff --git a/releasenotes/notes/0.18/add-pauli-list-5644d695f91de808.yaml b/releasenotes/notes/0.18/add-pauli-list-5644d695f91de808.yaml index bd614d766f50..1c70b64a91f4 100644 --- a/releasenotes/notes/0.18/add-pauli-list-5644d695f91de808.yaml +++ b/releasenotes/notes/0.18/add-pauli-list-5644d695f91de808.yaml @@ -4,7 +4,7 @@ features: A new class, :class:`~qiskit.quantum_info.PauliList`, has been added to the :mod:`qiskit.quantum_info` module. This class is used to efficiently represent a list of :class:`~qiskit.quantum_info.Pauli` - operators. This new class inherets from the same parent class as the + operators. This new class inherits from the same parent class as the existing :class:`~qiskit.quantum_info.PauliTable` (and therefore can be mostly used interchangeably), however it differs from the :class:`~qiskit.quantum_info.PauliTable` diff --git a/releasenotes/notes/0.19/fix-infinite-job-submissions-d6f6a583535ca798.yaml b/releasenotes/notes/0.19/fix-infinite-job-submissions-d6f6a583535ca798.yaml index b4b2bbbd8b37..c37fe6bbcdc6 100644 --- a/releasenotes/notes/0.19/fix-infinite-job-submissions-d6f6a583535ca798.yaml +++ b/releasenotes/notes/0.19/fix-infinite-job-submissions-d6f6a583535ca798.yaml @@ -5,7 +5,7 @@ features: to limit the number of times a job will attempt to be executed on a backend. Previously the submission and fetching of results would be attempted infinitely, even if the job was cancelled or errored on the backend. The - default is now 50, and the previous behaviour can be achieved by setting + default is now 50, and the previous behavior can be achieved by setting ``max_job_tries=-1``. Fixes `#6872 `__ and `#6821 `__. diff --git a/releasenotes/notes/0.19/gates-in-basis-pass-337f6637e61919db.yaml b/releasenotes/notes/0.19/gates-in-basis-pass-337f6637e61919db.yaml index 2da1ce1a40d7..fbde9d704a17 100644 --- a/releasenotes/notes/0.19/gates-in-basis-pass-337f6637e61919db.yaml +++ b/releasenotes/notes/0.19/gates-in-basis-pass-337f6637e61919db.yaml @@ -13,7 +13,7 @@ features: from qiskit.circuit import QuantumCircuit from qiskit.transpiler.passes import GatesInBasis - # Instatiate Pass + # Instantiate Pass basis_gates = ["cx", "h"] basis_check_pass = GatesInBasis(basis_gates) # Build circuit diff --git a/releasenotes/notes/0.19/measure_all-add_bits-8525317935197b90.yaml b/releasenotes/notes/0.19/measure_all-add_bits-8525317935197b90.yaml index d5737bba8e57..bdfca2b5c577 100644 --- a/releasenotes/notes/0.19/measure_all-add_bits-8525317935197b90.yaml +++ b/releasenotes/notes/0.19/measure_all-add_bits-8525317935197b90.yaml @@ -2,7 +2,7 @@ features: - | Added a new parameter, ``add_bits``, to :meth:`.QuantumCircuit.measure_all`. - By default it is set to ``True`` to maintain the previous behaviour of adding a new :obj:`.ClassicalRegister` of the same size as the number of qubits to store the measurements. + By default it is set to ``True`` to maintain the previous behavior of adding a new :obj:`.ClassicalRegister` of the same size as the number of qubits to store the measurements. If set to ``False``, the measurements will be stored in the already existing classical bits. For example, if you created a circuit with existing classical bits like:: diff --git a/releasenotes/notes/0.19/mpl-bump-33a1240266e66508.yaml b/releasenotes/notes/0.19/mpl-bump-33a1240266e66508.yaml index 16e636ab7c94..c553ee8f0ca6 100644 --- a/releasenotes/notes/0.19/mpl-bump-33a1240266e66508.yaml +++ b/releasenotes/notes/0.19/mpl-bump-33a1240266e66508.yaml @@ -10,5 +10,5 @@ upgrade: deprecated the use of APIs around 3D visualizations that were compatible with older releases and second installing older versions of Matplotlib was becoming increasingly difficult as matplotlib's upstream dependencies - have caused incompatiblities that made testing moving forward more + have caused incompatibilities that made testing moving forward more difficult. diff --git a/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml b/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml index 69d60ee1545f..26058e03f3dc 100644 --- a/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml +++ b/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml @@ -25,7 +25,7 @@ features: - | Added the :class:`~qiskit.result.LocalReadoutMitigator` class for performing measurement readout error mitigation of local measurement - errors. Local measuerment errors are those that are described by a + errors. Local measurement errors are those that are described by a tensor-product of single-qubit measurement errors. This class can be initialized with a list of :math:`N` single-qubit of @@ -40,7 +40,7 @@ features: performing measurement readout error mitigation of correlated measurement errors. This class can be initialized with a single :math:`2^N \times 2^N` measurement error assignment matrix that descirbes the error probabilities. - Mitigation is implemented via inversion of assigment matrix which has + Mitigation is implemented via inversion of assignment matrix which has mitigation complexity of :math:`O(4^N)` of :class:`~qiskit.result.QuasiDistribution` and expectation values. - | diff --git a/releasenotes/notes/0.19/remove-manual-warning-filters-028646b73bb86860.yaml b/releasenotes/notes/0.19/remove-manual-warning-filters-028646b73bb86860.yaml index 15ee5cc07ce6..1ec1774e680a 100644 --- a/releasenotes/notes/0.19/remove-manual-warning-filters-028646b73bb86860.yaml +++ b/releasenotes/notes/0.19/remove-manual-warning-filters-028646b73bb86860.yaml @@ -2,12 +2,12 @@ upgrade: - | An internal filter override that caused all Qiskit deprecation warnings to - be displayed has been removed. This means that the behaviour will now - revert to the standard Python behaviour for deprecations; you should only + be displayed has been removed. This means that the behavior will now + revert to the standard Python behavior for deprecations; you should only see a ``DeprecationWarning`` if it was triggered by code in the main script file, interpreter session or Jupyter notebook. The user will no longer be blamed with a warning if internal Qiskit functions call deprecated - behaviour. If you write libraries, you should occasionally run with the + behavior. If you write libraries, you should occasionally run with the default warning filters disabled, or have tests which always run with them disabled. See the `Python documentation on warnings`_, and in particular the `section on testing for deprecations`_ for more information on how to do this. @@ -16,7 +16,7 @@ upgrade: .. _section on testing for deprecations: https://docs.python.org/3/library/warnings.html#updating-code-for-new-versions-of-dependencies - | Certain warnings used to be only issued once, even if triggered from - multiple places. This behaviour has been removed, so it is possible that if + multiple places. This behavior has been removed, so it is possible that if you call deprecated functions, you may see more warnings than you did before. You should change any deprecated function calls to the suggested versions, because the deprecated forms will be removed in future Qiskit diff --git a/releasenotes/notes/0.19/sparse-pauli-internal-8226b4f57a61b982.yaml b/releasenotes/notes/0.19/sparse-pauli-internal-8226b4f57a61b982.yaml index 4ce25f211465..a9623206ecdb 100644 --- a/releasenotes/notes/0.19/sparse-pauli-internal-8226b4f57a61b982.yaml +++ b/releasenotes/notes/0.19/sparse-pauli-internal-8226b4f57a61b982.yaml @@ -12,5 +12,5 @@ upgrade: The return type of :func:`~qiskit.quantum_info.pauli_basis` will change from :class:`~qiskit.quantum_info.PauliTable` to :class:`~qiskit.quantum_info.PauliList` in a future release of Qiskit Terra. - To immediately swap to the new behaviour, pass the keyword argument + To immediately swap to the new behavior, pass the keyword argument ``pauli_list=True``. diff --git a/releasenotes/notes/0.19/vf2layout-4cea88087c355769.yaml b/releasenotes/notes/0.19/vf2layout-4cea88087c355769.yaml index 07c0ce9178df..74d66c7558cf 100644 --- a/releasenotes/notes/0.19/vf2layout-4cea88087c355769.yaml +++ b/releasenotes/notes/0.19/vf2layout-4cea88087c355769.yaml @@ -7,7 +7,7 @@ features: `__ to find a perfect layout (a layout which would not require additional routing) if one exists. The functionality exposed by this new pass is very - similar to exisiting :class:`~qiskit.transpiler.passes.CSPLayout` but + similar to existing :class:`~qiskit.transpiler.passes.CSPLayout` but :class:`~qiskit.transpiler.passes.VF2Layout` is significantly faster. .. _VF2 algorithm: https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.101.5342&rep=rep1&type=pdf diff --git a/releasenotes/notes/0.20/expose-tolerances-z2symmetries-9c444a7b1237252e.yaml b/releasenotes/notes/0.20/expose-tolerances-z2symmetries-9c444a7b1237252e.yaml index e6dc2829ca02..db9b15d86252 100644 --- a/releasenotes/notes/0.20/expose-tolerances-z2symmetries-9c444a7b1237252e.yaml +++ b/releasenotes/notes/0.20/expose-tolerances-z2symmetries-9c444a7b1237252e.yaml @@ -35,7 +35,7 @@ features: SparsePauliOp(['X', 'Y'], coeffs=[1.+0.j, 0.+1.j]) - Note that the chop method does not accumulate the coefficents of the same Paulis, e.g. + Note that the chop method does not accumulate the coefficients of the same Paulis, e.g. .. code-block:: diff --git a/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml b/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml index af5f158ecb17..5f79ace148ed 100644 --- a/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml +++ b/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml @@ -12,7 +12,7 @@ features: Previously, the pass chain would have been implemented as ``scheduling -> alignment`` which were both transform passes thus there were multiple :class:`~.DAGCircuit` - instances recreated during each pass. In addition, scheduling occured in each pass + instances recreated during each pass. In addition, scheduling occurred in each pass to obtain instruction start time. Now the required pass chain becomes ``scheduling -> alignment -> padding`` where the :class:`~.DAGCircuit` update only occurs at the end with the ``padding`` pass. @@ -59,7 +59,7 @@ features: The :class:`~.ConstrainedReschedule` pass considers both hardware alignment constraints that can be definied in a :class:`.BackendConfiguration` object, ``pulse_alignment`` and ``acquire_alignment``. This new class superscedes - the previosuly existing :class:`~.AlignMeasures` as it performs the same alignment + the previously existing :class:`~.AlignMeasures` as it performs the same alignment (via the property set) for measurement instructions in addition to general instruction alignment. By setting the ``acquire_alignment`` constraint argument for the :class:`~.ConstrainedReschedule` pass it is a drop-in replacement of @@ -67,7 +67,7 @@ features: - | Added two new transpiler passes :class:`~.ALAPScheduleAnalysis` and :class:`~.ASAPScheduleAnalysis` which superscede the :class:`~.ALAPSchedule` and :class:`~.ASAPSchedule` as part of the - reworked transpiler workflow for schedling. The new passes perform the same scheduling but + reworked transpiler workflow for scheduling. The new passes perform the same scheduling but in the property set and relying on a :class:`~.BasePadding` pass to adjust the circuit based on all the scheduling alignment analysis. @@ -155,5 +155,5 @@ features: Added a new transpiler pass :class:`~.PadDynamicalDecoupling` which superscedes the :class:`~.DynamicalDecoupling` pass as part of the reworked transpiler workflow for scheduling. This new pass will insert dynamical decoupling - sequences into the circuit per any scheduling and alignment analysis that occured in earlier + sequences into the circuit per any scheduling and alignment analysis that occurred in earlier passes. diff --git a/releasenotes/notes/0.20/vf2layout-preset-passmanager-db46513a24e79aa9.yaml b/releasenotes/notes/0.20/vf2layout-preset-passmanager-db46513a24e79aa9.yaml index 86e9c23612a0..93d39e481842 100644 --- a/releasenotes/notes/0.20/vf2layout-preset-passmanager-db46513a24e79aa9.yaml +++ b/releasenotes/notes/0.20/vf2layout-preset-passmanager-db46513a24e79aa9.yaml @@ -18,7 +18,7 @@ upgrade: (where ``circuit.qubits[0]`` is mapped to physical qubit 0, ``circuit.qubits[1]`` is mapped to physical qubit 1, etc) assuming the trivial layout is perfect. If your use case was dependent on the - trivial layout you can explictly request it when transpiling by specifying + trivial layout you can explicitly request it when transpiling by specifying ``layout_method="trivial"`` when calling :func:`~qiskit.compiler.transpile`. - | The preset pass manager for optimization level 1 (when calling diff --git a/releasenotes/notes/0.21/marginal-memory-29d9d6586ae78590.yaml b/releasenotes/notes/0.21/marginal-memory-29d9d6586ae78590.yaml index 361f90cf35f4..182ccd32a791 100644 --- a/releasenotes/notes/0.21/marginal-memory-29d9d6586ae78590.yaml +++ b/releasenotes/notes/0.21/marginal-memory-29d9d6586ae78590.yaml @@ -3,7 +3,7 @@ features: - | Added a new function :func:`~.marginal_memory` which is used to marginalize shot memory arrays. Provided with the shot memory array and the indices - of interest, the function will return a maginized shot memory array. This + of interest, the function will return a marginalized shot memory array. This function differs from the memory support in the :func:`~.marginal_counts` method which only works on the ``memory`` field in a :class:`~.Results` object. diff --git a/releasenotes/notes/0.21/vf2-post-layout-f0213e2c7ebb645c.yaml b/releasenotes/notes/0.21/vf2-post-layout-f0213e2c7ebb645c.yaml index 5446d12e9ee6..e508f5b734fc 100644 --- a/releasenotes/notes/0.21/vf2-post-layout-f0213e2c7ebb645c.yaml +++ b/releasenotes/notes/0.21/vf2-post-layout-f0213e2c7ebb645c.yaml @@ -15,7 +15,7 @@ features: This pass is similar to the :class:`~.VF2Layout` pass and both internally use the same VF2 implementation from `retworkx `__. However, - :class:`~.VF2PostLayout` is deisgned to run after initial layout, routing, + :class:`~.VF2PostLayout` is designed to run after initial layout, routing, basis translation, and any optimization passes run and will only work if a layout has already been applied, the circuit has been routed, and all gates are in the target basis. This is required so that when a new layout diff --git a/releasenotes/notes/0.21/vqd-implementation-details-09b0ead8b42cacda.yaml b/releasenotes/notes/0.21/vqd-implementation-details-09b0ead8b42cacda.yaml index a6a8cbd06bd5..f79c4ade2c7c 100644 --- a/releasenotes/notes/0.21/vqd-implementation-details-09b0ead8b42cacda.yaml +++ b/releasenotes/notes/0.21/vqd-implementation-details-09b0ead8b42cacda.yaml @@ -2,7 +2,7 @@ features: - | The algorithm iteratively computes each eigenstate by starting from the ground - state (which is computed as in VQE) and then optimising a modified cost function + state (which is computed as in VQE) and then optimizing a modified cost function that tries to compute eigen states that are orthogonal to the states computed in the previous iterations and have the lowest energy when computed over the ansatz. The interface implemented is very similar to that of VQE and is of the form: diff --git a/releasenotes/notes/0.22/add-reverse-linear-entanglement-nlocal-38581e4ffb7a7c68.yaml b/releasenotes/notes/0.22/add-reverse-linear-entanglement-nlocal-38581e4ffb7a7c68.yaml index 5606c830b419..65d64dda08d3 100644 --- a/releasenotes/notes/0.22/add-reverse-linear-entanglement-nlocal-38581e4ffb7a7c68.yaml +++ b/releasenotes/notes/0.22/add-reverse-linear-entanglement-nlocal-38581e4ffb7a7c68.yaml @@ -13,5 +13,5 @@ upgrade: :class:`~.RealAmplitudes` and :class:`~.EfficientSU2` classes has changed from ``"full"`` to ``"reverse_linear"``. This change was made because the output circuit is equivalent but uses only :math:`n-1` instead of :math:`\frac{n(n-1)}{2}` :class:`~.CXGate` gates. If you - desire the previous default you can explicity set ``entanglement="full"`` when calling either + desire the previous default you can explicitly set ``entanglement="full"`` when calling either constructor. diff --git a/releasenotes/notes/0.22/fix-target-control-flow-representation-09520e2838f0657e.yaml b/releasenotes/notes/0.22/fix-target-control-flow-representation-09520e2838f0657e.yaml index 7f1e448fd188..9f535dcd409b 100644 --- a/releasenotes/notes/0.22/fix-target-control-flow-representation-09520e2838f0657e.yaml +++ b/releasenotes/notes/0.22/fix-target-control-flow-representation-09520e2838f0657e.yaml @@ -93,7 +93,7 @@ upgrade: and no edges. This change was made to better reflect the actual connectivity constraints of the :class:`~.Target` because in this case there are no connectivity constraints on the backend being modeled by - the :class:`~.Target`, not a lack of connecitvity. If you desire the + the :class:`~.Target`, not a lack of connectivity. If you desire the previous behavior for any reason you can reproduce it by checking for a ``None`` return and manually building a coupling map, for example:: diff --git a/releasenotes/notes/0.22/gate-direction-target-a9f0acd0cf30ed66.yaml b/releasenotes/notes/0.22/gate-direction-target-a9f0acd0cf30ed66.yaml index a0a56ff0d85a..e471b6dc3717 100644 --- a/releasenotes/notes/0.22/gate-direction-target-a9f0acd0cf30ed66.yaml +++ b/releasenotes/notes/0.22/gate-direction-target-a9f0acd0cf30ed66.yaml @@ -2,5 +2,5 @@ fixes: - | The :class:`.GateDirection` transpiler pass will now respect the available - values for gate parameters when handling parametrised gates with a + values for gate parameters when handling parametrized gates with a :class:`.Target`. diff --git a/releasenotes/notes/0.22/primitive-run-5d1afab3655330a6.yaml b/releasenotes/notes/0.22/primitive-run-5d1afab3655330a6.yaml index d6267404c245..4ef32d600fc0 100644 --- a/releasenotes/notes/0.22/primitive-run-5d1afab3655330a6.yaml +++ b/releasenotes/notes/0.22/primitive-run-5d1afab3655330a6.yaml @@ -3,7 +3,7 @@ features: - | Added new methods for executing primitives: :meth:`.BaseSampler.run` and :meth:`.BaseEstimator.run`. These methods execute asynchronously and return :class:`.JobV1` objects which - provide a handle to the exections. These new run methods can be passed :class:`~.QuantumCircuit` + provide a handle to the exceptions. These new run methods can be passed :class:`~.QuantumCircuit` objects (and observables for :class:`~.BaseEstimator`) that are not registered in the constructor. For example:: diff --git a/releasenotes/notes/0.22/remove-symbolic-pulse-subclasses-77314a1654521852.yaml b/releasenotes/notes/0.22/remove-symbolic-pulse-subclasses-77314a1654521852.yaml index 0162dfcad6a7..15bb07698849 100644 --- a/releasenotes/notes/0.22/remove-symbolic-pulse-subclasses-77314a1654521852.yaml +++ b/releasenotes/notes/0.22/remove-symbolic-pulse-subclasses-77314a1654521852.yaml @@ -5,7 +5,7 @@ features: :class:`.Drag` and :class:`.Constant` have been upgraded to instantiate :class:`SymbolicPulse` rather than the subclass itself. All parametric pulse objects in pulse programs must be symbolic pulse instances, - because subclassing is no longer neccesary. Note that :class:`SymbolicPulse` can + because subclassing is no longer necessary. Note that :class:`SymbolicPulse` can uniquely identify a particular envelope with the symbolic expression object defined in :attr:`SymbolicPulse.envelope`. upgrade: @@ -15,7 +15,7 @@ upgrade: these pulse subclasses are no longer instantiated. They will still work in Terra 0.22, but you should begin transitioning immediately. Instead of using type information, :attr:`SymbolicPulse.pulse_type` should be used. - This is assumed to be a unique string identifer for pulse envelopes, + This is assumed to be a unique string identifier for pulse envelopes, and we can use string equality to investigate the pulse types. For example, .. code-block:: python diff --git a/releasenotes/notes/0.22/steppable-optimizers-9d9b48ba78bd58bb.yaml b/releasenotes/notes/0.22/steppable-optimizers-9d9b48ba78bd58bb.yaml index 9e5c3916c782..659b7312bb1a 100644 --- a/releasenotes/notes/0.22/steppable-optimizers-9d9b48ba78bd58bb.yaml +++ b/releasenotes/notes/0.22/steppable-optimizers-9d9b48ba78bd58bb.yaml @@ -72,7 +72,7 @@ features: evaluated_gradient = grad(ask_data.x_center) optimizer.state.njev += 1 - optmizer.state.nit += 1 + optimizer.state.nit += 1 cf = TellData(eval_jac=evaluated_gradient) optimizer.tell(ask_data=ask_data, tell_data=tell_data) diff --git a/releasenotes/notes/0.22/tensored-subset-fitter-bd28e6e6ec5bdaae.yaml b/releasenotes/notes/0.22/tensored-subset-fitter-bd28e6e6ec5bdaae.yaml index 6061ff04b3d0..3da0de9d6db0 100644 --- a/releasenotes/notes/0.22/tensored-subset-fitter-bd28e6e6ec5bdaae.yaml +++ b/releasenotes/notes/0.22/tensored-subset-fitter-bd28e6e6ec5bdaae.yaml @@ -5,5 +5,5 @@ features: class. The implementation is restricted to mitigation patterns in which each qubit is mitigated individually, e.g. ``[[0], [1], [2]]``. This is, however, the most widely used case. It allows the :class:`.TensoredMeasFitter` to - be used in cases where the numberical order of the physical qubits does not + be used in cases where the numerical order of the physical qubits does not match the index of the classical bit. diff --git a/releasenotes/notes/0.22/visualization-reorganisation-9e302239705c7842.yaml b/releasenotes/notes/0.22/visualization-reorganisation-9e302239705c7842.yaml index 009984fbd7f0..92c07b4e2452 100644 --- a/releasenotes/notes/0.22/visualization-reorganisation-9e302239705c7842.yaml +++ b/releasenotes/notes/0.22/visualization-reorganisation-9e302239705c7842.yaml @@ -2,7 +2,7 @@ upgrade: - | The visualization module :mod:`qiskit.visualization` has seen some internal - reorganisation. This should not have affected the public interface, but if + reorganization. This should not have affected the public interface, but if you were accessing any internals of the circuit drawers, they may now be in different places. The only parts of the visualization module that are considered public are the components that are documented in this online diff --git a/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml b/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml index a9fa4703d678..f28178eb320e 100644 --- a/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml +++ b/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml @@ -1,9 +1,9 @@ --- fixes: - | - QPY deserialisation will no longer add extra :class:`.Clbit` instances to the + QPY deserialization will no longer add extra :class:`.Clbit` instances to the circuit if there are both loose :class:`.Clbit`\ s in the circuit and more :class:`~qiskit.circuit.Qubit`\ s than :class:`.Clbit`\ s. - | - QPY deserialisation will no longer add registers named `q` and `c` if the + QPY deserialization will no longer add registers named `q` and `c` if the input circuit contained only loose bits. diff --git a/releasenotes/notes/0.23/fix_8897-2a90c4b0857c19c2.yaml b/releasenotes/notes/0.23/fix_8897-2a90c4b0857c19c2.yaml index e1d083a4a2d0..716726dcbe5d 100644 --- a/releasenotes/notes/0.23/fix_8897-2a90c4b0857c19c2.yaml +++ b/releasenotes/notes/0.23/fix_8897-2a90c4b0857c19c2.yaml @@ -2,7 +2,7 @@ fixes: - | Fixes issue where :meth:`.Statevector.evolve` and :meth:`.DensityMatrix.evolve` - would raise an exeception for nested subsystem evolution for non-qubit + would raise an exception for nested subsystem evolution for non-qubit subsystems. Fixes `issue #8897 `_ - | diff --git a/releasenotes/notes/0.23/initial_state-8e20b04fc2ec2f4b.yaml b/releasenotes/notes/0.23/initial_state-8e20b04fc2ec2f4b.yaml index 365c5858610c..d7dbd7edcb31 100644 --- a/releasenotes/notes/0.23/initial_state-8e20b04fc2ec2f4b.yaml +++ b/releasenotes/notes/0.23/initial_state-8e20b04fc2ec2f4b.yaml @@ -3,4 +3,4 @@ upgrade: - | The ``initial_state`` argument of the :class:`~NLocal` class should be a :class:`~.QuantumCircuit`. Passing any other type was deprecated as of Qiskit - Terra 0.18.0 (July 2021) and that posibility is now removed. + Terra 0.18.0 (July 2021) and that possibility is now removed. diff --git a/releasenotes/notes/0.23/target-aware-optimize-1q-decomposition-cb9bb4651607b639.yaml b/releasenotes/notes/0.23/target-aware-optimize-1q-decomposition-cb9bb4651607b639.yaml index 969b2aa2a24b..6d1d608aab72 100644 --- a/releasenotes/notes/0.23/target-aware-optimize-1q-decomposition-cb9bb4651607b639.yaml +++ b/releasenotes/notes/0.23/target-aware-optimize-1q-decomposition-cb9bb4651607b639.yaml @@ -3,6 +3,6 @@ features: - | The :class:`~.Optimize1qGatesDecomposition` transpiler pass has a new keyword argument, ``target``, on its constructor. This argument can be used to - specify a :class:`~.Target` object that represnts the compilation target. + specify a :class:`~.Target` object that represents the compilation target. If used it superscedes the ``basis`` argument to determine if an instruction in the circuit is present on the target backend. diff --git a/releasenotes/notes/0.23/target-aware-unroll-custom-definitions-a1b839de199ca048.yaml b/releasenotes/notes/0.23/target-aware-unroll-custom-definitions-a1b839de199ca048.yaml index c019f1329d0c..a755d3b95065 100644 --- a/releasenotes/notes/0.23/target-aware-unroll-custom-definitions-a1b839de199ca048.yaml +++ b/releasenotes/notes/0.23/target-aware-unroll-custom-definitions-a1b839de199ca048.yaml @@ -3,6 +3,6 @@ features: - | The :class:`~.UnrollCustomDefinitions` transpiler pass has a new keyword argument, ``target``, on its constructor. This argument can be used to - specify a :class:`~.Target` object that represnts the compilation target. - If used it superscedes the ``basis_gates`` argument to determine if an + specify a :class:`~.Target` object that represents the compilation target. + If used it supersedes the ``basis_gates`` argument to determine if an instruction in the circuit is present on the target backend. diff --git a/releasenotes/notes/0.24/add-hls-plugins-038388970ad43c55.yaml b/releasenotes/notes/0.24/add-hls-plugins-038388970ad43c55.yaml index ea98eddd2d68..7f4e7a64fcd3 100644 --- a/releasenotes/notes/0.24/add-hls-plugins-038388970ad43c55.yaml +++ b/releasenotes/notes/0.24/add-hls-plugins-038388970ad43c55.yaml @@ -65,7 +65,7 @@ features: qc.append(lin_fun, [0, 1, 2]) qc.append(cliff, [1, 2, 3]) - # Choose synthesis methods that adhere to linear-nearest-neighbour connectivity + # Choose synthesis methods that adhere to linear-nearest-neighbor connectivity hls_config = HLSConfig(linear_function=["kms"], clifford=["lnn"]) # Synthesize diff --git a/releasenotes/notes/0.24/add-new-symbolic-pulses-4dc46ecaaa1ba928.yaml b/releasenotes/notes/0.24/add-new-symbolic-pulses-4dc46ecaaa1ba928.yaml index 4072e8635383..55bd079d0f89 100644 --- a/releasenotes/notes/0.24/add-new-symbolic-pulses-4dc46ecaaa1ba928.yaml +++ b/releasenotes/notes/0.24/add-new-symbolic-pulses-4dc46ecaaa1ba928.yaml @@ -9,5 +9,5 @@ features: * :class:``~qiskit.pulse.library.Triangle`` The new functions return a ``ScalableSymbolicPulse``. With the exception of the ``Sawtooth`` phase, - behaviour is identical to that of the corresponding waveform generators (:class:``~qiskit.pulse.library.sin`` etc). + behavior is identical to that of the corresponding waveform generators (:class:``~qiskit.pulse.library.sin`` etc). The ``Sawtooth`` phase is defined such that a phase of :math:``2\\pi`` shifts by a full cycle. diff --git a/releasenotes/notes/0.24/deprecate-bip-mapping-f0025c4c724e1ec8.yaml b/releasenotes/notes/0.24/deprecate-bip-mapping-f0025c4c724e1ec8.yaml index 93a0523397bd..85637311dc47 100644 --- a/releasenotes/notes/0.24/deprecate-bip-mapping-f0025c4c724e1ec8.yaml +++ b/releasenotes/notes/0.24/deprecate-bip-mapping-f0025c4c724e1ec8.yaml @@ -10,5 +10,5 @@ deprecations: The pass was made into a separate plugin package for two reasons, first the dependency on CPLEX makes it harder to use and secondly the plugin - packge more cleanly integrates with :func:`~.transpile`. + package more cleanly integrates with :func:`~.transpile`. diff --git a/releasenotes/notes/0.24/fix-setting-circuit-data-operation-1b8326b1b089f10c.yaml b/releasenotes/notes/0.24/fix-setting-circuit-data-operation-1b8326b1b089f10c.yaml index e06fec8772f8..c992160b740e 100644 --- a/releasenotes/notes/0.24/fix-setting-circuit-data-operation-1b8326b1b089f10c.yaml +++ b/releasenotes/notes/0.24/fix-setting-circuit-data-operation-1b8326b1b089f10c.yaml @@ -5,4 +5,4 @@ fixes: to be any object that implements :class:`.Operation`, not just a :class:`.circuit.Instruction`. Note that any manual mutation of :attr:`.QuantumCircuit.data` is discouraged; it is not *usually* any more efficient than building a new circuit object, as checking the invariants - surrounding parametrised objects can be surprisingly expensive. + surrounding parametrized objects can be surprisingly expensive. diff --git a/releasenotes/notes/0.24/fix-tensoredop-to-matrix-6f22644f1bdb8b41.yaml b/releasenotes/notes/0.24/fix-tensoredop-to-matrix-6f22644f1bdb8b41.yaml index 000dd81ee984..c217852c33c4 100644 --- a/releasenotes/notes/0.24/fix-tensoredop-to-matrix-6f22644f1bdb8b41.yaml +++ b/releasenotes/notes/0.24/fix-tensoredop-to-matrix-6f22644f1bdb8b41.yaml @@ -2,6 +2,6 @@ fixes: - | Fixed a bug in :meth:`.TensoredOp.to_matrix` where the global coefficient of the operator - was multiplied to the final matrix more than once. Now, the global coefficient is correclty + was multiplied to the final matrix more than once. Now, the global coefficient is correctly applied, independent of the number of tensored operators or states. Fixed `#9398 `__. diff --git a/releasenotes/notes/0.24/include-ecr-gates-for-pulse-scaling-8369eb584c6d8fe1.yaml b/releasenotes/notes/0.24/include-ecr-gates-for-pulse-scaling-8369eb584c6d8fe1.yaml index f33b3d240ba2..79fe381d896e 100644 --- a/releasenotes/notes/0.24/include-ecr-gates-for-pulse-scaling-8369eb584c6d8fe1.yaml +++ b/releasenotes/notes/0.24/include-ecr-gates-for-pulse-scaling-8369eb584c6d8fe1.yaml @@ -5,4 +5,4 @@ features: and RZXCalibrationBuilderNoEcho to consume `ecr` entangling gates from the backend, in addition to the `cx` gates they were build for. These native gates contain the calibrated pulse schedules that the pulse scaling passes use to - generate arbitraty rotations of the :class:`~RZXGate` operation. + generate arbitrary rotations of the :class:`~RZXGate` operation. diff --git a/releasenotes/notes/0.24/qasm2-exporter-rewrite-8993dd24f930b180.yaml b/releasenotes/notes/0.24/qasm2-exporter-rewrite-8993dd24f930b180.yaml index c6170b62a8fa..c2003139d67f 100644 --- a/releasenotes/notes/0.24/qasm2-exporter-rewrite-8993dd24f930b180.yaml +++ b/releasenotes/notes/0.24/qasm2-exporter-rewrite-8993dd24f930b180.yaml @@ -21,7 +21,7 @@ fixes: `#7769 `__ and `#7773 `__. - | - Standard gates defined by Qiskit, such as :class:`.RZXGate`, will now have properly parametrised + Standard gates defined by Qiskit, such as :class:`.RZXGate`, will now have properly parametrized definitions when exported using the OpenQASM 2 exporter (:meth:`.QuantumCircuit.qasm`). See `#7172 `__. - | diff --git a/releasenotes/notes/0.24/qasm2-parser-rust-ecf6570e2d445a94.yaml b/releasenotes/notes/0.24/qasm2-parser-rust-ecf6570e2d445a94.yaml index f38d694e77ad..0fc7dd6b69ac 100644 --- a/releasenotes/notes/0.24/qasm2-parser-rust-ecf6570e2d445a94.yaml +++ b/releasenotes/notes/0.24/qasm2-parser-rust-ecf6570e2d445a94.yaml @@ -17,7 +17,7 @@ features: This new parser is approximately 10x faster than the existing ones at :meth:`.QuantumCircuit.from_qasm_file` and :meth:`.QuantumCircuit.from_qasm_str` for large files, - and has less overhead on each call as well. The new parser is more extensible, customisable and + and has less overhead on each call as well. The new parser is more extensible, customizable and generally also more type-safe; it will not attempt to output custom Qiskit objects when the definition in the OpenQASM 2 file clashes with the Qiskit object, unlike the current exporter. See the :mod:`qiskit.qasm2` module documentation for full details and more examples. diff --git a/releasenotes/notes/0.24/vqd-list-initial-points-list-optimizers-033d7439f86bbb71.yaml b/releasenotes/notes/0.24/vqd-list-initial-points-list-optimizers-033d7439f86bbb71.yaml index fadd99c80234..9af85d8572da 100644 --- a/releasenotes/notes/0.24/vqd-list-initial-points-list-optimizers-033d7439f86bbb71.yaml +++ b/releasenotes/notes/0.24/vqd-list-initial-points-list-optimizers-033d7439f86bbb71.yaml @@ -5,6 +5,6 @@ features: to pass a list of optimizers and initial points for the different minimization runs. For example, the ``k``-th initial point and ``k``-th optimizer will be used for the optimization of the - ``k-1``-th exicted state. + ``k-1``-th excited state. diff --git a/releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml b/releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml index d5dfd69f54aa..999d30095d12 100644 --- a/releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml +++ b/releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml @@ -3,7 +3,7 @@ features: - | :meth:`.DAGCircuit.substitute_node` gained a ``propagate_condition`` keyword argument that is analogous to the same argument in :meth:`~.DAGCircuit.substitute_node_with_dag`. Setting this - to ``False`` opts out of the legacy behaviour of copying a condition on the ``node`` onto the + to ``False`` opts out of the legacy behavior of copying a condition on the ``node`` onto the new ``op`` that is replacing it. This option is ignored for general control-flow operations, which will never propagate their diff --git a/releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml b/releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml index c8f7e3ddd8c2..4902fb85a2fd 100644 --- a/releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml +++ b/releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml @@ -15,6 +15,6 @@ features: reduce the overhead of input normalisation in this function. fixes: - | - A parametrised circuit that contains a custom gate whose definition has a parametrised global phase + A parametrized circuit that contains a custom gate whose definition has a parametrized global phase can now successfully bind the parameter in the inner global phase. See `#10283 `__ for more detail. diff --git a/releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml b/releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml index aa89abeb662d..30d76baa4364 100644 --- a/releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml +++ b/releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml @@ -4,4 +4,4 @@ fixes: Fixed the gate decomposition of multi-controlled Z rotation gates added via :meth:`.QuantumCircuit.mcrz`. Previously, this method implemented a multi-controlled phase gate, which has a relative phase difference to the Z rotation. To obtain the - previous `.QuantumCircuit.mcrz` behaviour, use `.QuantumCircuit.mcp`. + previous `.QuantumCircuit.mcrz` behavior, use `.QuantumCircuit.mcp`. diff --git a/releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml b/releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml index 890223e82b8c..40975c31d1d2 100644 --- a/releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml +++ b/releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml @@ -3,5 +3,5 @@ fixes: - | When the parameter ``conditional=True`` is set in ``qiskit.circuit.random.random_circuit``, the conditional operations will - be preceded by a full mid-circuit measurment. + be preceded by a full mid-circuit measurement. Fixes `#9016 `__ diff --git a/releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml b/releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml index ebb699e8c7b1..246387384ca4 100644 --- a/releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml +++ b/releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml @@ -17,6 +17,6 @@ features: :class:`~.circuit.Instruction` objects. While this isn't optimal for visualization it typically results in much better runtime performance, especially with :meth:`.QuantumCircuit.bind_parameters` and - :meth:`.QuantumCircuit.assign_parameters` which can see a substatial + :meth:`.QuantumCircuit.assign_parameters` which can see a substantial runtime improvement with a flattened output compared to the nested wrapped default output. diff --git a/releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml b/releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml index e460b6e55ee1..6615b11605ef 100644 --- a/releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml +++ b/releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml @@ -4,5 +4,5 @@ features: The instructions :class:`.StatePreparation` and :class:`~.extensions.Initialize`, and their associated circuit methods :meth:`.QuantumCircuit.prepare_state` and :meth:`~.QuantumCircuit.initialize`, gained a keyword argument ``normalize``, which can be set to ``True`` to automatically normalize - an array target. By default this is ``False``, which retains the current behaviour of + an array target. By default this is ``False``, which retains the current behavior of raising an exception when given non-normalized input. diff --git a/releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml b/releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml index 35ce0ce4ca57..9ca56d961bc6 100644 --- a/releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml +++ b/releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml @@ -7,6 +7,6 @@ upgrade: fixes: - | Fixed the :mod:`~qiskit.qpy` serialization of :attr:`.QuantumCircuit.layout` - attribue. Previously, the :attr:`~.QuantumCircuit.layout` attribute would + attribute. Previously, the :attr:`~.QuantumCircuit.layout` attribute would have been dropped when serializing a circuit to QPY. Fixed `#10112 `__ diff --git a/releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml b/releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml index fdfaf394e8b0..844deea0b804 100644 --- a/releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml +++ b/releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml @@ -2,5 +2,5 @@ upgrade: - | The :meth:`~.ApproximateTokenSwapper.map` has been modified to use the new ``rustworkx`` version - of :func:`~graph_token_swapper` for performance reasons. Qiskit Terra 0.25 now requires versison + of :func:`~graph_token_swapper` for performance reasons. Qiskit Terra 0.25 now requires version 0.13.0 of ``rustworkx``. diff --git a/releasenotes/notes/0.45/dag-appenders-check-84d4ef20c1e20fd0.yaml b/releasenotes/notes/0.45/dag-appenders-check-84d4ef20c1e20fd0.yaml index 4126b91bb869..17bf77f51c1b 100644 --- a/releasenotes/notes/0.45/dag-appenders-check-84d4ef20c1e20fd0.yaml +++ b/releasenotes/notes/0.45/dag-appenders-check-84d4ef20c1e20fd0.yaml @@ -4,5 +4,5 @@ features: The :class:`.DAGCircuit` methods :meth:`~.DAGCircuit.apply_operation_back` and :meth:`~.DAGCircuit.apply_operation_front` have gained a ``check`` keyword argument that can be set ``False`` to skip validation that the inputs uphold the :class:`.DAGCircuit` data-structure - invariants. This is useful as a performance optimisation when the DAG is being built from + invariants. This is useful as a performance optimization when the DAG is being built from known-good data, such as during transpiler passes. diff --git a/releasenotes/notes/0.45/deprecate-duplicates-a871f83bbbe1c96f.yaml b/releasenotes/notes/0.45/deprecate-duplicates-a871f83bbbe1c96f.yaml index a62ed79a0471..a55582f9476c 100644 --- a/releasenotes/notes/0.45/deprecate-duplicates-a871f83bbbe1c96f.yaml +++ b/releasenotes/notes/0.45/deprecate-duplicates-a871f83bbbe1c96f.yaml @@ -13,5 +13,5 @@ deprecations: * :meth:`.QuantumCircuit.i` in favor of :meth:`.QuantumCircuit.id` Note that :meth:`.QuantumCircuit.i` is the only exception to the rule above, but since - :meth:`.QuantumCircuit.id` more intuively represents the identity and is used more, we chose + :meth:`.QuantumCircuit.id` more intuitively represents the identity and is used more, we chose it over its counterpart. \ No newline at end of file diff --git a/releasenotes/notes/0.45/discrete-basis-gatedirection-bdffad3b47c1c532.yaml b/releasenotes/notes/0.45/discrete-basis-gatedirection-bdffad3b47c1c532.yaml index c3d4bbe63861..2819be565c1d 100644 --- a/releasenotes/notes/0.45/discrete-basis-gatedirection-bdffad3b47c1c532.yaml +++ b/releasenotes/notes/0.45/discrete-basis-gatedirection-bdffad3b47c1c532.yaml @@ -4,5 +4,5 @@ fixes: The :class:`.GateDirection` transpiler pass will now use discrete-basis translations rather than relying on a continuous :class:`.RYGate`, which should help make some discrete-basis-set targets slightly more reliable. In general, :func:`.transpile` only has partial support for basis sets - that do not contain a continuously-parametrised operation, and so it may not always succeed in + that do not contain a continuously-parametrized operation, and so it may not always succeed in these situations, and will almost certainly not produce optimal results. diff --git a/releasenotes/notes/0.45/expr-rvalue-conditions-8b5d5f7c015658c0.yaml b/releasenotes/notes/0.45/expr-rvalue-conditions-8b5d5f7c015658c0.yaml index 234888b2ac9b..335c819dc8ce 100644 --- a/releasenotes/notes/0.45/expr-rvalue-conditions-8b5d5f7c015658c0.yaml +++ b/releasenotes/notes/0.45/expr-rvalue-conditions-8b5d5f7c015658c0.yaml @@ -60,7 +60,7 @@ features: and :class:`.Clbit` instances. All these classical expressions are fully supported through the Qiskit transpiler stack, through - QPY serialisation (:mod:`qiskit.qpy`) and for export to OpenQASM 3 (:mod:`qiskit.qasm3`). Import + QPY serialization (:mod:`qiskit.qpy`) and for export to OpenQASM 3 (:mod:`qiskit.qasm3`). Import from OpenQASM 3 is currently managed by `a separate package `__ (which is re-exposed via :mod:`qiskit.qasm3`), which we hope will be extended to match the new features in Qiskit. diff --git a/releasenotes/notes/0.45/fix-parameter-hash-d22c270090ffc80e.yaml b/releasenotes/notes/0.45/fix-parameter-hash-d22c270090ffc80e.yaml index e03fa8555a48..8f04a25141e0 100644 --- a/releasenotes/notes/0.45/fix-parameter-hash-d22c270090ffc80e.yaml +++ b/releasenotes/notes/0.45/fix-parameter-hash-d22c270090ffc80e.yaml @@ -3,8 +3,8 @@ features: - | :class:`.Parameter` now has an advanced-usage keyword argument ``uuid`` in its constructor, which can be used to make the :class:`.Parameter` compare equal to another of the same name. - This should not typically be used by users, and is most useful for custom serialisation and - deserialisation. + This should not typically be used by users, and is most useful for custom serialization and + deserialization. fixes: - | The hash of a :class:`.Parameter` is now equal to the hashes of any diff --git a/releasenotes/notes/0.45/fix-timeline-draw-unscheduled-warning-873f7a24c6b51e2c.yaml b/releasenotes/notes/0.45/fix-timeline-draw-unscheduled-warning-873f7a24c6b51e2c.yaml index e93e62c963a3..e75c8e096062 100644 --- a/releasenotes/notes/0.45/fix-timeline-draw-unscheduled-warning-873f7a24c6b51e2c.yaml +++ b/releasenotes/notes/0.45/fix-timeline-draw-unscheduled-warning-873f7a24c6b51e2c.yaml @@ -4,7 +4,7 @@ deprecations: Passing a circuit to :func:`qiskit.visualization.timeline_drawer` that does not have scheduled node start-time information is deprecated. Only circuits that have gone through one of the scheduling analysis passes (for example :class:`.ALAPScheduleAnalysis` or - :class:`.ASAPScheduleAnalysis`) can be visualised. If you have used one of the old-style + :class:`.ASAPScheduleAnalysis`) can be visualized. If you have used one of the old-style scheduling passes (for example :class:`.ALAPSchedule` or :class:`.ASAPSchedule`), you can propagate the scheduling information by running:: @@ -18,5 +18,5 @@ deprecations: instruction_durations=InstructionDurations(), ) - This behaviour was previously intended to be deprecated in Qiskit 0.37, but due to a bug in the - warning, it was not displayed to users until now. The behaviour will be removed in Qiskit 1.0. + This behavior was previously intended to be deprecated in Qiskit 0.37, but due to a bug in the + warning, it was not displayed to users until now. The behavior will be removed in Qiskit 1.0. diff --git a/releasenotes/notes/0.45/qasm2-new-api-4e1e4803d6a5a175.yaml b/releasenotes/notes/0.45/qasm2-new-api-4e1e4803d6a5a175.yaml index befe3011c701..a1e4a5492667 100644 --- a/releasenotes/notes/0.45/qasm2-new-api-4e1e4803d6a5a175.yaml +++ b/releasenotes/notes/0.45/qasm2-new-api-4e1e4803d6a5a175.yaml @@ -18,7 +18,7 @@ features: (:mod:`qiskit.qasm3`) and QPY (:mod:`qiskit.qpy`) modules. This is particularly important since the method name :meth:`~.QuantumCircuit.qasm` gave no indication of the OpenQASM version, and since it was originally - added, Qiskit has gained several serialisation modules that could easily + added, Qiskit has gained several serialization modules that could easily become confused. deprecations: - | diff --git a/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml b/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml index 7b8313e6b080..ac38751d9cee 100644 --- a/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml +++ b/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml @@ -103,7 +103,7 @@ features: :attr:`.Instruction.mutable` which is used to get a mutable copy and check whether an :class:`~.circuit.Instruction` object is mutable. With the introduction of :class:`~.SingletonGate` these methods can be used to have a unified interface - to deal with the mutablitiy of instruction objects. + to deal with the mutability of instruction objects. - | Added an attribute :attr:`.Instruction.base_class`, which gets the "base" type of an instruction. Many instructions will satisfy ``type(obj) == obj.base_class``, however the diff --git a/releasenotes/notes/0.9/changes-on-upgrade-6fcd573269a8ebc5.yaml b/releasenotes/notes/0.9/changes-on-upgrade-6fcd573269a8ebc5.yaml index 151127a0c8de..a6a8745d1754 100644 --- a/releasenotes/notes/0.9/changes-on-upgrade-6fcd573269a8ebc5.yaml +++ b/releasenotes/notes/0.9/changes-on-upgrade-6fcd573269a8ebc5.yaml @@ -121,7 +121,7 @@ other: ``qiskit.execute()`` has been changed to optimization level 1 pass manager defined at ``qiskit.transpile.preset_passmanagers.level1_pass_manager``. - | - All the circuit drawer backends now willl express gate parameters in a + All the circuit drawer backends now will express gate parameters in a circuit as common fractions of pi in the output visualization. If the value of a parameter can be expressed as a fraction of pi that will be used instead of the numeric equivalent. diff --git a/releasenotes/notes/0.9/new-features-0.9-159645f977a139f7.yaml b/releasenotes/notes/0.9/new-features-0.9-159645f977a139f7.yaml index 925da8840f5b..30b7baca7839 100644 --- a/releasenotes/notes/0.9/new-features-0.9-159645f977a139f7.yaml +++ b/releasenotes/notes/0.9/new-features-0.9-159645f977a139f7.yaml @@ -38,7 +38,7 @@ features: Two new functions, ``sech()`` and ``sech_deriv()`` were added to the pulse library module ``qiskit.pulse.pulse_lib`` for creating an unnormalized hyperbolic secant ``SamplePulse`` object and an unnormalized hyperbolic - secant derviative ``SamplePulse`` object respectively. + secant derivative ``SamplePulse`` object respectively. - | A new kwarg option ``vertical_compression`` was added to the ``QuantumCircuit.draw()`` method and the @@ -61,7 +61,7 @@ features: - | When creating a PassManager you can now specify a callback function that if specified will be run after each pass is executed. This function gets - passed a set of kwargs on each call with the state of the pass maanger after + passed a set of kwargs on each call with the state of the pass manager after each pass execution. Currently these kwargs are: * pass\_ (Pass): the pass being run diff --git a/releasenotes/notes/1.0/remove-opflow-qi-utils-3debd943c65b17da.yaml b/releasenotes/notes/1.0/remove-opflow-qi-utils-3debd943c65b17da.yaml index 62626e809957..1b11fe945425 100644 --- a/releasenotes/notes/1.0/remove-opflow-qi-utils-3debd943c65b17da.yaml +++ b/releasenotes/notes/1.0/remove-opflow-qi-utils-3debd943c65b17da.yaml @@ -8,7 +8,7 @@ upgrade_algorithms: - | - A series of legacy quantum execution utililties have been removed, following their deprecation in Qiskit 0.44. + A series of legacy quantum execution utilities have been removed, following their deprecation in Qiskit 0.44. These include the ``qiskit.utils.QuantumInstance`` class, as well the modules: - ``qiskit.utils.backend_utils`` diff --git a/releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml b/releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml index a86869a4e540..62e7e9451619 100644 --- a/releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml +++ b/releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml @@ -1,3 +1,3 @@ fixes: - | - Fix incorrect implemention of `qDRIFT`, negative coeffients of the Hamiltonian are now added back whereas they were always forced to be positive. + Fix incorrect implemention of `qDRIFT`, negative coefficients of the Hamiltonian are now added back whereas they were always forced to be positive. diff --git a/releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml b/releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml index 0bf60329a230..ff83deee9392 100644 --- a/releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml +++ b/releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml @@ -3,7 +3,7 @@ features: - | Added a new transpiler pass :class:`.StarPreRouting` which is designed to identify star connectivity subcircuits and then replace them with an optimal linear routing. This is useful for certain circuits that are composed of - this circuit connectivity such as Berstein Vazirani and QFT. For example: + this circuit connectivity such as Bernstein Vazirani and QFT. For example: .. plot: diff --git a/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml b/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml index 73da8e6b7ad3..6fa548d9245c 100644 --- a/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml +++ b/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml @@ -5,7 +5,7 @@ features: :meth:'~.StabilizerState.probabilities_dict_from_bitstring' allowing the user to pass single bitstring to measure an outcome for. Previouslly the :meth:'~.StabilizerState.probabilities_dict' would be utilized and would - at worst case calculate (2^n) number of probabilbity calculations (depending + at worst case calculate (2^n) number of probability calculations (depending on the state), even if a user wanted a single result. With this new method the user can calculate just the single outcome bitstring value a user passes to measure the probability for. As the number of qubits increases, the more diff --git a/setup.py b/setup.py index 38af5286e817..61168050547c 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ # # python setup.py build_rust --inplace --release # -# to make optimised Rust components even for editable releases, which would otherwise be quite +# to make optimized Rust components even for editable releases, which would otherwise be quite # unergonomic to do otherwise. diff --git a/test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm b/test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm index 4910c35dfec6..ba5db1397046 100644 --- a/test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm +++ b/test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm @@ -1,7 +1,7 @@ // Originally source from the QUEKO benchmark suite // https://github.com/UCLA-VAST/QUEKO-benchmark // A benchmark that is near-term feasible for Google Sycamore with a optimal -// soluation depth of 25 +// solution depth of 25 OPENQASM 2.0; include "qelib1.inc"; qreg q[54]; diff --git a/test/python/circuit/classical/test_expr_constructors.py b/test/python/circuit/classical/test_expr_constructors.py index 10cef88122c1..012697a17dd0 100644 --- a/test/python/circuit/classical/test_expr_constructors.py +++ b/test/python/circuit/classical/test_expr_constructors.py @@ -224,7 +224,7 @@ def test_binary_bitwise_explicit(self, function, opcode): ) @ddt.unpack def test_binary_bitwise_uint_inference(self, function, opcode): - """The binary bitwise functions have specialised inference for the widths of integer + """The binary bitwise functions have specialized inference for the widths of integer literals, since the bitwise functions require the operands to already be of exactly the same width without promotion.""" cr = ClassicalRegister(8, "c") @@ -247,7 +247,7 @@ def test_binary_bitwise_uint_inference(self, function, opcode): ), ) - # Inference between two integer literals is "best effort". This behaviour isn't super + # Inference between two integer literals is "best effort". This behavior isn't super # important to maintain if we want to change the expression system. self.assertEqual( function(5, 255), diff --git a/test/python/circuit/classical/test_expr_properties.py b/test/python/circuit/classical/test_expr_properties.py index 6e1dcf77e1ef..625db22cc12d 100644 --- a/test/python/circuit/classical/test_expr_properties.py +++ b/test/python/circuit/classical/test_expr_properties.py @@ -85,7 +85,7 @@ def test_var_equality(self): self.assertNotEqual(var_a_bool, expr.Var.new("a", types.Bool())) # Manually constructing the same object with the same UUID should cause it compare equal, - # though, for serialisation ease. + # though, for serialization ease. self.assertEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="a")) # This is a badly constructed variable because it's using a different type to refer to the diff --git a/test/python/circuit/library/test_diagonal.py b/test/python/circuit/library/test_diagonal.py index 7dde0d62d8ff..b1158fdab3f0 100644 --- a/test/python/circuit/library/test_diagonal.py +++ b/test/python/circuit/library/test_diagonal.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test the digonal circuit.""" +"""Test the diagonal circuit.""" import unittest from ddt import ddt, data diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index 3d85bb526dc2..078b5af04ea9 100644 --- a/test/python/circuit/library/test_qft.py +++ b/test/python/circuit/library/test_qft.py @@ -183,7 +183,7 @@ def __init__(self, *_args, **_kwargs): raise self # We don't want to issue a warning on mutation until we know that the values are - # finalised; this is because a user might want to mutate the number of qubits and the + # finalized; this is because a user might want to mutate the number of qubits and the # approximation degree. In these cases, wait until we try to build the circuit. with warnings.catch_warnings(record=True) as caught_warnings: warnings.filterwarnings( diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 216ea3a59bb0..04e71a0dd4d7 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -1163,7 +1163,7 @@ def test_qpy_with_for_loop_iterator(self): self.assertDeprecatedBitProperties(qc, new_circuit) def test_qpy_clbit_switch(self): - """Test QPY serialisation for a switch statement with a Clbit target.""" + """Test QPY serialization for a switch statement with a Clbit target.""" case_t = QuantumCircuit(2, 1) case_t.x(0) case_f = QuantumCircuit(2, 1) @@ -1180,7 +1180,7 @@ def test_qpy_clbit_switch(self): self.assertDeprecatedBitProperties(qc, new_circuit) def test_qpy_register_switch(self): - """Test QPY serialisation for a switch statement with a ClassicalRegister target.""" + """Test QPY serialization for a switch statement with a ClassicalRegister target.""" qreg = QuantumRegister(2, "q") creg = ClassicalRegister(3, "c") diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index e9a7416f78c4..517a7093e81e 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -928,7 +928,7 @@ def test_remove_final_measurements_7089(self): self.assertEqual(circuit.clbits, []) def test_remove_final_measurements_bit_locations(self): - """Test remove_final_measurements properly recalculates clbit indicies + """Test remove_final_measurements properly recalculates clbit indices and preserves order of remaining cregs and clbits. """ c0 = ClassicalRegister(1) diff --git a/test/python/circuit/test_circuit_vars.py b/test/python/circuit/test_circuit_vars.py index 8b7167eed7e1..f6916dcb72db 100644 --- a/test/python/circuit/test_circuit_vars.py +++ b/test/python/circuit/test_circuit_vars.py @@ -76,7 +76,7 @@ def test_initialise_declarations_mapping(self): ) def test_initialise_declarations_dependencies(self): - """Test that the cirucit initialiser can take in declarations with dependencies between + """Test that the circuit initializer can take in declarations with dependencies between them, provided they're specified in a suitable order.""" a = expr.Var.new("a", types.Bool()) vars_ = [ diff --git a/test/python/circuit/test_control_flow_builders.py b/test/python/circuit/test_control_flow_builders.py index 0aeeb084f479..e41b5c1f9f31 100644 --- a/test/python/circuit/test_control_flow_builders.py +++ b/test/python/circuit/test_control_flow_builders.py @@ -1438,7 +1438,7 @@ def test_break_continue_deeply_nested(self, loop_operation): These are the deepest tests, hitting all parts of the deferred builder scopes. We test ``if``, ``if/else`` and ``switch`` paths at various levels of the scoping to try and account - for as many weird edge cases with the deferred behaviour as possible. We try to make sure, + for as many weird edge cases with the deferred behavior as possible. We try to make sure, particularly in the most complicated examples, that there are resources added before and after every single scope, to try and catch all possibilities of where resources may be missed. @@ -2943,7 +2943,7 @@ def test_inplace_compose_within_builder(self): self.assertEqual(canonicalize_control_flow(outer), canonicalize_control_flow(expected)) def test_global_phase_of_blocks(self): - """It should be possible to set a global phase of a scope independantly of the containing + """It should be possible to set a global phase of a scope independently of the containing scope and other sibling scopes.""" qr = QuantumRegister(3) cr = ClassicalRegister(3) @@ -3335,7 +3335,7 @@ def test_if_rejects_break_continue_if_not_in_loop(self): def test_for_rejects_reentry(self): """Test that the ``for``-loop context manager rejects attempts to re-enter it. Since it holds some forms of state during execution (the loop variable, which may be generated), we - can't safely re-enter it and get the expected behaviour.""" + can't safely re-enter it and get the expected behavior.""" for_manager = QuantumCircuit(2, 2).for_loop(range(2)) with for_manager: @@ -3584,7 +3584,7 @@ def test_reject_c_if_from_outside_scope(self): # As a side-effect of how the lazy building of 'if' statements works, we actually # *could* add a condition to the gate after the 'if' block as long as we were still # within the 'for' loop. It should actually manage the resource correctly as well, but - # it's "undefined behaviour" than something we specifically want to forbid or allow. + # it's "undefined behavior" than something we specifically want to forbid or allow. test = QuantumCircuit(bits) with test.for_loop(range(2)): with test.if_test(cond): diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index ced7229415ea..8ba70ee852ca 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -1262,7 +1262,7 @@ def test_modify_cugate_params_slice(self): self.assertEqual(cu.base_gate.params, [0.4, 0.3, 0.2]) def test_assign_nested_controlled_cu(self): - """Test assignment of an arbitrary controlled parametrised gate that appears through the + """Test assignment of an arbitrary controlled parametrized gate that appears through the `Gate.control()` method on an already-controlled gate.""" theta = Parameter("t") qc_c = QuantumCircuit(2) diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index dbda9262f15b..170b47632c4d 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -566,7 +566,7 @@ def case(specifier, message): case(1.0, r"Unknown classical resource specifier: .*") def test_instructionset_c_if_with_no_requester(self): - """Test that using a raw :obj:`.InstructionSet` with no classical-resource resoluer accepts + """Test that using a raw :obj:`.InstructionSet` with no classical-resource resolver accepts arbitrary :obj:`.Clbit` and `:obj:`.ClassicalRegister` instances, but rejects integers.""" with self.subTest("accepts arbitrary register"): diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index feab3002f582..0095f87be9ae 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -314,7 +314,7 @@ def test_assign_parameters_by_name(self): ) def test_bind_parameters_custom_definition_global_phase(self): - """Test that a custom gate with a parametrised `global_phase` is assigned correctly.""" + """Test that a custom gate with a parametrized `global_phase` is assigned correctly.""" x = Parameter("x") custom = QuantumCircuit(1, global_phase=x).to_gate() base = QuantumCircuit(1) @@ -1485,7 +1485,7 @@ def test_cast_to_float_when_underlying_expression_bound(self): def test_cast_to_float_intermediate_complex_value(self): """Verify expression can be cast to a float when it is fully bound, but an intermediate part of the expression evaluation involved complex types. Sympy is generally more permissive - than symengine here, and sympy's tends to be the expected behaviour for our users.""" + than symengine here, and sympy's tends to be the expected behavior for our users.""" x = Parameter("x") bound_expr = (x + 1.0 + 1.0j).bind({x: -1.0j}) self.assertEqual(float(bound_expr), 1.0) @@ -2166,7 +2166,7 @@ def test_parameter_equal_to_identical_expression(self): self.assertEqual(theta, expr) def test_parameter_symbol_equal_after_ufunc(self): - """Verfiy ParameterExpression phi + """Verify ParameterExpression phi and ParameterExpression cos(phi) have the same symbol map""" phi = Parameter("phi") cos_phi = numpy.cos(phi) diff --git a/test/python/compiler/test_assembler.py b/test/python/compiler/test_assembler.py index 630db1c0c1c0..1a6e5b6b8fe5 100644 --- a/test/python/compiler/test_assembler.py +++ b/test/python/compiler/test_assembler.py @@ -233,7 +233,7 @@ def test_assemble_opaque_inst(self): self.assertEqual(qobj.experiments[0].instructions[0].params, [0.5, 0.4]) def test_assemble_unroll_parametervector(self): - """Verfiy that assemble unrolls parametervectors ref #5467""" + """Verify that assemble unrolls parametervectors ref #5467""" pv1 = ParameterVector("pv1", 3) pv2 = ParameterVector("pv2", 3) qc = QuantumCircuit(2, 2) @@ -609,7 +609,7 @@ def test_pulse_gates_common_cals(self): self.assertFalse(hasattr(qobj.experiments[1].config, "calibrations")) def test_assemble_adds_circuit_metadata_to_experiment_header(self): - """Verify that any circuit metadata is added to the exeriment header.""" + """Verify that any circuit metadata is added to the experiment header.""" circ = QuantumCircuit(2, metadata={"experiment_type": "gst", "execution_number": "1234"}) qobj = assemble(circ, shots=100, memory=False, seed_simulator=6) self.assertEqual( @@ -943,7 +943,7 @@ def setUp(self): self.header = {"backend_name": "FakeOpenPulse2Q", "backend_version": "0.0.0"} def test_assemble_adds_schedule_metadata_to_experiment_header(self): - """Verify that any circuit metadata is added to the exeriment header.""" + """Verify that any circuit metadata is added to the experiment header.""" self.schedule.metadata = {"experiment_type": "gst", "execution_number": "1234"} qobj = assemble( self.schedule, diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 6058f922e171..77b63a3098bc 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -499,7 +499,7 @@ def test_transpile_bell(self): self.assertIsInstance(circuits, QuantumCircuit) def test_transpile_bell_discrete_basis(self): - """Test that it's possible to transpile a very simple circuit to a discrete stabiliser-like + """Test that it's possible to transpile a very simple circuit to a discrete stabilizer-like basis. In general, we do not make any guarantees about the possibility or quality of transpilation in these situations, but this is at least useful as a check that stuff that _could_ be possible remains so.""" @@ -1890,7 +1890,7 @@ def test_transpile_control_flow_no_backend(self, opt_level): @data(0, 1, 2, 3) def test_transpile_with_custom_control_flow_target(self, opt_level): - """Test transpile() with a target and constrol flow ops.""" + """Test transpile() with a target and control flow ops.""" target = GenericBackendV2(num_qubits=8, control_flow=True).target circuit = QuantumCircuit(6, 1) @@ -1927,7 +1927,7 @@ def test_transpile_with_custom_control_flow_target(self, opt_level): transpiled = transpile( circuit, optimization_level=opt_level, target=target, seed_transpiler=12434 ) - # Tests of the complete validity of a circuit are mostly done at the indiviual pass level; + # Tests of the complete validity of a circuit are mostly done at the individual pass level; # here we're just checking that various passes do appear to have run. self.assertIsInstance(transpiled, QuantumCircuit) # Assert layout ran. diff --git a/test/python/converters/test_circuit_to_instruction.py b/test/python/converters/test_circuit_to_instruction.py index d4b69e71aa10..e3239d4b5ff4 100644 --- a/test/python/converters/test_circuit_to_instruction.py +++ b/test/python/converters/test_circuit_to_instruction.py @@ -226,7 +226,7 @@ def test_forbids_captured_vars(self): qc.to_instruction() def test_forbids_input_vars(self): - """This test can be relaxed when we have proper support for the behaviour. + """This test can be relaxed when we have proper support for the behavior. This actually has a natural meaning; the input variables could become typed parameters. We don't have a formal structure for managing that yet, though, so it's forbidden until the @@ -236,7 +236,7 @@ def test_forbids_input_vars(self): qc.to_instruction() def test_forbids_declared_vars(self): - """This test can be relaxed when we have proper support for the behaviour. + """This test can be relaxed when we have proper support for the behavior. This has a very natural representation, which needs basically zero special handling, since the variables are necessarily entirely internal to the subroutine. The reason it is diff --git a/test/python/dagcircuit/test_collect_blocks.py b/test/python/dagcircuit/test_collect_blocks.py index 2fe3e4bad7bf..b2715078d7f5 100644 --- a/test/python/dagcircuit/test_collect_blocks.py +++ b/test/python/dagcircuit/test_collect_blocks.py @@ -163,7 +163,7 @@ def test_collect_and_split_gates_from_dagcircuit(self): self.assertEqual(len(split_blocks), 3) def test_collect_and_split_gates_from_dagdependency(self): - """Test collecting and splitting blocks from DAGDependecy.""" + """Test collecting and splitting blocks from DAGDependency.""" qc = QuantumCircuit(6) qc.cx(0, 1) qc.cx(3, 5) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 0fcff29e5b40..4ab4e392cbb1 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -853,7 +853,7 @@ def test_quantum_successors(self): self.assertIsInstance(cnot_node.op, CXGate) successor_cnot = self.dag.quantum_successors(cnot_node) - # Ordering between Reset and out[q1] is indeterminant. + # Ordering between Reset and out[q1] is indeterminate. successor1 = next(successor_cnot) successor2 = next(successor_cnot) @@ -902,7 +902,7 @@ def test_quantum_predecessors(self): self.assertIsInstance(cnot_node.op, CXGate) predecessor_cnot = self.dag.quantum_predecessors(cnot_node) - # Ordering between Reset and in[q1] is indeterminant. + # Ordering between Reset and in[q1] is indeterminate. predecessor1 = next(predecessor_cnot) predecessor2 = next(predecessor_cnot) @@ -1611,7 +1611,7 @@ def setUp(self): qc.h(0) qc.measure(0, 0) # The depth of an if-else is the path through the longest block (regardless of the - # condition). The size is the sum of both blocks (mostly for optimisation-target purposes). + # condition). The size is the sum of both blocks (mostly for optimization-target purposes). with qc.if_test((qc.clbits[0], True)) as else_: qc.x(1) qc.cx(2, 3) @@ -2420,11 +2420,11 @@ def test_raise_if_var_mismatch(self): def test_raise_if_substituting_dag_modifies_its_conditional(self): """Verify that we raise if the input dag modifies any of the bits in node.op.condition.""" - # The `propagate_condition=True` argument (and behaviour of `substitute_node_with_dag` + # The `propagate_condition=True` argument (and behavior of `substitute_node_with_dag` # before the parameter was added) treats the replacement DAG as implementing only the # un-controlled operation. The original contract considers it an error to replace a node # with an operation that may modify one of the condition bits in case this affects - # subsequent operations, so when `propagate_condition=True`, this error behaviour is + # subsequent operations, so when `propagate_condition=True`, this error behavior is # maintained. instr = Instruction("opaque", 1, 1, []) diff --git a/test/python/primitives/containers/test_observables_array.py b/test/python/primitives/containers/test_observables_array.py index 5a8513a5ed9c..ea51718aebea 100644 --- a/test/python/primitives/containers/test_observables_array.py +++ b/test/python/primitives/containers/test_observables_array.py @@ -112,7 +112,7 @@ def test_coerce_observable_zero_sparse_pauli_op(self): self.assertEqual(obs["Z"], 1) def test_coerce_observable_duplicate_sparse_pauli_op(self): - """Test coerce_observable for SparsePauliOp wiht duplicate paulis""" + """Test coerce_observable for SparsePauliOp with duplicate paulis""" op = qi.SparsePauliOp(["XX", "-XX", "XX", "-XX"], [2, 1, 3, 2]) obs = ObservablesArray.coerce_observable(op) self.assertIsInstance(obs, dict) diff --git a/test/python/primitives/test_estimator.py b/test/python/primitives/test_estimator.py index 80045dee0d67..535841cc90f7 100644 --- a/test/python/primitives/test_estimator.py +++ b/test/python/primitives/test_estimator.py @@ -346,9 +346,9 @@ class TestObservableValidation(QiskitTestCase): ), ) @unpack - def test_validate_observables(self, obsevables, expected): - """Test obsevables standardization.""" - self.assertEqual(validation._validate_observables(obsevables), expected) + def test_validate_observables(self, observables, expected): + """Test observables standardization.""" + self.assertEqual(validation._validate_observables(observables), expected) @data(None, "ERROR") def test_qiskit_error(self, observables): @@ -358,7 +358,7 @@ def test_qiskit_error(self, observables): @data((), []) def test_value_error(self, observables): - """Test value error if no obsevables are provided.""" + """Test value error if no observables are provided.""" with self.assertRaises(ValueError): validation._validate_observables(observables) diff --git a/test/python/primitives/test_statevector_estimator.py b/test/python/primitives/test_statevector_estimator.py index 117ead6717ad..1ed2d42e0e3b 100644 --- a/test/python/primitives/test_statevector_estimator.py +++ b/test/python/primitives/test_statevector_estimator.py @@ -276,7 +276,7 @@ def test_precision_seed(self): result = job.result() np.testing.assert_allclose(result[0].data.evs, [1.901141473854881]) np.testing.assert_allclose(result[1].data.evs, [1.901141473854881]) - # precision=0 impliese the exact expectation value + # precision=0 implies the exact expectation value job = estimator.run([(psi1, hamiltonian1, [theta1])], precision=0) result = job.result() np.testing.assert_allclose(result[0].data.evs, [1.5555572817900956]) diff --git a/test/python/pulse/test_instruction_schedule_map.py b/test/python/pulse/test_instruction_schedule_map.py index bf56f980a729..67628ba845ae 100644 --- a/test/python/pulse/test_instruction_schedule_map.py +++ b/test/python/pulse/test_instruction_schedule_map.py @@ -342,7 +342,7 @@ def test_sequenced_parameterized_schedule(self): self.assertEqual(sched.instructions[2][-1].phase, 3) def test_schedule_generator(self): - """Test schedule generator functionalty.""" + """Test schedule generator functionality.""" dur_val = 10 amp = 1.0 @@ -364,7 +364,7 @@ def test_func(dur: int): self.assertEqual(inst_map.get_parameters("f", (0,)), ("dur",)) def test_schedule_generator_supports_parameter_expressions(self): - """Test expression-based schedule generator functionalty.""" + """Test expression-based schedule generator functionality.""" t_param = Parameter("t") amp = 1.0 diff --git a/test/python/pulse/test_reference.py b/test/python/pulse/test_reference.py index c33d7588e920..3d7603461756 100644 --- a/test/python/pulse/test_reference.py +++ b/test/python/pulse/test_reference.py @@ -87,7 +87,7 @@ def test_refer_schedule_parameter_scope(self): self.assertEqual(sched_z1.parameters, sched_y1.parameters) def test_refer_schedule_parameter_assignment(self): - """Test assigning to parametr in referenced schedule""" + """Test assigning to parameter in referenced schedule""" param = circuit.Parameter("name") with pulse.build() as sched_x1: @@ -197,7 +197,7 @@ def test_calling_similar_schedule(self): """Test calling schedules with the same representation. sched_x1 and sched_y1 are the different subroutines, but same representation. - Two references shoud be created. + Two references should be created. """ param1 = circuit.Parameter("param") param2 = circuit.Parameter("param") @@ -539,7 +539,7 @@ def test_lazy_ecr(self): def test_cnot(self): """Integration test with CNOT schedule construction.""" - # echeod cross resonance + # echoed cross resonance with pulse.build(name="ecr", default_alignment="sequential") as ecr_sched: pulse.call(self.cr_sched, name="cr") pulse.call(self.xp_sched, name="xp") diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py index 135e874be488..048c5d7852b4 100644 --- a/test/python/qasm3/test_export.py +++ b/test/python/qasm3/test_export.py @@ -495,7 +495,7 @@ def test_unbound_circuit(self): self.assertEqual(Exporter().dumps(qc), expected_qasm) def test_unknown_parameterized_gate_called_multiple_times(self): - """Test that a parameterised gate is called correctly if the first instance of it is + """Test that a parameterized gate is called correctly if the first instance of it is generic.""" x, y = Parameter("x"), Parameter("y") qc = QuantumCircuit(2) @@ -1310,7 +1310,7 @@ def test_chain_else_if(self): "", ] ) - # This is not the default behaviour, and it's pretty buried how you'd access it. + # This is not the default behavior, and it's pretty buried how you'd access it. builder = QASM3Builder( qc, includeslist=("stdgates.inc",), @@ -1370,7 +1370,7 @@ def test_chain_else_if_does_not_chain_if_extra_instructions(self): "", ] ) - # This is not the default behaviour, and it's pretty buried how you'd access it. + # This is not the default behavior, and it's pretty buried how you'd access it. builder = QASM3Builder( qc, includeslist=("stdgates.inc",), @@ -1935,7 +1935,7 @@ def test_var_naming_clash_gate(self): class TestCircuitQASM3ExporterTemporaryCasesWithBadParameterisation(QiskitTestCase): """Test functionality that is not what we _want_, but is what we need to do while the definition - of custom gates with parameterisation does not work correctly. + of custom gates with parameterization does not work correctly. These tests are modified versions of those marked with the `requires_fixed_parameterisation` decorator, and this whole class can be deleted once those are fixed. See gh-7335. diff --git a/test/python/qasm3/test_import.py b/test/python/qasm3/test_import.py index 85da3f41933e..522f68c89562 100644 --- a/test/python/qasm3/test_import.py +++ b/test/python/qasm3/test_import.py @@ -13,7 +13,7 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring # Since the import is nearly entirely delegated to an external package, most of the testing is done -# there. Here we need to test our wrapping behaviour for base functionality and exceptions. We +# there. Here we need to test our wrapping behavior for base functionality and exceptions. We # don't want to get into a situation where updates to `qiskit_qasm3_import` breaks Terra's test # suite due to too specific tests on the Terra side. diff --git a/test/python/quantum_info/operators/symplectic/test_pauli_list.py b/test/python/quantum_info/operators/symplectic/test_pauli_list.py index 0ef7079f461a..8c96f63c4ddf 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli_list.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli_list.py @@ -2119,7 +2119,7 @@ def qubitwise_commutes(left: Pauli, right: Pauli) -> bool: pauli_list = PauliList(input_labels) groups = pauli_list.group_qubit_wise_commuting() - # checking that every input Pauli in pauli_list is in a group in the ouput + # checking that every input Pauli in pauli_list is in a group in the output output_labels = [pauli.to_label() for group in groups for pauli in group] self.assertListEqual(sorted(output_labels), sorted(input_labels)) @@ -2153,7 +2153,7 @@ def commutes(left: Pauli, right: Pauli) -> bool: # if qubit_wise=True, equivalent to test_group_qubit_wise_commuting groups = pauli_list.group_commuting(qubit_wise=False) - # checking that every input Pauli in pauli_list is in a group in the ouput + # checking that every input Pauli in pauli_list is in a group in the output output_labels = [pauli.to_label() for group in groups for pauli in group] self.assertListEqual(sorted(output_labels), sorted(input_labels)) # Within each group, every operator commutes with every other operator. diff --git a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py index e7a9b89b7318..c4f09ec2d795 100644 --- a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py +++ b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py @@ -181,7 +181,7 @@ def test_from_index_list(self): self.assertEqual(spp_op.paulis, PauliList(expected_labels)) def test_from_index_list_parameters(self): - """Test from_list method specifying the Paulis via indices with paramteres.""" + """Test from_list method specifying the Paulis via indices with parameters.""" expected_labels = ["XXZ", "IXI", "YIZ", "III"] paulis = ["XXZ", "X", "YZ", ""] indices = [[2, 1, 0], [1], [2, 0], []] @@ -1028,7 +1028,7 @@ def commutes(left: Pauli, right: Pauli, qubit_wise: bool) -> bool: coeffs = np.random.random(len(input_labels)) + np.random.random(len(input_labels)) * 1j sparse_pauli_list = SparsePauliOp(input_labels, coeffs) groups = sparse_pauli_list.group_commuting(qubit_wise) - # checking that every input Pauli in sparse_pauli_list is in a group in the ouput + # checking that every input Pauli in sparse_pauli_list is in a group in the output output_labels = [pauli.to_label() for group in groups for pauli in group.paulis] self.assertListEqual(sorted(output_labels), sorted(input_labels)) # checking that every coeffs are grouped according to sparse_pauli_list group @@ -1056,7 +1056,7 @@ def commutes(left: Pauli, right: Pauli, qubit_wise: bool) -> bool: ) def test_dot_real(self): - """Test dot for real coefficiets.""" + """Test dot for real coefficients.""" x = SparsePauliOp("X", np.array([1])) y = SparsePauliOp("Y", np.array([1])) iz = SparsePauliOp("Z", 1j) diff --git a/test/python/result/test_mitigators.py b/test/python/result/test_mitigators.py index 66662bb587e1..3b3e83bce009 100644 --- a/test/python/result/test_mitigators.py +++ b/test/python/result/test_mitigators.py @@ -215,7 +215,7 @@ def test_clbits_parameter(self): self.assertLess( mitigated_error, 0.001, - f"Mitigator {mitigator} did not correctly marganalize for qubits 1,2", + f"Mitigator {mitigator} did not correctly marginalize for qubits 1,2", ) mitigated_probs_02 = ( @@ -227,7 +227,7 @@ def test_clbits_parameter(self): self.assertLess( mitigated_error, 0.001, - f"Mitigator {mitigator} did not correctly marganalize for qubits 0,2", + f"Mitigator {mitigator} did not correctly marginalize for qubits 0,2", ) def test_qubits_parameter(self): diff --git a/test/python/result/test_result.py b/test/python/result/test_result.py index 7d73ab2ebcf6..ff1f4cbf29a0 100644 --- a/test/python/result/test_result.py +++ b/test/python/result/test_result.py @@ -105,7 +105,7 @@ def test_counts_duplicate_name(self): result.get_counts("foo") def test_result_repr(self): - """Test that repr is contstructed correctly for a results object.""" + """Test that repr is constructed correctly for a results object.""" raw_counts = {"0x0": 4, "0x2": 10} data = models.ExperimentResultData(counts=raw_counts) exp_result_header = QobjExperimentHeader( diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index 19183ce17303..1db810621e32 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -53,7 +53,7 @@ def test_decompose_clifford(self, num_qubits): @combine(num_qubits=[4, 5, 6, 7]) def test_decompose_lnn_depth(self, num_qubits): - """Test layered decomposition for linear-nearest-neighbour (LNN) connectivity.""" + """Test layered decomposition for linear-nearest-neighbor (LNN) connectivity.""" rng = np.random.default_rng(1234) samples = 10 for _ in range(samples): @@ -64,7 +64,7 @@ def test_decompose_lnn_depth(self, num_qubits): filter_function=lambda x: x.operation.num_qubits == 2 ) self.assertTrue(depth2q <= 7 * num_qubits + 2) - # Check that the Clifford circuit has linear nearest neighbour connectivity + # Check that the Clifford circuit has linear nearest neighbor connectivity self.assertTrue(check_lnn_connectivity(circ.decompose())) cliff_target = Clifford(circ) self.assertEqual(cliff, cliff_target) diff --git a/test/python/synthesis/test_cx_cz_synthesis.py b/test/python/synthesis/test_cx_cz_synthesis.py index ef7eeb38b8f6..63353dab95df 100644 --- a/test/python/synthesis/test_cx_cz_synthesis.py +++ b/test/python/synthesis/test_cx_cz_synthesis.py @@ -34,7 +34,7 @@ class TestCXCZSynth(QiskitTestCase): @combine(num_qubits=[3, 4, 5, 6, 7, 8, 9, 10]) def test_cx_cz_synth_lnn(self, num_qubits): - """Test the CXCZ synthesis code for linear nearest neighbour connectivity.""" + """Test the CXCZ synthesis code for linear nearest neighbor connectivity.""" seed = 1234 rng = np.random.default_rng(seed) num_gates = 10 diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index 7284039e56c8..af663a4f0d32 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -31,7 +31,7 @@ class TestCZSynth(QiskitTestCase): @combine(num_qubits=[3, 4, 5, 6, 7]) def test_cz_synth_lnn(self, num_qubits): - """Test the CZ synthesis code for linear nearest neighbour connectivity.""" + """Test the CZ synthesis code for linear nearest neighbor connectivity.""" seed = 1234 rng = np.random.default_rng(seed) num_gates = 10 diff --git a/test/python/synthesis/test_stabilizer_synthesis.py b/test/python/synthesis/test_stabilizer_synthesis.py index e195c9cf2707..958faa204c11 100644 --- a/test/python/synthesis/test_stabilizer_synthesis.py +++ b/test/python/synthesis/test_stabilizer_synthesis.py @@ -54,7 +54,7 @@ def test_decompose_stab(self, num_qubits): @combine(num_qubits=[4, 5, 6, 7]) def test_decompose_lnn_depth(self, num_qubits): - """Test stabilizer state decomposition for linear-nearest-neighbour (LNN) connectivity.""" + """Test stabilizer state decomposition for linear-nearest-neighbor (LNN) connectivity.""" rng = np.random.default_rng(1234) samples = 10 for _ in range(samples): @@ -66,7 +66,7 @@ def test_decompose_lnn_depth(self, num_qubits): filter_function=lambda x: x.operation.num_qubits == 2 ) self.assertTrue(depth2q == 2 * num_qubits + 2) - # Check that the stabilizer state circuit has linear nearest neighbour connectivity + # Check that the stabilizer state circuit has linear nearest neighbor connectivity self.assertTrue(check_lnn_connectivity(circ.decompose())) stab_target = StabilizerState(circ) # Verify that the two stabilizers generate the same state diff --git a/test/python/synthesis/test_synthesis.py b/test/python/synthesis/test_synthesis.py index 8d953ff1b83c..025b9accf227 100644 --- a/test/python/synthesis/test_synthesis.py +++ b/test/python/synthesis/test_synthesis.py @@ -219,7 +219,7 @@ def check_two_qubit_weyl_specialization( ): """Check that the two qubit Weyl decomposition gets specialized as expected""" - # Loop to check both for implicit and explicity specialization + # Loop to check both for implicit and explicitly specialization for decomposer in (TwoQubitWeylDecomposition, expected_specialization): if isinstance(decomposer, TwoQubitWeylDecomposition): with self.assertDebugOnly(): diff --git a/test/python/test_util.py b/test/python/test_util.py index f807f0d4e255..d403ed004bc3 100644 --- a/test/python/test_util.py +++ b/test/python/test_util.py @@ -43,7 +43,7 @@ def test_local_hardware_no_cpu_count(self): self.assertEqual(1, result["cpus"]) def test_local_hardware_no_sched_five_count(self): - """Test cpu cound if sched affinity method is missing and cpu count is 5.""" + """Test cpu could if sched affinity method is missing and cpu count is 5.""" with mock.patch.object(multiprocessing, "os", spec=[]): multiprocessing.os.cpu_count = mock.MagicMock(return_value=5) del multiprocessing.os.sched_getaffinity @@ -51,7 +51,7 @@ def test_local_hardware_no_sched_five_count(self): self.assertEqual(2, result["cpus"]) def test_local_hardware_no_sched_sixty_four_count(self): - """Test cpu cound if sched affinity method is missing and cpu count is 64.""" + """Test cpu could if sched affinity method is missing and cpu count is 64.""" with mock.patch.object(multiprocessing, "os", spec=[]): multiprocessing.os.cpu_count = mock.MagicMock(return_value=64) del multiprocessing.os.sched_getaffinity diff --git a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py index c1df8adbad2c..c95d65422d4b 100644 --- a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py +++ b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py @@ -406,7 +406,7 @@ def test_valid_pulse_duration(self): self.pulse_gate_validation_pass(circuit) def test_no_calibration(self): - """No error raises if no calibration is addedd.""" + """No error raises if no calibration is added.""" circuit = QuantumCircuit(1) circuit.x(0) diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index 206a652299c2..ff8be63ffbcd 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -119,7 +119,7 @@ def test_can_combine_cliffords(self): cliff2 = self.create_cliff2() cliff3 = self.create_cliff3() - # Create a circuit with two consective cliffords + # Create a circuit with two consecutive cliffords qc1 = QuantumCircuit(4) qc1.append(cliff1, [3, 1, 2]) qc1.append(cliff2, [3, 1, 2]) diff --git a/test/python/transpiler/test_commutative_cancellation.py b/test/python/transpiler/test_commutative_cancellation.py index 1030b83ae2ac..71bab61708cd 100644 --- a/test/python/transpiler/test_commutative_cancellation.py +++ b/test/python/transpiler/test_commutative_cancellation.py @@ -198,7 +198,7 @@ def test_control_bit_of_cnot(self): self.assertEqual(expected, new_circuit) def test_control_bit_of_cnot1(self): - """A simple circuit where the two cnots shoule be cancelled. + """A simple circuit where the two cnots should be cancelled. qr0:----.------[Z]------.-- qr0:---[Z]--- | | @@ -219,7 +219,7 @@ def test_control_bit_of_cnot1(self): self.assertEqual(expected, new_circuit) def test_control_bit_of_cnot2(self): - """A simple circuit where the two cnots shoule be cancelled. + """A simple circuit where the two cnots should be cancelled. qr0:----.------[T]------.-- qr0:---[T]--- | | @@ -240,7 +240,7 @@ def test_control_bit_of_cnot2(self): self.assertEqual(expected, new_circuit) def test_control_bit_of_cnot3(self): - """A simple circuit where the two cnots shoule be cancelled. + """A simple circuit where the two cnots should be cancelled. qr0:----.------[Rz]------.-- qr0:---[Rz]--- | | @@ -261,7 +261,7 @@ def test_control_bit_of_cnot3(self): self.assertEqual(expected, new_circuit) def test_control_bit_of_cnot4(self): - """A simple circuit where the two cnots shoule be cancelled. + """A simple circuit where the two cnots should be cancelled. qr0:----.------[T]------.-- qr0:---[T]--- | | @@ -662,7 +662,7 @@ def test_basis_global_phase_02(self): self.assertEqual(Operator(circ), Operator(ccirc)) def test_basis_global_phase_03(self): - """Test global phase preservation if cummulative z-rotation is 0""" + """Test global phase preservation if cumulative z-rotation is 0""" circ = QuantumCircuit(1) circ.rz(np.pi / 2, 0) circ.p(np.pi / 2, 0) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 9b34d095b3b1..8a11af2bd688 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -517,7 +517,7 @@ def test_inverted_order(self): # The first two 'if' blocks here represent exactly the same operation as each other on the # outer bits, because in the second, the bit-order of the block is reversed, but so is the # order of the bits in the outer circuit that they're bound to, which makes them the same. - # The second two 'if' blocks also represnt the same operation as each other, but the 'first + # The second two 'if' blocks also represent the same operation as each other, but the 'first # two' and 'second two' pairs represent qubit-flipped operations. qc.if_test((0, False), body.copy(), qc.qubits, qc.clbits) qc.if_test((0, False), body.reverse_bits(), reversed(qc.qubits), qc.clbits) diff --git a/test/python/transpiler/test_full_ancilla_allocation.py b/test/python/transpiler/test_full_ancilla_allocation.py index 73d9708d0ba6..452d9d93965a 100644 --- a/test/python/transpiler/test_full_ancilla_allocation.py +++ b/test/python/transpiler/test_full_ancilla_allocation.py @@ -194,7 +194,7 @@ def test_name_collision(self): ) def test_bad_layout(self): - """Layout referes to a register that do not exist in the circuit""" + """Layout refers to a register that do not exist in the circuit""" qr = QuantumRegister(3, "q") circ = QuantumCircuit(qr) dag = circuit_to_dag(circ) diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py index 1e0f19b1a331..569a210f8a9d 100644 --- a/test/python/transpiler/test_gate_direction.py +++ b/test/python/transpiler/test_gate_direction.py @@ -342,7 +342,7 @@ def test_symmetric_gates(self, gate): self.assertEqual(pass_(circuit), expected) def test_target_parameter_any(self): - """Test that a parametrised 2q gate is replaced correctly both if available and not + """Test that a parametrized 2q gate is replaced correctly both if available and not available.""" circuit = QuantumCircuit(2) circuit.rzx(1.5, 0, 1) @@ -356,7 +356,7 @@ def test_target_parameter_any(self): self.assertNotEqual(GateDirection(None, target=swapped)(circuit), circuit) def test_target_parameter_exact(self): - """Test that a parametrised 2q gate is detected correctly both if available and not + """Test that a parametrized 2q gate is detected correctly both if available and not available.""" circuit = QuantumCircuit(2) circuit.rzx(1.5, 0, 1) diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index f20b102d1838..ff54169374bc 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -2045,7 +2045,7 @@ def test_unroll_empty_definition_with_phase(self): self.assertEqual(pass_(qc), expected) def test_leave_store_alone_basis(self): - """Don't attempt to synthesise `Store` instructions with basis gates.""" + """Don't attempt to synthesize `Store` instructions with basis gates.""" pass_ = HighLevelSynthesis(equivalence_library=std_eqlib, basis_gates=["u", "cx"]) @@ -2068,7 +2068,7 @@ def test_leave_store_alone_basis(self): self.assertEqual(pass_(qc), expected) def test_leave_store_alone_with_target(self): - """Don't attempt to synthesise `Store` instructions with a `Target`.""" + """Don't attempt to synthesize `Store` instructions with a `Target`.""" # Note no store. target = Target() diff --git a/test/python/transpiler/test_instruction_alignments.py b/test/python/transpiler/test_instruction_alignments.py index bd14891bb8c4..1431449779b3 100644 --- a/test/python/transpiler/test_instruction_alignments.py +++ b/test/python/transpiler/test_instruction_alignments.py @@ -98,7 +98,7 @@ def test_valid_pulse_duration(self): pm.run(circuit) def test_no_calibration(self): - """No error raises if no calibration is addedd.""" + """No error raises if no calibration is added.""" circuit = QuantumCircuit(1) circuit.x(0) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index c00208a2ffaf..ee85dc34ffd6 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1110,7 +1110,7 @@ def test_1(self, circuit, level): self.assertIn("swap", resulting_basis) # Skipping optimization level 3 because the swap gates get absorbed into - # a unitary block as part of the KAK decompostion optimization passes and + # a unitary block as part of the KAK decomposition optimization passes and # optimized away. @combine( level=[0, 1, 2], @@ -1487,7 +1487,7 @@ def _define(self): optimization_level=optimization_level, seed_transpiler=2022_10_04, ) - # Tests of the complete validity of a circuit are mostly done at the indiviual pass level; + # Tests of the complete validity of a circuit are mostly done at the individual pass level; # here we're just checking that various passes do appear to have run. self.assertIsInstance(transpiled, QuantumCircuit) # Assert layout ran. diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index 487fbf9daef1..0a7b977162a3 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -358,7 +358,7 @@ def test_dual_ghz_with_wide_barrier(self): self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8]) def test_dual_ghz_with_intermediate_barriers(self): - """Test dual ghz circuit with intermediate barriers local to each componennt.""" + """Test dual ghz circuit with intermediate barriers local to each component.""" qc = QuantumCircuit(8, name="double dhz") qc.h(0) qc.cz(0, 1) diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index fbe4e1fbf74c..b1effdae7d8b 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -241,7 +241,7 @@ def test_do_not_reorder_measurements(self): self.assertIsInstance(second_measure.operation, Measure) # Assert that the first measure is on the same qubit that the HGate was applied to, and the # second measurement is on a different qubit (though we don't care which exactly - that - # depends a little on the randomisation of the pass). + # depends a little on the randomization of the pass). self.assertEqual(last_h.qubits, first_measure.qubits) self.assertNotEqual(last_h.qubits, second_measure.qubits) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 1e4da01cb426..d7c4baa18fd9 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -43,7 +43,7 @@ def _ry_to_rz_template_pass(parameter: Parameter = None, extra_costs=None): - """Create a simple pass manager that runs a template optimisation with a single transformation. + """Create a simple pass manager that runs a template optimization with a single transformation. It turns ``RX(pi/2).RY(parameter).RX(-pi/2)`` into the equivalent virtual ``RZ`` rotation, where if ``parameter`` is given, it will be the instance used in the template.""" if parameter is None: @@ -409,7 +409,7 @@ def test_optimizer_does_not_replace_unbound_partial_match(self): circuit_out = PassManager(pass_).run(circuit_in) - # The template optimisation should not have replaced anything, because + # The template optimization should not have replaced anything, because # that would require it to leave dummy parameters in place without # binding them. self.assertEqual(circuit_in, circuit_out) diff --git a/test/python/transpiler/test_token_swapper.py b/test/python/transpiler/test_token_swapper.py index 9ded634eba75..8a3a8c72ee2d 100644 --- a/test/python/transpiler/test_token_swapper.py +++ b/test/python/transpiler/test_token_swapper.py @@ -67,7 +67,7 @@ def test_small(self) -> None: self.assertEqual({i: i for i in range(8)}, permutation) def test_bug1(self) -> None: - """Tests for a bug that occured in happy swap chains of length >2.""" + """Tests for a bug that occurred in happy swap chains of length >2.""" graph = rx.PyGraph() graph.extend_from_edge_list( [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (3, 6)] diff --git a/test/python/transpiler/test_unitary_synthesis_plugin.py b/test/python/transpiler/test_unitary_synthesis_plugin.py index ceca591ce087..f6790e8ed14d 100644 --- a/test/python/transpiler/test_unitary_synthesis_plugin.py +++ b/test/python/transpiler/test_unitary_synthesis_plugin.py @@ -71,7 +71,7 @@ class ControllableSynthesis(UnitarySynthesisPlugin): """A dummy synthesis plugin, which can have its ``supports_`` properties changed to test different parts of the synthesis plugin interface. By default, it accepts all keyword arguments and accepts all number of qubits, but if its run method is called, it just returns ``None`` to - indicate that the gate should not be synthesised.""" + indicate that the gate should not be synthesized.""" min_qubits = None max_qubits = None @@ -153,7 +153,7 @@ def mock_default_run_method(self): # We need to mock out DefaultUnitarySynthesis.run, except it will actually get called as an # instance method, so we can't just wrap the method defined on the class, but instead we # need to wrap a method that has been bound to a particular instance. This is slightly - # frgaile, because we're likely wrapping a _different_ instance, but since there are no + # fragile, because we're likely wrapping a _different_ instance, but since there are no # arguments to __init__, and no internal state, it should be ok. It doesn't matter if we # dodged the patching of the manager class that happens elsewhere in this test suite, # because we're always accessing something that the patch would delegate to the inner diff --git a/test/python/utils/test_lazy_loaders.py b/test/python/utils/test_lazy_loaders.py index bd63d7ff04ac..11b37ccb9d15 100644 --- a/test/python/utils/test_lazy_loaders.py +++ b/test/python/utils/test_lazy_loaders.py @@ -423,7 +423,7 @@ def exec_module(self, module): def test_import_allows_attributes_failure(self): """Check that the import tester can accept a dictionary mapping module names to attributes, - and that these are recognised when they are missing.""" + and that these are recognized when they are missing.""" # We can just use existing modules for this. name_map = { "sys": ("executable", "path"), diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py index 2a0a61c7904b..e7d28aac8a9e 100644 --- a/test/python/visualization/test_circuit_text_drawer.py +++ b/test/python/visualization/test_circuit_text_drawer.py @@ -5626,7 +5626,7 @@ def test_draw_hamiltonian_single(self): self.assertEqual(circuit.draw(output="text").single_string(), expected) def test_draw_hamiltonian_multi(self): - """Text Hamiltonian gate with mutiple qubits.""" + """Text Hamiltonian gate with multiple qubits.""" expected = "\n".join( [ " ┌──────────────┐", @@ -5647,7 +5647,7 @@ def test_draw_hamiltonian_multi(self): class TestTextPhase(QiskitTestCase): - """Testing the draweing a circuit with phase""" + """Testing the drawing a circuit with phase""" def test_bell(self): """Text Bell state with phase.""" diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index f2ce2bee1086..cc70cccf7d4d 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -975,7 +975,7 @@ def load_qpy(qpy_files, version_parts): def _main(): - parser = argparse.ArgumentParser(description="Test QPY backwards compatibilty") + parser = argparse.ArgumentParser(description="Test QPY backwards compatibility") parser.add_argument("command", choices=["generate", "load"]) parser.add_argument( "--version", diff --git a/test/utils/_canonical.py b/test/utils/_canonical.py index 367281f512c9..b05254b3e7bc 100644 --- a/test/utils/_canonical.py +++ b/test/utils/_canonical.py @@ -52,7 +52,7 @@ def canonicalize_control_flow(circuit: QuantumCircuit) -> QuantumCircuit: """Canonicalize all control-flow operations in a circuit. This is not an efficient operation, and does not affect any properties of the circuit. Its - intent is to normalise parts of circuits that have a non-deterministic construction. These are + intent is to normalize parts of circuits that have a non-deterministic construction. These are the ordering of bit arguments in control-flow blocks output by the builder interface, and automatically generated ``for``-loop variables. From 591260f0694fb1944507283ce10cf8f068bd3935 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 20 Jun 2024 11:09:00 +0200 Subject: [PATCH 05/89] use compose instead of + (#12609) --- qiskit/circuit/library/n_local/two_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/n_local/two_local.py b/qiskit/circuit/library/n_local/two_local.py index 1cb388349fec..f3822d53243f 100644 --- a/qiskit/circuit/library/n_local/two_local.py +++ b/qiskit/circuit/library/n_local/two_local.py @@ -110,7 +110,7 @@ class TwoLocal(NLocal): >>> entangler_map = [[0, 3], [0, 2]] # entangle the first and last two-way >>> two = TwoLocal(4, [], 'cry', entangler_map, reps=1) - >>> circuit = two + two + >>> circuit = two.compose(two) >>> print(circuit.decompose().draw()) # note, that the parameters are the same! q_0: ─────■───────────■───────────■───────────■────── │ │ │ │ From 7d1731b60d8dd6219a292dc62f24d1a8d780e43a Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 20 Jun 2024 22:01:56 +0900 Subject: [PATCH 06/89] Fix v2 pulse drawer (#12608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix error when V2 model is set * Apply suggestions from code review * Fix black --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> Co-authored-by: Elena Peña Tapia --- qiskit/visualization/pulse_v2/device_info.py | 89 ++++++++++++------- .../fix-v2-pulse-drawer-d05e4e392766909f.yaml | 7 ++ 2 files changed, 65 insertions(+), 31 deletions(-) create mode 100644 releasenotes/notes/fix-v2-pulse-drawer-d05e4e392766909f.yaml diff --git a/qiskit/visualization/pulse_v2/device_info.py b/qiskit/visualization/pulse_v2/device_info.py index 1e809c43abd2..7898f978772c 100644 --- a/qiskit/visualization/pulse_v2/device_info.py +++ b/qiskit/visualization/pulse_v2/device_info.py @@ -40,7 +40,7 @@ class :py:class:``DrawerBackendInfo`` with necessary methods to generate drawing from qiskit import pulse from qiskit.providers import BackendConfigurationError -from qiskit.providers.backend import Backend +from qiskit.providers.backend import Backend, BackendV2 class DrawerBackendInfo(ABC): @@ -106,40 +106,67 @@ def create_from_backend(cls, backend: Backend): Returns: OpenPulseBackendInfo: New configured instance. """ - configuration = backend.configuration() - defaults = backend.defaults() - - # load name - name = backend.name() - - # load cycle time - dt = configuration.dt - - # load frequencies chan_freqs = {} - - chan_freqs.update( - {pulse.DriveChannel(qind): freq for qind, freq in enumerate(defaults.qubit_freq_est)} - ) - chan_freqs.update( - {pulse.MeasureChannel(qind): freq for qind, freq in enumerate(defaults.meas_freq_est)} - ) - for qind, u_lo_mappers in enumerate(configuration.u_channel_lo): - temp_val = 0.0 + 0.0j - for u_lo_mapper in u_lo_mappers: - temp_val += defaults.qubit_freq_est[u_lo_mapper.q] * u_lo_mapper.scale - chan_freqs[pulse.ControlChannel(qind)] = temp_val.real - - # load qubit channel mapping qubit_channel_map = defaultdict(list) - for qind in range(configuration.n_qubits): - qubit_channel_map[qind].append(configuration.drive(qubit=qind)) - qubit_channel_map[qind].append(configuration.measure(qubit=qind)) - for tind in range(configuration.n_qubits): + + if hasattr(backend, "configuration") and hasattr(backend, "defaults"): + configuration = backend.configuration() + defaults = backend.defaults() + + name = configuration.backend_name + dt = configuration.dt + + # load frequencies + chan_freqs.update( + { + pulse.DriveChannel(qind): freq + for qind, freq in enumerate(defaults.qubit_freq_est) + } + ) + chan_freqs.update( + { + pulse.MeasureChannel(qind): freq + for qind, freq in enumerate(defaults.meas_freq_est) + } + ) + for qind, u_lo_mappers in enumerate(configuration.u_channel_lo): + temp_val = 0.0 + 0.0j + for u_lo_mapper in u_lo_mappers: + temp_val += defaults.qubit_freq_est[u_lo_mapper.q] * u_lo_mapper.scale + chan_freqs[pulse.ControlChannel(qind)] = temp_val.real + + # load qubit channel mapping + for qind in range(configuration.n_qubits): + qubit_channel_map[qind].append(configuration.drive(qubit=qind)) + qubit_channel_map[qind].append(configuration.measure(qubit=qind)) + for tind in range(configuration.n_qubits): + try: + qubit_channel_map[qind].extend(configuration.control(qubits=(qind, tind))) + except BackendConfigurationError: + pass + elif isinstance(backend, BackendV2): + # Pure V2 model doesn't contain channel frequency information. + name = backend.name + dt = backend.dt + + # load qubit channel mapping + for qind in range(backend.num_qubits): + # channels are NotImplemented by default so we must catch arbitrary error. + try: + qubit_channel_map[qind].append(backend.drive_channel(qind)) + except Exception: # pylint: disable=broad-except + pass try: - qubit_channel_map[qind].extend(configuration.control(qubits=(qind, tind))) - except BackendConfigurationError: + qubit_channel_map[qind].append(backend.measure_channel(qind)) + except Exception: # pylint: disable=broad-except pass + for tind in range(backend.num_qubits): + try: + qubit_channel_map[qind].extend(backend.control_channel(qubits=(qind, tind))) + except Exception: # pylint: disable=broad-except + pass + else: + raise RuntimeError("Backend object not yet supported") return OpenPulseBackendInfo( name=name, dt=dt, channel_frequency_map=chan_freqs, qubit_channel_map=qubit_channel_map diff --git a/releasenotes/notes/fix-v2-pulse-drawer-d05e4e392766909f.yaml b/releasenotes/notes/fix-v2-pulse-drawer-d05e4e392766909f.yaml new file mode 100644 index 000000000000..b158703c6b89 --- /dev/null +++ b/releasenotes/notes/fix-v2-pulse-drawer-d05e4e392766909f.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed a bug in :func:`qiskit.visualization.pulse_v2.interface.draw` that didn't + draw pulse schedules when the draw function was called with a :class:`.BackendV2` argument. + Because the V2 backend doesn't report hardware channel frequencies, + the generated drawing will show 'no freq.' below each channel label. From b6c61661272c2e242963c416e64a9a2e050ea25d Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 20 Jun 2024 18:15:25 +0100 Subject: [PATCH 07/89] Invalidate `parameters` cache on circuit copy (#12619) Previously, the caching of the parameter view could persist between copies of the circuit because it was part of the `copy.copy`. --- qiskit/circuit/quantumcircuit.py | 2 ++ .../fix-parameter-cache-05eac2f24477ccb8.yaml | 7 ++++++ test/python/circuit/test_parameters.py | 23 +++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 releasenotes/notes/fix-parameter-cache-05eac2f24477ccb8.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index a88dfd43ea4b..ee52e3308a94 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -3674,6 +3674,8 @@ def copy_empty_like( cpy._data = CircuitData( self._data.qubits, self._data.clbits, global_phase=self._data.global_phase ) + # Invalidate parameters caching. + cpy._parameters = None cpy._calibrations = _copy.deepcopy(self._calibrations) cpy._metadata = _copy.deepcopy(self._metadata) diff --git a/releasenotes/notes/fix-parameter-cache-05eac2f24477ccb8.yaml b/releasenotes/notes/fix-parameter-cache-05eac2f24477ccb8.yaml new file mode 100644 index 000000000000..05ac759569f7 --- /dev/null +++ b/releasenotes/notes/fix-parameter-cache-05eac2f24477ccb8.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + The :attr:`.QuantumCircuit.parameters` attribute will now correctly be empty + when using :meth:`.QuantumCircuit.copy_empty_like` on a parametric circuit. + Previously, an internal cache would be copied over without invalidation. + Fix `#12617 `__. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 0095f87be9ae..f841f969c00c 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -203,6 +203,29 @@ def test_parameters_property_by_index(self): for i, vi in enumerate(v): self.assertEqual(vi, qc.parameters[i]) + def test_parameters_property_independent_after_copy(self): + """Test that any `parameters` property caching is invalidated after a copy operation.""" + a = Parameter("a") + b = Parameter("b") + c = Parameter("c") + + qc1 = QuantumCircuit(1) + qc1.rz(a, 0) + self.assertEqual(set(qc1.parameters), {a}) + + qc2 = qc1.copy_empty_like() + self.assertEqual(set(qc2.parameters), set()) + + qc3 = qc1.copy() + self.assertEqual(set(qc3.parameters), {a}) + qc3.rz(b, 0) + self.assertEqual(set(qc3.parameters), {a, b}) + self.assertEqual(set(qc1.parameters), {a}) + + qc1.rz(c, 0) + self.assertEqual(set(qc1.parameters), {a, c}) + self.assertEqual(set(qc3.parameters), {a, b}) + def test_get_parameter(self): """Test the `get_parameter` method.""" x = Parameter("x") From b8de17f9082089275ae979c21cf03b8e99e245b3 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 21 Jun 2024 11:15:07 +0100 Subject: [PATCH 08/89] Localise `py_op` caching data in `RefCell` (#12594) This localises the caching concerns of the `PackedInstruction::py_op` field into a method `unpack_py_op`, which can now update the cache through an immutable reference (if no other immutable references are taken out). Having the new method to encapsulate the `cache_pyops` feature simplifies the large `#[cfg(feature = "cache_pyop")]` gates. --- crates/circuit/src/circuit_data.rs | 363 ++-------------------- crates/circuit/src/circuit_instruction.rs | 82 ++++- 2 files changed, 102 insertions(+), 343 deletions(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index da35787e3207..07f4579a4cd3 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -10,10 +10,13 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +#[cfg(feature = "cache_pygates")] +use std::cell::RefCell; + use crate::bit_data::BitData; use crate::circuit_instruction::{ - convert_py_to_operation_type, operation_type_and_data_to_py, CircuitInstruction, - ExtraInstructionAttributes, OperationInput, PackedInstruction, + convert_py_to_operation_type, CircuitInstruction, ExtraInstructionAttributes, OperationInput, + PackedInstruction, }; use crate::imports::{BUILTIN_LIST, QUBIT}; use crate::interner::{IndexedInterner, Interner, InternerKey}; @@ -489,66 +492,40 @@ impl CircuitData { .getattr(intern!(py, "deepcopy"))?; for inst in &mut res.data { match &mut inst.op { - OperationType::Standard(_) => { - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } - } + OperationType::Standard(_) => {} OperationType::Gate(ref mut op) => { op.gate = deepcopy.call1((&op.gate,))?.unbind(); - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } OperationType::Instruction(ref mut op) => { op.instruction = deepcopy.call1((&op.instruction,))?.unbind(); - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } OperationType::Operation(ref mut op) => { op.operation = deepcopy.call1((&op.operation,))?.unbind(); - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } }; + #[cfg(feature = "cache_pygates")] + { + *inst.py_op.borrow_mut() = None; + } } } else if copy_instructions { for inst in &mut res.data { match &mut inst.op { - OperationType::Standard(_) => { - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } - } + OperationType::Standard(_) => {} OperationType::Gate(ref mut op) => { op.gate = op.gate.call_method0(py, intern!(py, "copy"))?; - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } OperationType::Instruction(ref mut op) => { op.instruction = op.instruction.call_method0(py, intern!(py, "copy"))?; - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } OperationType::Operation(ref mut op) => { op.operation = op.operation.call_method0(py, intern!(py, "copy"))?; - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } }; + #[cfg(feature = "cache_pygates")] + { + *inst.py_op.borrow_mut() = None; + } } } Ok(res) @@ -589,87 +566,10 @@ impl CircuitData { /// Args: /// func (Callable[[:class:`~.Operation`], None]): /// The callable to invoke. - #[cfg(not(feature = "cache_pygates"))] #[pyo3(signature = (func))] pub fn foreach_op(&self, py: Python<'_>, func: &Bound) -> PyResult<()> { for inst in self.data.iter() { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - - let op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - func.call1((op,))?; - } - Ok(()) - } - - /// Invokes callable ``func`` with each instruction's operation. - /// - /// Args: - /// func (Callable[[:class:`~.Operation`], None]): - /// The callable to invoke. - #[cfg(feature = "cache_pygates")] - #[pyo3(signature = (func))] - pub fn foreach_op(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { - for inst in self.data.iter_mut() { - let op = match &inst.py_op { - Some(op) => op.clone_ref(py), - None => { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - let new_op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - inst.py_op = Some(new_op.clone_ref(py)); - new_op - } - }; - func.call1((op,))?; + func.call1((inst.unpack_py_op(py)?,))?; } Ok(()) } @@ -680,88 +580,10 @@ impl CircuitData { /// Args: /// func (Callable[[int, :class:`~.Operation`], None]): /// The callable to invoke. - #[cfg(not(feature = "cache_pygates"))] #[pyo3(signature = (func))] pub fn foreach_op_indexed(&self, py: Python<'_>, func: &Bound) -> PyResult<()> { for (index, inst) in self.data.iter().enumerate() { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - - let op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - func.call1((index, op))?; - } - Ok(()) - } - - /// Invokes callable ``func`` with the positional index and operation - /// of each instruction. - /// - /// Args: - /// func (Callable[[int, :class:`~.Operation`], None]): - /// The callable to invoke. - #[cfg(feature = "cache_pygates")] - #[pyo3(signature = (func))] - pub fn foreach_op_indexed(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { - for (index, inst) in self.data.iter_mut().enumerate() { - let op = match &inst.py_op { - Some(op) => op.clone_ref(py), - None => { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - let new_op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - inst.py_op = Some(new_op.clone_ref(py)); - new_op - } - }; - func.call1((index, op))?; + func.call1((index, inst.unpack_py_op(py)?))?; } Ok(()) } @@ -779,49 +601,23 @@ impl CircuitData { /// func (Callable[[:class:`~.Operation`], :class:`~.Operation`]): /// A callable used to map original operation to their /// replacements. - #[cfg(not(feature = "cache_pygates"))] #[pyo3(signature = (func))] pub fn map_ops(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { for inst in self.data.iter_mut() { - let old_op = match &inst.op { - OperationType::Standard(op) => { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - if condition.is_some() { - operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )? - } else { - op.into_py(py) + let py_op = { + if let OperationType::Standard(op) = inst.op { + match inst.extra_attrs.as_deref() { + None + | Some(ExtraInstructionAttributes { + condition: None, .. + }) => op.into_py(py), + _ => inst.unpack_py_op(py)?, } + } else { + inst.unpack_py_op(py)? } - OperationType::Gate(op) => op.gate.clone_ref(py), - OperationType::Instruction(op) => op.instruction.clone_ref(py), - OperationType::Operation(op) => op.operation.clone_ref(py), }; - let result: OperationInput = func.call1((old_op,))?.extract()?; + let result: OperationInput = func.call1((py_op,))?.extract()?; match result { OperationInput::Standard(op) => { inst.op = OperationType::Standard(op); @@ -836,7 +632,7 @@ impl CircuitData { inst.op = OperationType::Operation(op); } OperationInput::Object(new_op) => { - let new_inst_details = convert_py_to_operation_type(py, new_op)?; + let new_inst_details = convert_py_to_operation_type(py, new_op.clone_ref(py))?; inst.op = new_inst_details.operation; inst.params = new_inst_details.params; if new_inst_details.label.is_some() @@ -851,103 +647,10 @@ impl CircuitData { condition: new_inst_details.condition, })) } - } - } - } - Ok(()) - } - - /// Invokes callable ``func`` with each instruction's operation, - /// replacing the operation with the result. - /// - /// .. note:: - /// - /// This is only to be used by map_vars() in quantumcircuit.py it - /// assumes that a full Python instruction will only be returned from - /// standard gates iff a condition is set. - /// - /// Args: - /// func (Callable[[:class:`~.Operation`], :class:`~.Operation`]): - /// A callable used to map original operation to their - /// replacements. - #[cfg(feature = "cache_pygates")] - #[pyo3(signature = (func))] - pub fn map_ops(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { - for inst in self.data.iter_mut() { - let old_op = match &inst.py_op { - Some(op) => op.clone_ref(py), - None => match &inst.op { - OperationType::Standard(op) => { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - if condition.is_some() { - let new_op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - inst.py_op = Some(new_op.clone_ref(py)); - new_op - } else { - op.into_py(py) - } - } - OperationType::Gate(op) => op.gate.clone_ref(py), - OperationType::Instruction(op) => op.instruction.clone_ref(py), - OperationType::Operation(op) => op.operation.clone_ref(py), - }, - }; - let result: OperationInput = func.call1((old_op,))?.extract()?; - match result { - OperationInput::Standard(op) => { - inst.op = OperationType::Standard(op); - } - OperationInput::Gate(op) => { - inst.op = OperationType::Gate(op); - } - OperationInput::Instruction(op) => { - inst.op = OperationType::Instruction(op); - } - OperationInput::Operation(op) => { - inst.op = OperationType::Operation(op); - } - OperationInput::Object(new_op) => { - let new_inst_details = convert_py_to_operation_type(py, new_op.clone_ref(py))?; - inst.op = new_inst_details.operation; - inst.params = new_inst_details.params; - if new_inst_details.label.is_some() - || new_inst_details.duration.is_some() - || new_inst_details.unit.is_some() - || new_inst_details.condition.is_some() + #[cfg(feature = "cache_pygates")] { - inst.extra_attrs = Some(Box::new(ExtraInstructionAttributes { - label: new_inst_details.label, - duration: new_inst_details.duration, - unit: new_inst_details.unit, - condition: new_inst_details.condition, - })) + *inst.py_op.borrow_mut() = Some(new_op); } - inst.py_op = Some(new_op); } } } @@ -1537,7 +1240,7 @@ impl CircuitData { params: inst.params.clone(), extra_attrs: inst.extra_attrs.clone(), #[cfg(feature = "cache_pygates")] - py_op: inst.py_op.clone(), + py_op: RefCell::new(inst.py_op.clone()), }) } @@ -1557,7 +1260,7 @@ impl CircuitData { params: inst.params.clone(), extra_attrs: inst.extra_attrs.clone(), #[cfg(feature = "cache_pygates")] - py_op: inst.py_op.clone(), + py_op: RefCell::new(inst.py_op.clone()), }) } } diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 2bb90367082d..5179190d8aa2 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -10,6 +10,9 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +#[cfg(feature = "cache_pygates")] +use std::cell::RefCell; + use pyo3::basic::CompareOp; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; @@ -47,8 +50,61 @@ pub(crate) struct PackedInstruction { pub clbits_id: Index, pub params: SmallVec<[Param; 3]>, pub extra_attrs: Option>, + #[cfg(feature = "cache_pygates")] - pub py_op: Option, + /// This is hidden in a `RefCell` because, while that has additional memory-usage implications + /// while we're still building with the feature enabled, we intend to remove the feature in the + /// future, and hiding the cache within a `RefCell` lets us keep the cache transparently in our + /// interfaces, without needing various functions to unnecessarily take `&mut` references. + pub py_op: RefCell>, +} + +impl PackedInstruction { + /// Build a reference to the Python-space operation object (the `Gate`, etc) packed into this + /// instruction. This may construct the reference if the `PackedInstruction` is a standard + /// gate with no already stored operation. + /// + /// A standard-gate operation object returned by this function is disconnected from the + /// containing circuit; updates to its label, duration, unit and condition will not be + /// propagated back. + pub fn unpack_py_op(&self, py: Python) -> PyResult> { + #[cfg(feature = "cache_pygates")] + { + if let Some(cached_op) = self.py_op.borrow().as_ref() { + return Ok(cached_op.clone_ref(py)); + } + } + let (label, duration, unit, condition) = match self.extra_attrs.as_deref() { + Some(ExtraInstructionAttributes { + label, + duration, + unit, + condition, + }) => ( + label.as_deref(), + duration.as_ref(), + unit.as_deref(), + condition.as_ref(), + ), + None => (None, None, None, None), + }; + let out = operation_type_and_data_to_py( + py, + &self.op, + &self.params, + label, + duration, + unit, + condition, + )?; + #[cfg(feature = "cache_pygates")] + { + if let Ok(mut cell) = self.py_op.try_borrow_mut() { + cell.get_or_insert_with(|| out.clone_ref(py)); + } + } + Ok(out) + } } /// A single instruction in a :class:`.QuantumCircuit`, comprised of the :attr:`operation` and @@ -666,20 +722,20 @@ pub(crate) fn operation_type_to_py( let (label, duration, unit, condition) = match &circuit_inst.extra_attrs { None => (None, None, None, None), Some(extra_attrs) => ( - extra_attrs.label.clone(), - extra_attrs.duration.clone(), - extra_attrs.unit.clone(), - extra_attrs.condition.clone(), + extra_attrs.label.as_deref(), + extra_attrs.duration.as_ref(), + extra_attrs.unit.as_deref(), + extra_attrs.condition.as_ref(), ), }; operation_type_and_data_to_py( py, &circuit_inst.operation, &circuit_inst.params, - &label, - &duration, - &unit, - &condition, + label, + duration, + unit, + condition, ) } @@ -692,10 +748,10 @@ pub(crate) fn operation_type_and_data_to_py( py: Python, operation: &OperationType, params: &[Param], - label: &Option, - duration: &Option, - unit: &Option, - condition: &Option, + label: Option<&str>, + duration: Option<&PyObject>, + unit: Option<&str>, + condition: Option<&PyObject>, ) -> PyResult { match &operation { OperationType::Standard(op) => { From 91f0c70885e6b58d1168e43c330373197b1c19ee Mon Sep 17 00:00:00 2001 From: "Tiago R. Cunha" <155388148+cstiago@users.noreply.github.com> Date: Fri, 21 Jun 2024 08:29:48 -0300 Subject: [PATCH 09/89] Fix type hint in SolovayKitaevDecomposition (#12627) The return type hint of `find_basic_approximation` method changes from `Gate` to `GateSequence` in `SolovayKitaevDecomposition` class, as implied by `self.basic_approximations`. With no remaining mentions of `Gate`, its corresponding import statement is removed. --- qiskit/synthesis/discrete_basis/solovay_kitaev.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/qiskit/synthesis/discrete_basis/solovay_kitaev.py b/qiskit/synthesis/discrete_basis/solovay_kitaev.py index 2e5cfeafcecd..2c8df5bd1b6d 100644 --- a/qiskit/synthesis/discrete_basis/solovay_kitaev.py +++ b/qiskit/synthesis/discrete_basis/solovay_kitaev.py @@ -16,8 +16,6 @@ import numpy as np -from qiskit.circuit.gate import Gate - from .gate_sequence import GateSequence from .commutator_decompose import commutator_decompose from .generate_basis_approximations import generate_basic_approximations, _1q_gates, _1q_inverses @@ -157,14 +155,14 @@ def _recurse(self, sequence: GateSequence, n: int, check_input: bool = True) -> w_n1 = self._recurse(w_n, n - 1, check_input=check_input) return v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1) - def find_basic_approximation(self, sequence: GateSequence) -> Gate: - """Finds gate in ``self._basic_approximations`` that best represents ``sequence``. + def find_basic_approximation(self, sequence: GateSequence) -> GateSequence: + """Find ``GateSequence`` in ``self._basic_approximations`` that approximates ``sequence``. Args: - sequence: The gate to find the approximation to. + sequence: ``GateSequence`` to find the approximation to. Returns: - Gate in basic approximations that is closest to ``sequence``. + ``GateSequence`` in ``self._basic_approximations`` that approximates ``sequence``. """ # TODO explore using a k-d tree here From 8752f900f090bb46bc1e2468d76f99a797b9182f Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Fri, 21 Jun 2024 08:18:28 -0400 Subject: [PATCH 10/89] Add remaining tests for `ParameterVector` and tweak its `repr` (#12597) --- qiskit/circuit/parametervector.py | 2 +- test/python/circuit/test_parameters.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/qiskit/circuit/parametervector.py b/qiskit/circuit/parametervector.py index 151e3e7fea73..7b32395e1430 100644 --- a/qiskit/circuit/parametervector.py +++ b/qiskit/circuit/parametervector.py @@ -87,7 +87,7 @@ def __str__(self): return f"{self.name}, {[str(item) for item in self.params]}" def __repr__(self): - return f"{self.__class__.__name__}(name={self.name}, length={len(self)})" + return f"{self.__class__.__name__}(name={repr(self.name)}, length={len(self)})" def resize(self, length): """Resize the parameter vector. If necessary, new elements are generated. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index f841f969c00c..f580416eccf5 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1398,6 +1398,21 @@ def test_parametervector_resize(self): self.assertEqual(element, vec[1]) self.assertListEqual([param.name for param in vec], _paramvec_names("x", 3)) + def test_parametervector_repr(self): + """Test the __repr__ method of the parameter vector.""" + vec = ParameterVector("x", 2) + self.assertEqual(repr(vec), "ParameterVector(name='x', length=2)") + + def test_parametervector_str(self): + """Test the __str__ method of the parameter vector.""" + vec = ParameterVector("x", 2) + self.assertEqual(str(vec), "x, ['x[0]', 'x[1]']") + + def test_parametervector_index(self): + """Test the index method of the parameter vector.""" + vec = ParameterVector("x", 2) + self.assertEqual(vec.index(vec[1]), 1) + def test_raise_if_sub_unknown_parameters(self): """Verify we raise if asked to sub a parameter not in self.""" x = Parameter("x") From 22c145aa0e978e6cdefd59d0ff43e371b2d6487b Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 21 Jun 2024 13:28:57 +0100 Subject: [PATCH 11/89] Fix `CircuitInstruction` legacy iterable typing (#12630) The legacy 3-tuple format of `CircuitInstruction` still exposes the object in the old tuple-like way of `(Operation, list[Qubit], list[Clbit])`, rather than the new-style attribute access using tuples for the qargs and cargs. This was inadvertantly changed when it moved to Rust. --- crates/circuit/src/circuit_instruction.rs | 12 ++++++++++-- test/python/circuit/test_circuit_data.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 5179190d8aa2..93e73ccbc42f 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -542,7 +542,11 @@ impl CircuitInstruction { Ok(PyTuple::new_bound( py, - [op, self.qubits.to_object(py), self.clbits.to_object(py)], + [ + op, + self.qubits.bind(py).to_list().into(), + self.clbits.bind(py).to_list().into(), + ], )) } @@ -558,7 +562,11 @@ impl CircuitInstruction { }; Ok(PyTuple::new_bound( py, - [op, self.qubits.to_object(py), self.clbits.to_object(py)], + [ + op, + self.qubits.bind(py).to_list().into(), + self.clbits.bind(py).to_list().into(), + ], )) } diff --git a/test/python/circuit/test_circuit_data.py b/test/python/circuit/test_circuit_data.py index 6fc6e8e72bd7..35ae27b2fcfb 100644 --- a/test/python/circuit/test_circuit_data.py +++ b/test/python/circuit/test_circuit_data.py @@ -403,6 +403,22 @@ class TestQuantumCircuitInstructionData(QiskitTestCase): # but are included as tests to maintain compatability with the previous # list interface of circuit.data. + def test_iteration_of_data_entry(self): + """Verify that the base types of the legacy tuple iteration are correct, since they're + different to attribute access.""" + qc = QuantumCircuit(3, 3) + qc.h(0) + qc.cx(0, 1) + qc.cx(1, 2) + qc.measure([0, 1, 2], [0, 1, 2]) + + def to_legacy(instruction): + return (instruction.operation, list(instruction.qubits), list(instruction.clbits)) + + expected = [to_legacy(instruction) for instruction in qc.data] + actual = [tuple(instruction) for instruction in qc.data] + self.assertEqual(actual, expected) + def test_getitem_by_insertion_order(self): """Verify one can get circuit.data items in insertion order.""" qr = QuantumRegister(2) From 87aa89c19387ef7ff4177ef5144f60119da392e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Fri, 21 Jun 2024 17:25:59 +0200 Subject: [PATCH 12/89] Add Rust representation for `XXMinusYYGate` and `XXPlusYYGate` (#12606) * Add XXMinusYYGate and XXPlusYYGate, implement parameter multiplication function (naive approach). Co-authored by: Julien Gacon jul@zurich.ibm.com * * Format code * Use multiply_param in RZGate * Fix signs and indices --- crates/circuit/src/gate_matrix.rs | 46 +++++ crates/circuit/src/imports.rs | 14 +- crates/circuit/src/operations.rs | 167 ++++++++++++++---- .../library/standard_gates/xx_minus_yy.py | 3 + .../library/standard_gates/xx_plus_yy.py | 3 + 5 files changed, 194 insertions(+), 39 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index ad8c918e73bc..23ce94869227 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -212,6 +212,7 @@ pub static ISWAP_GATE: [[Complex64; 4]; 4] = [ ]; pub static S_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., 1.)]]; + pub static SDG_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., -1.)]]; @@ -219,6 +220,7 @@ pub static T_GATE: [[Complex64; 2]; 2] = [ [c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)], ]; + pub static TDG_GATE: [[Complex64; 2]; 2] = [ [c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], @@ -246,3 +248,47 @@ pub fn u_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { [c64(0., phi).exp() * sin, c64(0., phi + lam).exp() * cos], ] } + +#[inline] +pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [ + c64(cos, 0.), + c64(0., 0.), + c64(0., 0.), + c64(0., -sin) * c64(0., -beta).exp(), + ], + [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], + [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], + [ + c64(0., -sin) * c64(0., beta).exp(), + c64(0., 0.), + c64(0., 0.), + c64(cos, 0.), + ], + ] +} + +#[inline] +pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], + [ + c64(0., 0.), + c64(cos, 0.), + c64(0., -sin) * c64(0., -beta).exp(), + c64(0., 0.), + ], + [ + c64(0., 0.), + c64(0., -sin) * c64(0., beta).exp(), + c64(cos, 0.), + c64(0., 0.), + ], + [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], + ] +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 8db3b88fd7d2..3a9a942db8df 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -124,13 +124,23 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // SdgGate = 19 ["qiskit.circuit.library.standard_gates.s", "SdgGate"], // TGate = 20 - ["qiskit.circuit.library.standard_gates.s", "TGate"], + ["qiskit.circuit.library.standard_gates.t", "TGate"], // TdgGate = 21 - ["qiskit.circuit.library.standard_gates.s", "TdgGate"], + ["qiskit.circuit.library.standard_gates.t", "TdgGate"], // SXdgGate = 22 ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"], // iSWAPGate = 23 ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"], + //XXMinusYYGate = 24 + [ + "qiskit.circuit.library.standard_gates.xx_minus_yy", + "XXMinusYYGate", + ], + //XXPlusYYGate = 25 + [ + "qiskit.circuit.library.standard_gates.xx_plus_yy", + "XXPlusYYGate", + ], ]; /// A mapping from the enum variant in crate::operations::StandardGate to the python object for the diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 9048c55d9d48..6dedd3ac2061 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -203,14 +203,16 @@ pub enum StandardGate { TdgGate = 21, SXdgGate = 22, ISwapGate = 23, + XXMinusYYGate = 24, + XXPlusYYGate = 25, } static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ - 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, ]; static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ @@ -238,6 +240,8 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "tdg", "sxdg", "iswap", + "xx_minus_yy", + "xx_plus_yy", ]; #[pymethods] @@ -287,7 +291,7 @@ impl StandardGate { // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) -pub const STANDARD_GATE_SIZE: usize = 24; +pub const STANDARD_GATE_SIZE: usize = 26; impl Operation for StandardGate { fn name(&self) -> &str { @@ -416,6 +420,18 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::ISWAP_GATE).to_owned()), _ => None, }, + Self::XXMinusYYGate => match params { + [Param::Float(theta), Param::Float(beta)] => { + Some(aview2(&gate_matrix::xx_minus_yy_gate(*theta, *beta)).to_owned()) + } + _ => None, + }, + Self::XXPlusYYGate => match params { + [Param::Float(theta), Param::Float(beta)] => { + Some(aview2(&gate_matrix::xx_plus_yy_gate(*theta, *beta)).to_owned()) + } + _ => None, + }, } } @@ -502,6 +518,7 @@ impl Operation for StandardGate { }), Self::CXGate => None, Self::CCXGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; let q2 = smallvec![Qubit(2)]; let q0_1 = smallvec![Qubit(0), Qubit(1)]; @@ -524,7 +541,7 @@ impl Operation for StandardGate { (Self::TGate, smallvec![], q2.clone()), (Self::HGate, smallvec![], q2), (Self::CXGate, smallvec![], q0_1.clone()), - (Self::TGate, smallvec![], smallvec![Qubit(0)]), + (Self::TGate, smallvec![], q0), (Self::TdgGate, smallvec![], q1), (Self::CXGate, smallvec![], q0_1), ], @@ -536,39 +553,20 @@ impl Operation for StandardGate { Self::RXGate => todo!("Add when we have R"), Self::RYGate => todo!("Add when we have R"), Self::RZGate => Python::with_gil(|py| -> Option { - match ¶ms[0] { - Param::Float(theta) => Some( - CircuitData::from_standard_gates( - py, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(*theta)], - smallvec![Qubit(0)], - )], - Param::Float(-0.5 * theta), - ) - .expect("Unexpected Qiskit python bug"), - ), - Param::ParameterExpression(theta) => Some( - CircuitData::from_standard_gates( - py, - 1, - [( - Self::PhaseGate, - smallvec![Param::ParameterExpression(theta.clone_ref(py))], - smallvec![Qubit(0)], - )], - Param::ParameterExpression( - theta - .call_method1(py, intern!(py, "__rmul__"), (-0.5,)) - .expect("Parameter expression for global phase failed"), - ), - ) - .expect("Unexpected Qiskit python bug"), - ), - Param::Obj(_) => unreachable!(), - } + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::PhaseGate, + smallvec![theta.clone()], + smallvec![Qubit(0)], + )], + multiply_param(theta, -0.5, py), + ) + .expect("Unexpected Qiskit python bug"), + ) }), Self::ECRGate => todo!("Add when we have RZX"), Self::SwapGate => Python::with_gil(|py| -> Option { @@ -732,6 +730,88 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::XXMinusYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + let beta = ¶ms[1]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::RZGate, + smallvec![multiply_param(beta, -1.0, py)], + q1.clone(), + ), + (Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()), + (Self::SXGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI2)], q0.clone()), + (Self::SGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1.clone()), + ( + Self::RYGate, + smallvec![multiply_param(theta, 0.5, py)], + q0.clone(), + ), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q1.clone(), + ), + (Self::CXGate, smallvec![], q0_1), + (Self::SdgGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()), + (Self::SXdgGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI2)], q0), + (Self::RZGate, smallvec![beta.clone()], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::XXPlusYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q1_0 = smallvec![Qubit(1), Qubit(0)]; + let theta = ¶ms[0]; + let beta = ¶ms[1]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::RZGate, smallvec![beta.clone()], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()), + (Self::SXGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI2)], q1.clone()), + (Self::SGate, smallvec![], q0.clone()), + (Self::CXGate, smallvec![], q1_0.clone()), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q1.clone(), + ), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q0.clone(), + ), + (Self::CXGate, smallvec![], q1_0), + (Self::SdgGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()), + (Self::SXdgGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI2)], q1), + (Self::RZGate, smallvec![multiply_param(beta, -1.0, py)], q0), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), } } @@ -742,6 +822,19 @@ impl Operation for StandardGate { const FLOAT_ZERO: Param = Param::Float(0.0); +fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { + match param { + Param::Float(theta) => Param::Float(*theta * mult), + Param::ParameterExpression(theta) => Param::ParameterExpression( + theta + .clone_ref(py) + .call_method1(py, intern!(py, "__rmul__"), (mult,)) + .expect("Parameter expression for global phase failed"), + ), + Param::Obj(_) => unreachable!(), + } +} + /// This class is used to wrap a Python side Instruction that is not in the standard library #[derive(Clone, Debug)] #[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")] diff --git a/qiskit/circuit/library/standard_gates/xx_minus_yy.py b/qiskit/circuit/library/standard_gates/xx_minus_yy.py index 4bf4ab80eca2..db3c3dc89153 100644 --- a/qiskit/circuit/library/standard_gates/xx_minus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_minus_yy.py @@ -27,6 +27,7 @@ from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import QuantumRegister +from qiskit._accelerate.circuit import StandardGate class XXMinusYYGate(Gate): @@ -91,6 +92,8 @@ class XXMinusYYGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.XXMinusYYGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/xx_plus_yy.py b/qiskit/circuit/library/standard_gates/xx_plus_yy.py index a82316ed7b09..7920454d0b98 100644 --- a/qiskit/circuit/library/standard_gates/xx_plus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_plus_yy.py @@ -21,6 +21,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.parameterexpression import ParameterValueType +from qiskit._accelerate.circuit import StandardGate class XXPlusYYGate(Gate): @@ -87,6 +88,8 @@ class XXPlusYYGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.XXPlusYYGate + def __init__( self, theta: ParameterValueType, From de6c6eb2f944c58ecf2258801e921e0846d41d89 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 24 Jun 2024 10:02:31 +0200 Subject: [PATCH 13/89] Follow up on #12327: circuit construction in Rust (#12605) * Follow up on #12327 also port circuit construction to rust and add a reno * move _get_ordered_swap to Rust only * drop redundant Ok(expect()) * proper synthesis structure --- crates/accelerate/src/lib.rs | 2 +- crates/accelerate/src/synthesis/mod.rs | 22 ++++++ .../src/synthesis/permutation/mod.rs | 68 +++++++++++++++++++ .../permutation/utils.rs} | 58 ++++------------ crates/pyext/src/lib.rs | 10 +-- qiskit/__init__.py | 2 +- .../library/generalized_gates/permutation.py | 17 +++-- .../synthesis/permutation/permutation_full.py | 14 +--- .../permutation/permutation_utils.py | 3 +- .../oxidize-permbasic-be27578187ac472f.yaml | 4 ++ .../synthesis/test_permutation_synthesis.py | 14 ---- 11 files changed, 124 insertions(+), 90 deletions(-) create mode 100644 crates/accelerate/src/synthesis/mod.rs create mode 100644 crates/accelerate/src/synthesis/permutation/mod.rs rename crates/accelerate/src/{permutation.rs => synthesis/permutation/utils.rs} (66%) create mode 100644 releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 3924c1de4092..dcfbdc9f1878 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -23,12 +23,12 @@ pub mod isometry; pub mod nlayout; pub mod optimize_1q_gates; pub mod pauli_exp_val; -pub mod permutation; pub mod results; pub mod sabre; pub mod sampled_exp_val; pub mod sparse_pauli_op; pub mod stochastic_swap; +pub mod synthesis; pub mod two_qubit_decompose; pub mod uc_gate; pub mod utils; diff --git a/crates/accelerate/src/synthesis/mod.rs b/crates/accelerate/src/synthesis/mod.rs new file mode 100644 index 000000000000..f1a720459211 --- /dev/null +++ b/crates/accelerate/src/synthesis/mod.rs @@ -0,0 +1,22 @@ +// 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. + +mod permutation; + +use pyo3::prelude::*; +use pyo3::wrap_pymodule; + +#[pymodule] +pub fn synthesis(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pymodule!(permutation::permutation))?; + Ok(()) +} diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs new file mode 100644 index 000000000000..bf0ff97848f2 --- /dev/null +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -0,0 +1,68 @@ +// 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 numpy::PyArrayLike1; +use smallvec::smallvec; + +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::operations::{Param, StandardGate}; +use qiskit_circuit::Qubit; + +mod utils; + +/// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _validate_permutation(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + utils::validate_permutation(&view)?; + Ok(py.None()) +} + +/// Finds inverse of a permutation pattern. +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + let inverse_i64: Vec = utils::invert(&view).iter().map(|&x| x as i64).collect(); + Ok(inverse_i64.to_object(py)) +} + +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + let num_qubits = view.len(); + CircuitData::from_standard_gates( + py, + num_qubits as u32, + utils::get_ordered_swap(&view).iter().map(|(i, j)| { + ( + StandardGate::SwapGate, + smallvec![], + smallvec![Qubit(*i as u32), Qubit(*j as u32)], + ) + }), + Param::Float(0.0), + ) +} + +#[pymodule] +pub fn permutation(m: &Bound) -> PyResult<()> { + m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; + m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; + m.add_function(wrap_pyfunction!(_synth_permutation_basic, m)?)?; + Ok(()) +} diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/synthesis/permutation/utils.rs similarity index 66% rename from crates/accelerate/src/permutation.rs rename to crates/accelerate/src/synthesis/permutation/utils.rs index 31ba433ddd30..a78088bfbfa9 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/synthesis/permutation/utils.rs @@ -11,12 +11,11 @@ // that they have been altered from the originals. use ndarray::{Array1, ArrayView1}; -use numpy::PyArrayLike1; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use std::vec::Vec; -fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { +pub fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { let n = pattern.len(); let mut seen: Vec = vec![false; n]; @@ -47,7 +46,7 @@ fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { Ok(()) } -fn invert(pattern: &ArrayView1) -> Array1 { +pub fn invert(pattern: &ArrayView1) -> Array1 { let mut inverse: Array1 = Array1::zeros(pattern.len()); pattern.iter().enumerate().for_each(|(ii, &jj)| { inverse[jj as usize] = ii; @@ -55,7 +54,16 @@ fn invert(pattern: &ArrayView1) -> Array1 { inverse } -fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { +/// Sorts the input permutation by iterating through the permutation list +/// and putting each element to its correct position via a SWAP (if it's not +/// at the correct position already). If ``n`` is the length of the input +/// permutation, this requires at most ``n`` SWAPs. +/// +/// More precisely, if the input permutation is a cycle of length ``m``, +/// then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``); +/// if the input permutation consists of several disjoint cycles, then each cycle +/// is essentially treated independently. +pub fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { let mut permutation: Vec = pattern.iter().map(|&x| x as usize).collect(); let mut index_map = invert(pattern); @@ -76,45 +84,3 @@ fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { swaps[..].reverse(); swaps } - -/// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. -#[pyfunction] -#[pyo3(signature = (pattern))] -fn _validate_permutation(py: Python, pattern: PyArrayLike1) -> PyResult { - let view = pattern.as_array(); - validate_permutation(&view)?; - Ok(py.None()) -} - -/// Finds inverse of a permutation pattern. -#[pyfunction] -#[pyo3(signature = (pattern))] -fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { - let view = pattern.as_array(); - let inverse_i64: Vec = invert(&view).iter().map(|&x| x as i64).collect(); - Ok(inverse_i64.to_object(py)) -} - -/// Sorts the input permutation by iterating through the permutation list -/// and putting each element to its correct position via a SWAP (if it's not -/// at the correct position already). If ``n`` is the length of the input -/// permutation, this requires at most ``n`` SWAPs. -/// -/// More precisely, if the input permutation is a cycle of length ``m``, -/// then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``); -/// if the input permutation consists of several disjoint cycles, then each cycle -/// is essentially treated independently. -#[pyfunction] -#[pyo3(signature = (permutation_in))] -fn _get_ordered_swap(py: Python, permutation_in: PyArrayLike1) -> PyResult { - let view = permutation_in.as_array(); - Ok(get_ordered_swap(&view).to_object(py)) -} - -#[pymodule] -pub fn permutation(m: &Bound) -> PyResult<()> { - m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; - m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; - m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; - Ok(()) -} diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index b80aad1a7a45..72f0d759099a 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -17,10 +17,10 @@ use qiskit_accelerate::{ convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, - pauli_exp_val::pauli_expval, permutation::permutation, results::results, sabre::sabre, - sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, - stochastic_swap::stochastic_swap, two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, - utils::utils, vf2_layout::vf2_layout, + pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, + sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap, synthesis::synthesis, + two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils, + vf2_layout::vf2_layout, }; #[pymodule] @@ -36,7 +36,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(nlayout))?; m.add_wrapped(wrap_pymodule!(optimize_1q_gates))?; m.add_wrapped(wrap_pymodule!(pauli_expval))?; - m.add_wrapped(wrap_pymodule!(permutation))?; + m.add_wrapped(wrap_pymodule!(synthesis))?; m.add_wrapped(wrap_pymodule!(results))?; m.add_wrapped(wrap_pymodule!(sabre))?; m.add_wrapped(wrap_pymodule!(sampled_exp_val))?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index fce544333478..5b8505654428 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -80,7 +80,7 @@ sys.modules["qiskit._accelerate.stochastic_swap"] = _accelerate.stochastic_swap sys.modules["qiskit._accelerate.two_qubit_decompose"] = _accelerate.two_qubit_decompose sys.modules["qiskit._accelerate.vf2_layout"] = _accelerate.vf2_layout -sys.modules["qiskit._accelerate.permutation"] = _accelerate.permutation +sys.modules["qiskit._accelerate.synthesis.permutation"] = _accelerate.synthesis.permutation from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/circuit/library/generalized_gates/permutation.py b/qiskit/circuit/library/generalized_gates/permutation.py index 776c69d94f01..b2d17d2bed23 100644 --- a/qiskit/circuit/library/generalized_gates/permutation.py +++ b/qiskit/circuit/library/generalized_gates/permutation.py @@ -80,15 +80,13 @@ def __init__( name = "permutation_" + np.array_str(pattern).replace(" ", ",") - circuit = QuantumCircuit(num_qubits, name=name) - super().__init__(num_qubits, name=name) # pylint: disable=cyclic-import - from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap + from qiskit.synthesis.permutation import synth_permutation_basic - for i, j in _get_ordered_swap(pattern): - circuit.swap(i, j) + circuit = synth_permutation_basic(pattern) + circuit.name = name all_qubits = self.qubits self.append(circuit.to_gate(), all_qubits) @@ -184,10 +182,11 @@ def inverse(self, annotated: bool = False): def _qasm2_decomposition(self): # pylint: disable=cyclic-import - from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap + from qiskit.synthesis.permutation import synth_permutation_basic name = f"permutation__{'_'.join(str(n) for n in self.pattern)}_" - out = QuantumCircuit(self.num_qubits, name=name) - for i, j in _get_ordered_swap(self.pattern): - out.swap(i, j) + + out = synth_permutation_basic(self.pattern) + out.name = name + return out.to_gate() diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index ff014cb3a051..c280065c2a57 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -16,8 +16,8 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit._accelerate.synthesis.permutation import _synth_permutation_basic from .permutation_utils import ( - _get_ordered_swap, _inverse_pattern, _pattern_to_cycles, _decompose_cycles, @@ -44,17 +44,7 @@ def synth_permutation_basic(pattern: list[int] | np.ndarray[int]) -> QuantumCirc Returns: The synthesized quantum circuit. """ - # This is the very original Qiskit algorithm for synthesizing permutations. - - num_qubits = len(pattern) - qc = QuantumCircuit(num_qubits) - - swaps = _get_ordered_swap(pattern) - - for swap in swaps: - qc.swap(swap[0], swap[1]) - - return qc + return QuantumCircuit._from_circuit_data(_synth_permutation_basic(pattern)) def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircuit: diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index dbd73bfe8111..4520e18f4d06 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -13,9 +13,8 @@ """Utility functions for handling permutations.""" # pylint: disable=unused-import -from qiskit._accelerate.permutation import ( +from qiskit._accelerate.synthesis.permutation import ( _inverse_pattern, - _get_ordered_swap, _validate_permutation, ) diff --git a/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml b/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml new file mode 100644 index 000000000000..e770aa1ca31b --- /dev/null +++ b/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml @@ -0,0 +1,4 @@ +--- +upgrade_synthesis: + - | + Port :func:`.synth_permutation_basic`, used to synthesize qubit permutations, to Rust. diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py index 050df5a3fe1c..b6a1ca9e1857 100644 --- a/test/python/synthesis/test_permutation_synthesis.py +++ b/test/python/synthesis/test_permutation_synthesis.py @@ -27,7 +27,6 @@ ) from qiskit.synthesis.permutation.permutation_utils import ( _inverse_pattern, - _get_ordered_swap, _validate_permutation, ) from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -47,19 +46,6 @@ def test_inverse_pattern(self, width): for ii, jj in enumerate(pattern): self.assertTrue(inverse[jj] == ii) - @data(4, 5, 10, 15, 20) - def test_get_ordered_swap(self, width): - """Test _get_ordered_swap function produces correct swap list.""" - np.random.seed(1) - for _ in range(5): - pattern = np.random.permutation(width) - swap_list = _get_ordered_swap(pattern) - output = list(range(width)) - for i, j in swap_list: - output[i], output[j] = output[j], output[i] - self.assertTrue(np.array_equal(pattern, output)) - self.assertLess(len(swap_list), width) - @data(10, 20) def test_invalid_permutations(self, width): """Check that _validate_permutation raises exceptions when the From bf8f398fa4ddf287c6182b39bd27b324ab11dda0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 24 Jun 2024 10:05:06 -0400 Subject: [PATCH 14/89] Add rust representation for the u1, u2, and u3 gates (#12572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add rust representation for the u1, u2, and u3 gates This commit adds the rust representation of the U1Gate, U2Gate, and U3Gate to the `StandardGates` enum in rust. Part of #12566 * Update crates/circuit/src/imports.rs Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Fix test failures * Fix pylint pedantry --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/gate_matrix.rs | 32 +++++ crates/circuit/src/imports.rs | 6 + crates/circuit/src/operations.rs | 74 +++++++++++- qiskit/circuit/library/standard_gates/u1.py | 3 + qiskit/circuit/library/standard_gates/u2.py | 3 + qiskit/circuit/library/standard_gates/u3.py | 3 + test/python/circuit/test_rust_equivalence.py | 25 ++-- test/python/qasm3/test_export.py | 114 +++++++++--------- .../transpiler/test_optimize_1q_gates.py | 36 +++++- 9 files changed, 224 insertions(+), 72 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 23ce94869227..2e5f55d6ddcb 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -271,6 +271,38 @@ pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { ] } +#[inline] +pub fn u1_gate(lam: f64) -> [[Complex64; 2]; 2] { + [ + [c64(1., 0.), c64(0., 0.)], + [c64(0., 0.), c64(0., lam).exp()], + ] +} + +#[inline] +pub fn u2_gate(phi: f64, lam: f64) -> [[Complex64; 2]; 2] { + [ + [ + c64(FRAC_1_SQRT_2, 0.), + (-c64(0., lam).exp()) * FRAC_1_SQRT_2, + ], + [ + c64(0., phi).exp() * FRAC_1_SQRT_2, + c64(0., phi + lam).exp() * FRAC_1_SQRT_2, + ], + ] +} + +#[inline] +pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [c64(cos, 0.), -(c64(0., lam).exp()) * sin], + [c64(0., phi).exp() * sin, c64(0., phi + lam).exp() * cos], + ] +} + #[inline] pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { let cos = (theta / 2.).cos(); diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 3a9a942db8df..7160798f56bb 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -141,6 +141,12 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ "qiskit.circuit.library.standard_gates.xx_plus_yy", "XXPlusYYGate", ], + // U1Gate = 26 + ["qiskit.circuit.library.standard_gates.u1", "U1Gate"], + // U2Gate = 27 + ["qiskit.circuit.library.standard_gates.u2", "U2Gate"], + // U3Gate = 28 + ["qiskit.circuit.library.standard_gates.u3", "U3Gate"], ]; /// 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 6dedd3ac2061..451b04947388 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -205,14 +205,17 @@ pub enum StandardGate { ISwapGate = 23, XXMinusYYGate = 24, XXPlusYYGate = 25, + U1Gate = 26, + U2Gate = 27, + U3Gate = 28, } static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ - 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, ]; static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, 1, 2, 3, ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ @@ -242,6 +245,9 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "iswap", "xx_minus_yy", "xx_plus_yy", + "u1", + "u2", + "u3", ]; #[pymethods] @@ -290,8 +296,7 @@ impl StandardGate { // // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) - -pub const STANDARD_GATE_SIZE: usize = 26; +pub const STANDARD_GATE_SIZE: usize = 29; impl Operation for StandardGate { fn name(&self) -> &str { @@ -432,6 +437,22 @@ impl Operation for StandardGate { } _ => None, }, + Self::U1Gate => match params[0] { + Param::Float(val) => Some(aview2(&gate_matrix::u1_gate(val)).to_owned()), + _ => None, + }, + Self::U2Gate => match params { + [Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::u2_gate(*phi, *lam)).to_owned()) + } + _ => None, + }, + Self::U3Gate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::u3_gate(*theta, *phi, *lam)).to_owned()) + } + _ => None, + }, } } @@ -667,6 +688,21 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::U1Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::PhaseGate, + params.iter().cloned().collect(), + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::SdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -682,6 +718,21 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::U2Gate => 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()], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::TGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -697,6 +748,21 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::U3Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::UGate, + params.iter().cloned().collect(), + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::TdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index 1d59cabae1f6..f141146b72dc 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -19,6 +19,7 @@ from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import _ctrl_state_to_int +from qiskit._accelerate.circuit import StandardGate class U1Gate(Gate): @@ -92,6 +93,8 @@ class U1Gate(Gate): `1612.00858 `_ """ + _standard_gate = StandardGate.U1Gate + def __init__( self, theta: ParameterValueType, label: str | None = None, *, duration=None, unit="dt" ): diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py index c8e4de96efec..9e59cd4c5bbd 100644 --- a/qiskit/circuit/library/standard_gates/u2.py +++ b/qiskit/circuit/library/standard_gates/u2.py @@ -18,6 +18,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumregister import QuantumRegister +from qiskit._accelerate.circuit import StandardGate class U2Gate(Gate): @@ -86,6 +87,8 @@ class U2Gate(Gate): using two X90 pulses. """ + _standard_gate = StandardGate.U2Gate + def __init__( self, phi: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index 0eef2518a85a..f191609ea8f1 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -19,6 +19,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumregister import QuantumRegister +from qiskit._accelerate.circuit import StandardGate class U3Gate(Gate): @@ -80,6 +81,8 @@ class U3Gate(Gate): U3(\theta, 0, 0) = RY(\theta) """ + _standard_gate = StandardGate.U3Gate + def __init__( self, theta: ParameterValueType, diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index bb09ae4caf3f..8d6d159c0b64 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -79,12 +79,23 @@ def test_definitions(self): ) # Rust uses P but python still uses u1 elif rs_inst.operation.name == "p": - self.assertEqual(py_inst.operation.name, "u1") - 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], - ) + if py_inst.operation.name == "u1": + self.assertEqual(py_inst.operation.name, "u1") + 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, "u3") + self.assertEqual( + rs_inst.operation.params[0], py_inst.operation.params[2] + ) + 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) @@ -102,7 +113,7 @@ def test_matrix(self): continue with self.subTest(name=name): - params = [pi] * standard_gate._num_params() + params = [0.1] * standard_gate._num_params() py_def = gate_class.base_class(*params).to_matrix() rs_def = standard_gate._to_matrix(params) np.testing.assert_allclose(rs_def, py_def) diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py index 048c5d7852b4..6df041420882 100644 --- a/test/python/qasm3/test_export.py +++ b/test/python/qasm3/test_export.py @@ -2018,65 +2018,63 @@ def test_teleportation(self): qc.z(2).c_if(qc.clbits[0], 1) transpiled = transpile(qc, initial_layout=[0, 1, 2]) - first_h = transpiled.data[0].operation - u2 = first_h.definition.data[0].operation - u3_1 = u2.definition.data[0].operation - first_x = transpiled.data[-2].operation - u3_2 = first_x.definition.data[0].operation - first_z = transpiled.data[-1].operation - u1 = first_z.definition.data[0].operation - u3_3 = u1.definition.data[0].operation + id_len = len(str(id(transpiled.data[0].operation))) - expected_qasm = "\n".join( - [ - "OPENQASM 3.0;", - f"gate u3_{id(u3_1)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{", - " U(pi/2, 0, pi) _gate_q_0;", - "}", - f"gate u2_{id(u2)}(_gate_p_0, _gate_p_1) _gate_q_0 {{", - f" u3_{id(u3_1)}(pi/2, 0, pi) _gate_q_0;", - "}", - "gate h _gate_q_0 {", - f" u2_{id(u2)}(0, pi) _gate_q_0;", - "}", - "gate cx c, t {", - " ctrl @ U(pi, 0, pi) c, t;", - "}", - f"gate u3_{id(u3_2)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{", - " U(pi, 0, pi) _gate_q_0;", - "}", - "gate x _gate_q_0 {", - f" u3_{id(u3_2)}(pi, 0, pi) _gate_q_0;", - "}", - f"gate u3_{id(u3_3)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{", - " U(0, 0, pi) _gate_q_0;", - "}", - f"gate u1_{id(u1)}(_gate_p_0) _gate_q_0 {{", - f" u3_{id(u3_3)}(0, 0, pi) _gate_q_0;", - "}", - "gate z _gate_q_0 {", - f" u1_{id(u1)}(pi) _gate_q_0;", - "}", - "bit[2] c;", - "h $1;", - "cx $1, $2;", - "barrier $0, $1, $2;", - "cx $0, $1;", - "h $0;", - "barrier $0, $1, $2;", - "c[0] = measure $0;", - "c[1] = measure $1;", - "barrier $0, $1, $2;", - "if (c[1]) {", - " x $2;", - "}", - "if (c[0]) {", - " z $2;", - "}", - "", - ] - ) - self.assertEqual(Exporter(includes=[]).dumps(transpiled), expected_qasm) + expected_qasm = [ + "OPENQASM 3.0;", + re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len), + " U(pi/2, 0, pi) _gate_q_0;", + "}", + re.compile(r"gate u2_\d{%s}\(_gate_p_0, _gate_p_1\) _gate_q_0 \{" % id_len), + re.compile(r" u3_\d{%s}\(pi/2, 0, pi\) _gate_q_0;" % id_len), + "}", + "gate h _gate_q_0 {", + re.compile(r" u2_\d{%s}\(0, pi\) _gate_q_0;" % id_len), + "}", + "gate cx c, t {", + " ctrl @ U(pi, 0, pi) c, t;", + "}", + re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len), + " U(pi, 0, pi) _gate_q_0;", + "}", + "gate x _gate_q_0 {", + re.compile(r" u3_\d{%s}\(pi, 0, pi\) _gate_q_0;" % id_len), + "}", + re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len), + " U(0, 0, pi) _gate_q_0;", + "}", + re.compile(r"gate u1_\d{%s}\(_gate_p_0\) _gate_q_0 \{" % id_len), + re.compile(r" u3_\d{%s}\(0, 0, pi\) _gate_q_0;" % id_len), + "}", + "gate z _gate_q_0 {", + re.compile(r" u1_\d{%s}\(pi\) _gate_q_0;" % id_len), + "}", + "bit[2] c;", + "h $1;", + "cx $1, $2;", + "barrier $0, $1, $2;", + "cx $0, $1;", + "h $0;", + "barrier $0, $1, $2;", + "c[0] = measure $0;", + "c[1] = measure $1;", + "barrier $0, $1, $2;", + "if (c[1]) {", + " x $2;", + "}", + "if (c[0]) {", + " z $2;", + "}", + "", + ] + res = Exporter(includes=[]).dumps(transpiled).splitlines() + for result, expected in zip(res, expected_qasm): + if isinstance(expected, str): + self.assertEqual(result, expected) + else: + self.assertTrue( + expected.search(result), f"Line {result} doesn't match regex: {expected}" + ) def test_custom_gate_with_params_bound_main_call(self): """Custom gate with unbound parameters that are bound in the main circuit""" diff --git a/test/python/transpiler/test_optimize_1q_gates.py b/test/python/transpiler/test_optimize_1q_gates.py index 9253130bedbf..e5483dd47499 100644 --- a/test/python/transpiler/test_optimize_1q_gates.py +++ b/test/python/transpiler/test_optimize_1q_gates.py @@ -19,7 +19,7 @@ from qiskit.transpiler import PassManager from qiskit.transpiler.passes import Optimize1qGates, BasisTranslator from qiskit.converters import circuit_to_dag -from qiskit.circuit import Parameter +from qiskit.circuit import Parameter, Gate from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, UGate, PhaseGate from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.target import Target @@ -323,9 +323,24 @@ def test_parameterized_expressions_in_circuits(self): def test_global_phase_u3_on_left(self): """Check proper phase accumulation with instruction with no definition.""" + + class CustomGate(Gate): + """Custom u1 gate definition.""" + + def __init__(self, lam): + super().__init__("u1", 1, [lam]) + + def _define(self): + qc = QuantumCircuit(1) + qc.p(*self.params, 0) + self.definition = qc + + def _matrix(self): + return U1Gate(*self.params).to_matrix() + qr = QuantumRegister(1) qc = QuantumCircuit(qr) - u1 = U1Gate(0.1) + u1 = CustomGate(0.1) u1.definition.global_phase = np.pi / 2 qc.append(u1, [0]) qc.global_phase = np.pi / 3 @@ -337,9 +352,24 @@ def test_global_phase_u3_on_left(self): def test_global_phase_u_on_left(self): """Check proper phase accumulation with instruction with no definition.""" + + class CustomGate(Gate): + """Custom u1 gate.""" + + def __init__(self, lam): + super().__init__("u1", 1, [lam]) + + def _define(self): + qc = QuantumCircuit(1) + qc.p(*self.params, 0) + self.definition = qc + + def _matrix(self): + return U1Gate(*self.params).to_matrix() + qr = QuantumRegister(1) qc = QuantumCircuit(qr) - u1 = U1Gate(0.1) + u1 = CustomGate(0.1) u1.definition.global_phase = np.pi / 2 qc.append(u1, [0]) qc.global_phase = np.pi / 3 From 35f6297f20be6d9b58671948adc179dad77894af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:23:42 -0400 Subject: [PATCH 15/89] Bump faer from 0.19.0 to 0.19.1 (#12645) Bumps [faer](https://github.com/sarah-ek/faer-rs) from 0.19.0 to 0.19.1. - [Changelog](https://github.com/sarah-ek/faer-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/sarah-ek/faer-rs/commits) --- updated-dependencies: - dependency-name: faer 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 ++-- crates/accelerate/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aefa3c932a04..454823748e8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,9 +307,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "faer" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ef9e1a4098a9e3a03c47bc5061406c04820552d869fd0fcd92587d07b271f0" +checksum = "41543c4de4bfb32efdffdd75cbcca5ef41b800e8a811ea4a41fb9393c6ef3bc0" dependencies = [ "bytemuck", "coe-rs", diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index d9865d545437..b377a9b38a6d 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -20,7 +20,7 @@ num-traits = "0.2" num-complex.workspace = true num-bigint = "0.4" rustworkx-core = "0.14" -faer = "0.19.0" +faer = "0.19.1" itertools = "0.13.0" qiskit-circuit.workspace = true From b20a7ceb58b0ec7e49004d3ce81bd3f2144e6f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Mon, 24 Jun 2024 20:16:16 +0200 Subject: [PATCH 16/89] Add placeholders for all mising standard gates in Rust (#12646) * Add placeholders for all gates, mark TODOs * Update name for CPhase * Remove todo from Ux gates --- crates/circuit/src/imports.rs | 53 ++++++++++- crates/circuit/src/operations.rs | 156 ++++++++++++++++++++++++------- 2 files changed, 175 insertions(+), 34 deletions(-) diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 7160798f56bb..76e808d1b308 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -79,6 +79,7 @@ pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = /// /// NOTE: the order here is significant, the StandardGate variant's number must match /// index of it's entry in this table. This is all done statically for performance +// TODO: replace placeholders with actual implementation static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // ZGate = 0 ["qiskit.circuit.library.standard_gates.z", "ZGate"], @@ -131,12 +132,12 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"], // iSWAPGate = 23 ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"], - //XXMinusYYGate = 24 + // XXMinusYYGate = 24 [ "qiskit.circuit.library.standard_gates.xx_minus_yy", "XXMinusYYGate", ], - //XXPlusYYGate = 25 + // XXPlusYYGate = 25 [ "qiskit.circuit.library.standard_gates.xx_plus_yy", "XXPlusYYGate", @@ -147,6 +148,54 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ ["qiskit.circuit.library.standard_gates.u2", "U2Gate"], // U3Gate = 28 ["qiskit.circuit.library.standard_gates.u3", "U3Gate"], + // CRXGate = 29 + ["placeholder", "placeholder"], + // CRYGate = 30 + ["placeholder", "placeholder"], + // CRZGate = 31 + ["placeholder", "placeholder"], + // RGate 32 + ["placeholder", "placeholder"], + // CHGate = 33 + ["qiskit.circuit.library.standard_gates.h", "CHGate"], + // CPhaseGate = 34 + ["qiskit.circuit.library.standard_gates.p", "CPhaseGate"], + // CSGate = 35 + ["qiskit.circuit.library.standard_gates.s", "CSGate"], + // CSdgGate = 36 + ["qiskit.circuit.library.standard_gates.s", "CSdgGate"], + // CSXGate = 37 + ["qiskit.circuit.library.standard_gates.sx", "CSXGate"], + // CSwapGate = 38 + ["qiskit.circuit.library.standard_gates.swap", "CSwapGate"], + // CUGate = 39 + ["qiskit.circuit.library.standard_gates.u", "CUGate"], + // CU1Gate = 40 + ["qiskit.circuit.library.standard_gates.u1", "CU1Gate"], + // CU3Gate = 41 + ["qiskit.circuit.library.standard_gates.u3", "CU3Gate"], + // C3XGate = 42 + ["placeholder", "placeholder"], + // C3SXGate = 43 + ["placeholder", "placeholder"], + // C4XGate = 44 + ["placeholder", "placeholder"], + // DCXGate = 45 + ["placeholder", "placeholder"], + // CCZGate = 46 + ["placeholder", "placeholder"], + // RCCXGate = 47 + ["placeholder", "placeholder"], + // RC3XGate = 48 + ["placeholder", "placeholder"], + // RXXGate = 49 + ["placeholder", "placeholder"], + // RYYGate = 50 + ["placeholder", "placeholder"], + // RZZGate = 51 + ["placeholder", "placeholder"], + // RZXGate = 52 + ["placeholder", "placeholder"], ]; /// 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 451b04947388..af7dabc86216 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -208,46 +208,106 @@ pub enum StandardGate { U1Gate = 26, U2Gate = 27, U3Gate = 28, + CRXGate = 29, + CRYGate = 30, + CRZGate = 31, + RGate = 32, + CHGate = 33, + CPhaseGate = 34, + CSGate = 35, + CSdgGate = 36, + CSXGate = 37, + CSwapGate = 38, + CUGate = 39, + CU1Gate = 40, + CU3Gate = 41, + C3XGate = 42, + C3SXGate = 43, + C4XGate = 44, + DCXGate = 45, + CCZGate = 46, + RCCXGate = 47, + RC3XGate = 48, + RXXGate = 49, + RYYGate = 50, + RZZGate = 51, + RZXGate = 52, } +// TODO: replace all 34s (placeholders) with actual number static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ - 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, + 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 + 2, 2, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49 + 34, 34, 34, // 50-52 ]; +// TODO: replace all 34s (placeholders) with actual number static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, 1, 2, 3, + 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 + 1, 3, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49 + 34, 34, 34, // 50-52 ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ - "z", - "y", - "x", - "cz", - "cy", - "cx", - "ccx", - "rx", - "ry", - "rz", - "ecr", - "swap", - "sx", - "global_phase", - "id", - "h", - "p", - "u", - "s", - "sdg", - "t", - "tdg", - "sxdg", - "iswap", - "xx_minus_yy", - "xx_plus_yy", - "u1", - "u2", - "u3", + "z", // 0 + "y", // 1 + "x", // 2 + "cz", // 3 + "cy", // 4 + "cx", // 5 + "ccx", // 6 + "rx", // 7 + "ry", // 8 + "rz", // 9 + "ecr", // 10 + "swap", // 11 + "sx", // 12 + "global_phase", // 13 + "id", // 14 + "h", // 15 + "p", // 16 + "u", // 17 + "s", // 18 + "sdg", // 19 + "t", // 20 + "tdg", // 21 + "sxdg", // 22 + "iswap", // 23 + "xx_minus_yy", // 24 + "xx_plus_yy", // 25 + "u1", // 26 + "u2", // 27 + "u3", // 28 + "crx", // 29 + "cry", // 30 + "crz", // 31 + "r", // 32 + "ch", // 33 + "cp", // 34 + "cs", // 35 + "csdg", // 36 + "csx", // 37 + "cswap", // 38 + "cu", // 39 + "cu1", // 40 + "cu3", // 41 + "c3x", // 42 + "c3sx", // 43 + "c4x", // 44 + "dcx", // 45 + "ccz", // 46 + "rccx", // 47 + "rc3x", // 48 + "rxx", // 49 + "ryy", // 50 + "rzz", // 51 + "rzx", // 52 ]; #[pymethods] @@ -296,7 +356,7 @@ impl StandardGate { // // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) -pub const STANDARD_GATE_SIZE: usize = 29; +pub const STANDARD_GATE_SIZE: usize = 53; impl Operation for StandardGate { fn name(&self) -> &str { @@ -453,6 +513,21 @@ impl Operation for StandardGate { } _ => None, }, + Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), + Self::RGate => todo!(), + 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 => todo!(), + Self::CCZGate => todo!(), + Self::RCCXGate | Self::RC3XGate => todo!(), + Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), + Self::RZXGate => todo!(), } } @@ -878,6 +953,23 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), + Self::RGate => todo!(), + 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!(), + Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), + Self::DCXGate => todo!(), + Self::CCZGate => todo!(), + Self::RCCXGate | Self::RC3XGate => todo!(), + Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), + Self::RZXGate => todo!(), } } From 8b1f75ffafc70596bcf45480aa2f6d59d822e337 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 24 Jun 2024 19:21:20 +0100 Subject: [PATCH 17/89] Deprecate tuple-like access to `CircuitInstruction` (#12640) This has been the legacy path since `CircuitInstruction` was added in gh-8093. It's more performant to use the attribute-access patterns, and with more of the internals moving to Rust and potentially needing more use of additional class methods and attributes, we need to start shifting people away from the old form. --- crates/circuit/src/circuit_instruction.rs | 39 +++++++++++++++++-- crates/circuit/src/imports.rs | 2 + ...-circuit-instruction-8a332ab09de73766.yaml | 23 +++++++++++ test/python/circuit/test_circuit_data.py | 6 ++- test/utils/base.py | 9 +++++ 5 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/deprecate-legacy-circuit-instruction-8a332ab09de73766.yaml diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 93e73ccbc42f..781a776c1566 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -14,7 +14,7 @@ use std::cell::RefCell; use pyo3::basic::CompareOp; -use pyo3::exceptions::PyValueError; +use pyo3::exceptions::{PyDeprecationWarning, PyValueError}; use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyList, PyTuple, PyType}; use pyo3::{intern, IntoPy, PyObject, PyResult}; @@ -22,7 +22,7 @@ use smallvec::{smallvec, SmallVec}; use crate::imports::{ get_std_gate_class, populate_std_gate_map, GATE, INSTRUCTION, OPERATION, - SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, + SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, WARNINGS_WARN, }; use crate::interner::Index; use crate::operations::{OperationType, Param, PyGate, PyInstruction, PyOperation, StandardGate}; @@ -572,26 +572,31 @@ impl CircuitInstruction { #[cfg(not(feature = "cache_pygates"))] pub fn __getitem__(&self, py: Python<'_>, key: &Bound) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py)) } #[cfg(feature = "cache_pygates")] pub fn __getitem__(&mut self, py: Python<'_>, key: &Bound) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py)) } #[cfg(not(feature = "cache_pygates"))] pub fn __iter__(&self, py: Python<'_>) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py)) } #[cfg(feature = "cache_pygates")] pub fn __iter__(&mut self, py: Python<'_>) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py)) } - pub fn __len__(&self) -> usize { - 3 + pub fn __len__(&self, py: Python) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; + Ok(3) } pub fn __richcmp__( @@ -939,3 +944,29 @@ pub(crate) fn convert_py_to_operation_type( } Err(PyValueError::new_err(format!("Invalid input: {}", py_op))) } + +/// Issue a Python `DeprecationWarning` about using the legacy tuple-like interface to +/// `CircuitInstruction`. +/// +/// Beware the `stacklevel` here doesn't work quite the same way as it does in Python as Rust-space +/// calls are completely transparent to Python. +#[inline] +fn warn_on_legacy_circuit_instruction_iteration(py: Python) -> PyResult<()> { + WARNINGS_WARN + .get_bound(py) + .call1(( + intern!( + py, + concat!( + "Treating CircuitInstruction as an iterable is deprecated legacy behavior", + " since Qiskit 1.2, and will be removed in Qiskit 2.0.", + " Instead, use the `operation`, `qubits` and `clbits` named attributes." + ) + ), + py.get_type_bound::(), + // Stack level. Compared to Python-space calls to `warn`, this is unusually low + // beacuse all our internal call structure is now Rust-space and invisible to Python. + 1, + )) + .map(|_| ()) +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 76e808d1b308..92700f3274e7 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -72,6 +72,8 @@ pub static SINGLETON_GATE: ImportOnceCell = pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.singleton", "SingletonControlledGate"); +pub static WARNINGS_WARN: ImportOnceCell = ImportOnceCell::new("warnings", "warn"); + /// A mapping from the enum variant in crate::operations::StandardGate to the python /// module path and class name to import it. This is used to populate the conversion table /// when a gate is added directly via the StandardGate path and there isn't a Python object diff --git a/releasenotes/notes/deprecate-legacy-circuit-instruction-8a332ab09de73766.yaml b/releasenotes/notes/deprecate-legacy-circuit-instruction-8a332ab09de73766.yaml new file mode 100644 index 000000000000..d656ee5cb823 --- /dev/null +++ b/releasenotes/notes/deprecate-legacy-circuit-instruction-8a332ab09de73766.yaml @@ -0,0 +1,23 @@ +--- +deprecations_circuits: + - | + Treating :class:`.CircuitInstruction` as a tuple-like iterable is deprecated, and this legacy + path way will be removed in Qiskit 2.0. You should use the attribute-access fields + :attr:`~.CircuitInstruction.operation`, :attr:`~.CircuitInstruction.qubits`, and + :attr:`~.CircuitInstruction.clbits` instead. For example:: + + from qiskit.circuit import QuantumCircuit + + qc = QuantumCircuit(2, 2) + qc.h(0) + qc.cx(0, 1) + qc.measure([0, 1], [0, 1]) + + # Deprecated. + for op, qubits, clbits in qc.data: + pass + # New style. + for instruction in qc.data: + op = instruction.operation + qubits = instruction.qubits + clbits = instruction.clbits diff --git a/test/python/circuit/test_circuit_data.py b/test/python/circuit/test_circuit_data.py index 35ae27b2fcfb..55028c8e883e 100644 --- a/test/python/circuit/test_circuit_data.py +++ b/test/python/circuit/test_circuit_data.py @@ -416,7 +416,11 @@ def to_legacy(instruction): return (instruction.operation, list(instruction.qubits), list(instruction.clbits)) expected = [to_legacy(instruction) for instruction in qc.data] - actual = [tuple(instruction) for instruction in qc.data] + + with self.assertWarnsRegex( + DeprecationWarning, "Treating CircuitInstruction as an iterable is deprecated" + ): + actual = [tuple(instruction) for instruction in qc.data] self.assertEqual(actual, expected) def test_getitem_by_insertion_order(self): diff --git a/test/utils/base.py b/test/utils/base.py index 63a8bf4384f0..bebf03008858 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -215,6 +215,15 @@ def setUpClass(cls): module=r"seaborn(\..*)?", ) + # Safe to remove once https://github.com/Qiskit/qiskit-aer/pull/2179 is in a release version + # of Aer. + warnings.filterwarnings( + "default", + category=DeprecationWarning, + message="Treating CircuitInstruction as an iterable is deprecated", + module=r"qiskit_aer(\.[a-zA-Z0-9_]+)*", + ) + allow_DeprecationWarning_modules = [ "test.python.pulse.test_builder", "test.python.pulse.test_block", From 1ed5951a98b594808525c8428e06178c160cfcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Tue, 25 Jun 2024 12:53:54 +0200 Subject: [PATCH 18/89] Pin scipy to 1.13.1 to bypass CI failures (#12654) * Pin scipy to 1.13.1 to bypass CI failures * whoops * double whoops --- constraints.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/constraints.txt b/constraints.txt index d3985581d362..6681de226d93 100644 --- a/constraints.txt +++ b/constraints.txt @@ -3,6 +3,10 @@ # https://github.com/Qiskit/qiskit-terra/issues/10345 for current details. scipy<1.11; python_version<'3.12' +# Temporary pin to avoid CI issues caused by scipy 1.14.0 +# See https://github.com/Qiskit/qiskit/issues/12655 for current details. +scipy==1.13.1; python_version=='3.12' + # 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 6974b4500f6c716b407b599e5cec80afbb757516 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Wed, 26 Jun 2024 08:29:34 +0200 Subject: [PATCH 19/89] Fix some bugs in loading Solovay Kitaev decompositions (#12579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix store & load - fix access via .item() - fix storing of global phase - fix storing ofgate sequence labels * undangle a dangling print * fix import order * Update releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml 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> --- .../generate_basis_approximations.py | 2 +- .../discrete_basis/solovay_kitaev.py | 24 ++++++++++---- ...ix-sk-load-from-file-02c6eabbbd7fcda3.yaml | 10 ++++++ test/python/transpiler/test_solovay_kitaev.py | 31 +++++++++++++++++++ 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml diff --git a/qiskit/synthesis/discrete_basis/generate_basis_approximations.py b/qiskit/synthesis/discrete_basis/generate_basis_approximations.py index 672d0eb9e8ef..da9708c24559 100644 --- a/qiskit/synthesis/discrete_basis/generate_basis_approximations.py +++ b/qiskit/synthesis/discrete_basis/generate_basis_approximations.py @@ -156,7 +156,7 @@ def generate_basic_approximations( data = {} for sequence in sequences: gatestring = sequence.name - data[gatestring] = sequence.product + data[gatestring] = (sequence.product, sequence.global_phase) np.save(filename, data) diff --git a/qiskit/synthesis/discrete_basis/solovay_kitaev.py b/qiskit/synthesis/discrete_basis/solovay_kitaev.py index 2c8df5bd1b6d..f367f6c0f0b5 100644 --- a/qiskit/synthesis/discrete_basis/solovay_kitaev.py +++ b/qiskit/synthesis/discrete_basis/solovay_kitaev.py @@ -51,14 +51,19 @@ def __init__( self.basic_approximations = self.load_basic_approximations(basic_approximations) - def load_basic_approximations(self, data: list | str | dict) -> list[GateSequence]: + @staticmethod + def load_basic_approximations(data: list | str | dict) -> list[GateSequence]: """Load basic approximations. Args: data: If a string, specifies the path to the file from where to load the data. - If a dictionary, directly specifies the decompositions as ``{gates: matrix}``. - There ``gates`` are the names of the gates producing the SO(3) matrix ``matrix``, - e.g. ``{"h t": np.array([[0, 0.7071, -0.7071], [0, -0.7071, -0.7071], [-1, 0, 0]]}``. + If a dictionary, directly specifies the decompositions as ``{gates: matrix}`` + or ``{gates: (matrix, global_phase)}``. There, ``gates`` are the names of the gates + producing the SO(3) matrix ``matrix``, e.g. + ``{"h t": np.array([[0, 0.7071, -0.7071], [0, -0.7071, -0.7071], [-1, 0, 0]]}`` + and the ``global_phase`` can be given to account for a global phase difference + between the U(2) matrix of the quantum gates and the stored SO(3) matrix. + If not given, the ``global_phase`` will be assumed to be 0. Returns: A list of basic approximations as type ``GateSequence``. @@ -72,13 +77,20 @@ def load_basic_approximations(self, data: list | str | dict) -> list[GateSequenc # if a file, load the dictionary if isinstance(data, str): - data = np.load(data, allow_pickle=True) + data = np.load(data, allow_pickle=True).item() sequences = [] - for gatestring, matrix in data.items(): + for gatestring, matrix_and_phase in data.items(): + if isinstance(matrix_and_phase, tuple): + matrix, global_phase = matrix_and_phase + else: + matrix, global_phase = matrix_and_phase, 0 + sequence = GateSequence() sequence.gates = [_1q_gates[element] for element in gatestring.split()] + sequence.labels = [gate.name for gate in sequence.gates] sequence.product = np.asarray(matrix) + sequence.global_phase = global_phase sequences.append(sequence) return sequences diff --git a/releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml b/releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml new file mode 100644 index 000000000000..d995af06bccb --- /dev/null +++ b/releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fix the :class:`.SolovayKitaev` transpiler pass when loading basic + approximations from an exising ``.npy`` file. Previously, loading + a stored approximation which allowed for further reductions (e.g. due + to gate cancellations) could cause a runtime failure. + Additionally, the global phase difference of the U(2) gate product + and SO(3) representation was lost during a save-reload procedure. + Fixes `Qiskit/qiskit#12576 `_. diff --git a/test/python/transpiler/test_solovay_kitaev.py b/test/python/transpiler/test_solovay_kitaev.py index e15a080f6f03..62b811c8e3bd 100644 --- a/test/python/transpiler/test_solovay_kitaev.py +++ b/test/python/transpiler/test_solovay_kitaev.py @@ -12,8 +12,10 @@ """Test the Solovay Kitaev transpilation pass.""" +import os import unittest import math +import tempfile import numpy as np import scipy @@ -230,6 +232,35 @@ def test_u_gates_work(self): included_gates = set(discretized.count_ops().keys()) self.assertEqual(set(basis_gates), included_gates) + def test_load_from_file(self): + """Test loading basic approximations from a file works. + + Regression test of Qiskit/qiskit#12576. + """ + filename = "approximations.npy" + + with tempfile.TemporaryDirectory() as tmp_dir: + fullpath = os.path.join(tmp_dir, filename) + + # dump approximations to file + generate_basic_approximations(basis_gates=["h", "s", "sdg"], depth=3, filename=fullpath) + + # circuit to decompose and reference decomp + circuit = QuantumCircuit(1) + circuit.rx(0.8, 0) + + reference = QuantumCircuit(1, global_phase=3 * np.pi / 4) + reference.h(0) + reference.s(0) + reference.h(0) + + # load the decomp and compare to reference + skd = SolovayKitaev(basic_approximations=fullpath) + # skd = SolovayKitaev(basic_approximations=filename) + discretized = skd(circuit) + + self.assertEqual(discretized, reference) + @ddt class TestGateSequence(QiskitTestCase): From e36027c01a5d18b72225502c0fd5021613893623 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 26 Jun 2024 09:41:11 +0200 Subject: [PATCH 20/89] GenericBackendV2 should fail when the backend cannot allocate the basis gate because its size (#12653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GenericBackendV2 should fail when the backend cannot allocate the basis gate because its size Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * reno * Update releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * another single qubit backend --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- qiskit/providers/fake_provider/generic_backend_v2.py | 5 +++++ .../notes/fixes_GenericBackendV2-668e40596e1f070d.yaml | 4 ++++ .../providers/fake_provider/test_generic_backend_v2.py | 10 ++++++++++ test/visual/mpl/graph/test_graph_matplotlib_drawer.py | 2 +- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index 1ac0484d775d..214754080e57 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -375,6 +375,11 @@ def _build_generic_target(self): f"in the standard qiskit circuit library." ) gate = self._supported_gates[name] + if self.num_qubits < gate.num_qubits: + raise QiskitError( + f"Provided basis gate {name} needs more qubits than {self.num_qubits}, " + f"which is the size of the backend." + ) noise_params = self._get_noise_defaults(name, gate.num_qubits) self._add_noisy_instruction_to_target(gate, noise_params, calibration_inst_map) diff --git a/releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml b/releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml new file mode 100644 index 000000000000..9d297125e3c2 --- /dev/null +++ b/releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + The constructor :class:`.GenericBackendV2` was allowing to create malformed backends because it accepted basis gates that couldn't be allocated in the backend size . That is, a backend with a single qubit should not accept a basis with two-qubit gates. diff --git a/test/python/providers/fake_provider/test_generic_backend_v2.py b/test/python/providers/fake_provider/test_generic_backend_v2.py index b4fbe944c332..cd7c611b2212 100644 --- a/test/python/providers/fake_provider/test_generic_backend_v2.py +++ b/test/python/providers/fake_provider/test_generic_backend_v2.py @@ -35,6 +35,16 @@ def test_supported_basis_gates(self): with self.assertRaises(QiskitError): GenericBackendV2(num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"]) + def test_cx_1Q(self): + """Test failing with a backend with single qubit but with a two-qubit basis gate""" + with self.assertRaises(QiskitError): + GenericBackendV2(num_qubits=1, basis_gates=["cx", "id"]) + + def test_ccx_2Q(self): + """Test failing with a backend with two qubits but with a three-qubit basis gate""" + with self.assertRaises(QiskitError): + GenericBackendV2(num_qubits=2, basis_gates=["ccx", "id"]) + def test_operation_names(self): """Test that target basis gates include "delay", "measure" and "reset" even if not provided by user.""" diff --git a/test/visual/mpl/graph/test_graph_matplotlib_drawer.py b/test/visual/mpl/graph/test_graph_matplotlib_drawer.py index ae69f212f89c..20fae107d30d 100644 --- a/test/visual/mpl/graph/test_graph_matplotlib_drawer.py +++ b/test/visual/mpl/graph/test_graph_matplotlib_drawer.py @@ -389,7 +389,7 @@ def test_plot_1_qubit_gate_map(self): """Test plot_gate_map using 1 qubit backend""" # getting the mock backend from FakeProvider - backend = GenericBackendV2(num_qubits=1) + backend = GenericBackendV2(num_qubits=1, basis_gates=["id", "rz", "sx", "x"]) fname = "1_qubit_gate_map.png" self.graph_plot_gate_map(backend=backend, filename=fname) From 4d3821b01a70c40e8542646ad92759aae877a096 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 26 Jun 2024 08:06:23 -0400 Subject: [PATCH 21/89] Simplify QuantumCircuit._from_circuit_data bit handling (#12661) * Simplify QuantumCircuit._from_circuit_data bit handling This commit simplifies the logic around bit handling in the `QuantumCircuit._from_circuit_data()` constructor. Previously it was calling `add_bits()` for each bit in the `CircuitData` object to update the output circuit's accounting for each qubit. But this was needlessly heavy as the `CircuitData` is already the source of truth for the bits in a circuit and we just need to update the indices dictionary. The `add_bits()` method attempts to add the bits to the `CircuitData` too but this is wasted overhead because the `CircuitData` already has the bits as that's where the came from. This changes the constructor to just directly set the bit indices as needed and return the circuit. * Use a dict comprehension instead of a for loop --- qiskit/circuit/quantumcircuit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index ee52e3308a94..8b3ff7bf1979 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1161,9 +1161,9 @@ def __init__( def _from_circuit_data(cls, data: CircuitData) -> typing.Self: """A private constructor from rust space circuit data.""" out = QuantumCircuit() - out.add_bits(data.qubits) - out.add_bits(data.clbits) out._data = data + out._qubit_indices = {bit: BitLocations(index, []) for index, bit in enumerate(data.qubits)} + out._clbit_indices = {bit: BitLocations(index, []) for index, bit in enumerate(data.clbits)} return out @staticmethod From 26680dcecf9457210f46aa6b2f62361a5ccd8d84 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 26 Jun 2024 14:45:24 +0200 Subject: [PATCH 22/89] adapting test/randomized/test_transpiler_equivalence.py to #12640 (#12663) * addapting to #12640 * more instances --- test/randomized/test_transpiler_equivalence.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/randomized/test_transpiler_equivalence.py b/test/randomized/test_transpiler_equivalence.py index 04dced90dfaa..3bd09d89348b 100644 --- a/test/randomized/test_transpiler_equivalence.py +++ b/test/randomized/test_transpiler_equivalence.py @@ -258,9 +258,9 @@ def add_c_if_last_gate(self, carg, data): last_gate = self.qc.data[-1] # Conditional instructions are not supported - assume(isinstance(last_gate[0], Gate)) + assume(isinstance(last_gate.operation, Gate)) - last_gate[0].c_if(creg, val) + last_gate.operation.c_if(creg, val) # Properties to check @@ -269,7 +269,7 @@ def qasm(self): """After each circuit operation, it should be possible to build QASM.""" qasm2.dumps(self.qc) - @precondition(lambda self: any(isinstance(d[0], Measure) for d in self.qc.data)) + @precondition(lambda self: any(isinstance(d.operation, Measure) for d in self.qc.data)) @rule(kwargs=transpiler_conf()) def equivalent_transpile(self, kwargs): """Simulate, transpile and simulate the present circuit. Verify that the From 39b2c90b813da63d006755580cabd80b718b74bb Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 26 Jun 2024 09:48:25 -0400 Subject: [PATCH 23/89] Add test case to validate the rust->Python gate conversion (#12623) This commit adds a test to the test_rust_equivalence module to assert that the Python gate objects returned from the Rust CircuitData is the correct type. --- test/python/circuit/test_rust_equivalence.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index 8d6d159c0b64..b20db4c79f94 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -18,7 +18,7 @@ import numpy as np -from qiskit.circuit import QuantumCircuit +from qiskit.circuit import QuantumCircuit, CircuitInstruction from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping SKIP_LIST = {"rx", "ry", "ecr"} @@ -39,6 +39,21 @@ def setUp(self): gate = gate.base_class(*[pi] * len(gate.params)) qc.append(gate, list(range(gate.num_qubits))) + def test_gate_cross_domain_conversion(self): + """Test the rust -> python conversion returns the right class.""" + for name, gate_class in self.standard_gates.items(): + standard_gate = getattr(gate_class, "_standard_gate", None) + if standard_gate is None: + # Gate not in rust yet or no constructor method + continue + with self.subTest(name=name): + qc = QuantumCircuit(standard_gate.num_qubits) + qc._append( + CircuitInstruction(standard_gate, qubits=qc.qubits, params=gate_class.params) + ) + self.assertEqual(qc.data[0].operation.base_class, gate_class.base_class) + self.assertEqual(qc.data[0].operation, gate_class) + def test_definitions(self): """Test definitions are the same in rust space.""" for name, gate_class in self.standard_gates.items(): 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 24/89] 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 25/89] 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 26/89] 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 27/89] 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 28/89] 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 29/89] 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 30/89] 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 31/89] 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 32/89] 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 33/89] 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 34/89] 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 35/89] 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 36/89] 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 37/89] 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 38/89] 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 39/89] 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 40/89] 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 From 5db984ae0c33a4a8558f6a0d5394854fe1a7a2c5 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:01:12 -0400 Subject: [PATCH 41/89] Initial: Expose operation conversion methods to other crates. (#12698) - Expose `operation_type_to_py`, `operation_type_and_data_to_py`, `convert_py_to_operation_type`, methods to other rust crates. - Expose `OperationTypeConstruct` struct to other crates. --- crates/circuit/src/circuit_instruction.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 74302b526d51..ffa6bb0c652c 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -728,10 +728,7 @@ impl CircuitInstruction { /// Take a reference to a `CircuitInstruction` and convert the operation /// inside that to a python side object. -pub(crate) fn operation_type_to_py( - py: Python, - circuit_inst: &CircuitInstruction, -) -> PyResult { +pub fn operation_type_to_py(py: Python, circuit_inst: &CircuitInstruction) -> PyResult { let (label, duration, unit, condition) = match &circuit_inst.extra_attrs { None => (None, None, None, None), Some(extra_attrs) => ( @@ -757,7 +754,7 @@ pub(crate) fn operation_type_to_py( /// a Python side full-fat Qiskit operation as a PyObject. This is typically /// used by accessor functions that need to return an operation to Qiskit, such /// as accesing `CircuitInstruction.operation`. -pub(crate) fn operation_type_and_data_to_py( +pub fn operation_type_and_data_to_py( py: Python, operation: &OperationType, params: &[Param], @@ -796,8 +793,8 @@ pub(crate) fn operation_type_and_data_to_py( /// A container struct that contains the output from the Python object to /// conversion to construct a CircuitInstruction object -#[derive(Debug)] -pub(crate) struct OperationTypeConstruct { +#[derive(Debug, Clone)] +pub struct OperationTypeConstruct { pub operation: OperationType, pub params: SmallVec<[Param; 3]>, pub label: Option, @@ -809,7 +806,7 @@ pub(crate) struct OperationTypeConstruct { /// Convert an inbound Python object for a Qiskit operation and build a rust /// representation of that operation. This will map it to appropriate variant /// of operation type based on class -pub(crate) fn convert_py_to_operation_type( +pub fn convert_py_to_operation_type( py: Python, py_op: PyObject, ) -> PyResult { From 05d5b98304eecca05feb6a2aeb2411911455d42d 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 22:50:06 +0200 Subject: [PATCH 42/89] Fix bad Sphinx syntax in several release notes and in API documentaiton (#12604) * Fix ' into ` * More typos on release notes and on API documentation * change `self.filter` for `Schedule.filter` * change `Schedule` -> `ScheduleBlock` in its `filter()` reference --- qiskit/pulse/schedule.py | 6 +-- ..._for_pulse_schedules-3a27bbbbf235fb9e.yaml | 2 +- .../pauli-apply-layout-cdcbc1bce724a150.yaml | 42 +++++++++---------- ...r_probabilities_dict-e53f524d115bbcfc.yaml | 6 +-- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py index 7ccd5053e6e0..c89fe3b4e306 100644 --- a/qiskit/pulse/schedule.py +++ b/qiskit/pulse/schedule.py @@ -252,7 +252,7 @@ def children(self) -> tuple[tuple[int, "ScheduleComponent"], ...]: Notes: Nested schedules are returned as-is. If you want to collect only instructions, - use py:meth:`~Schedule.instructions` instead. + use :py:meth:`~Schedule.instructions` instead. Returns: A tuple, where each element is a two-tuple containing the initial @@ -490,7 +490,7 @@ def exclude( ) -> "Schedule": """Return a ``Schedule`` with only the instructions from this Schedule *failing* at least one of the provided filters. - This method is the complement of py:meth:`~self.filter`, so that:: + This method is the complement of :py:meth:`~Schedule.filter`, so that:: self.filter(args) | self.exclude(args) == self @@ -1300,7 +1300,7 @@ def exclude( ): """Return a new ``ScheduleBlock`` with only the instructions from this ``ScheduleBlock`` *failing* at least one of the provided filters. - This method is the complement of py:meth:`~self.filter`, so that:: + This method is the complement of :py:meth:`~ScheduleBlock.filter`, so that:: self.filter(args) + self.exclude(args) == self in terms of instructions included. diff --git a/releasenotes/notes/1.1/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml b/releasenotes/notes/1.1/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml index 551ea9e918c6..d29089ef9491 100644 --- a/releasenotes/notes/1.1/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml +++ b/releasenotes/notes/1.1/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml @@ -1,7 +1,7 @@ --- features_pulse: - | - It is now possible to assign parameters to pulse :class:`.Schedule`and :class:`.ScheduleBlock` objects by specifying + It is now possible to assign parameters to pulse :class:`.Schedule` and :class:`.ScheduleBlock` objects by specifying the parameter name as a string. The parameter name can be used to assign values to all parameters within the `Schedule` or `ScheduleBlock` that have the same name. Moreover, the parameter name of a `ParameterVector` can be used to assign all values of the vector simultaneously (the list of values should therefore match the diff --git a/releasenotes/notes/1.1/pauli-apply-layout-cdcbc1bce724a150.yaml b/releasenotes/notes/1.1/pauli-apply-layout-cdcbc1bce724a150.yaml index f3f69ce5cb19..d1be6c450ee3 100644 --- a/releasenotes/notes/1.1/pauli-apply-layout-cdcbc1bce724a150.yaml +++ b/releasenotes/notes/1.1/pauli-apply-layout-cdcbc1bce724a150.yaml @@ -3,29 +3,29 @@ features_quantum_info: - | Added a new :meth:`~.Pauli.apply_layout` method that is equivalent to :meth:`~.SparsePauliOp.apply_layout`. This method is used to apply - a :class:`~.TranspileLayout` layout from the transpiler to a :class:~.Pauli` + a :class:`~.TranspileLayout` layout from the transpiler to a :class:`~.Pauli` observable that was built for an input circuit. This enables working with :class:`~.BaseEstimator` / :class:`~.BaseEstimatorV2` implementations and local transpilation when the input is of type :class:`~.Pauli`. For example:: - from qiskit.circuit.library import RealAmplitudes - from qiskit.primitives import BackendEstimatorV2 - from qiskit.providers.fake_provider import GenericBackendV2 - from qiskit.quantum_info import Pauli - from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager + from qiskit.circuit.library import RealAmplitudes + from qiskit.primitives import BackendEstimatorV2 + from qiskit.providers.fake_provider import GenericBackendV2 + from qiskit.quantum_info import Pauli + from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager + + psi = RealAmplitudes(num_qubits=2, reps=2) + H1 = Pauli("XI") + backend = GenericBackendV2(num_qubits=7) + estimator = BackendEstimatorV2(backend=backend) + thetas = [0, 1, 1, 2, 3, 5] + pm = generate_preset_pass_manager(optimization_level=3, backend=backend) + transpiled_psi = pm.run(psi) + permuted_op = H1.apply_layout(transpiled_psi.layout) + res = estimator.run([(transpiled_psi, permuted_op, thetas)]).result() - psi = RealAmplitudes(num_qubits=2, reps=2) - H1 = Pauli("XI") - backend = GenericBackendV2(num_qubits=7) - estimator = BackendEstimatorV2(backend=backend) - thetas = [0, 1, 1, 2, 3, 5] - pm = generate_preset_pass_manager(optimization_level=3, backend=backend) - transpiled_psi = pm.run(psi) - permuted_op = H1.apply_layout(transpiled_psi.layout) - res = estimator.run([(transpiled_psi, permuted_op, thetas)]).result() - - where an input circuit is transpiled locally before it's passed to - :class:`~.BaseEstimator.run`. Transpilation expands the original - circuit from 2 to 7 qubits (the size of ``backend``) and permutes its layout, - which is then applied to ``H1`` using :meth:`~.Pauli.apply_layout` - to reflect the transformations performed by ``pm.run()``. \ No newline at end of file + where an input circuit is transpiled locally before it's passed to + :class:`~.BaseEstimator.run`. Transpilation expands the original + circuit from 2 to 7 qubits (the size of ``backend``) and permutes its layout, + which is then applied to ``H1`` using :meth:`~.Pauli.apply_layout` + to reflect the transformations performed by ``pm.run()``. \ No newline at end of file diff --git a/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml b/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml index 6fa548d9245c..d0466b8f75d8 100644 --- a/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml +++ b/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml @@ -1,10 +1,10 @@ --- features: - | - The :class:'.StabilizerState' class now has a new method - :meth:'~.StabilizerState.probabilities_dict_from_bitstring' allowing the + The :class:`.StabilizerState` class now has a new method + :meth:`~.StabilizerState.probabilities_dict_from_bitstring` allowing the user to pass single bitstring to measure an outcome for. Previouslly the - :meth:'~.StabilizerState.probabilities_dict' would be utilized and would + :meth:`~.StabilizerState.probabilities_dict` would be utilized and would at worst case calculate (2^n) number of probability calculations (depending on the state), even if a user wanted a single result. With this new method the user can calculate just the single outcome bitstring value a user passes From d2ab4dfb480dbe77c42d01dc9a9c6d11cb9aa12c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 2 Jul 2024 04:13:26 -0400 Subject: [PATCH 43/89] Enable avoiding Python operation creation in transpiler (#12692) * Avoid Python operation creation in transpiler Since #12459 accessing `node.op` in the transpiler eagerly creates a Python object on access. This is because we now are no longer storing a Python object internally and we need to rebuild the object to return the python object as expected by the api. This is causing a significant performance regression because of the extra overhead. The longer term goal is to move as much of the performance critical passes to operate in rust which will eliminate this overhead. But in the meantime we can mitigate the performance overhead by changing the Python access patterns to avoid the operation object creation. This commit adds some new getter methods to DAGOpNode to give access to the inner rust data so that we can avoid the extra overhead. As a proof of concept this updates the unitary synthesis pass in isolation. Doing this fixes the regression caused by #12459 for that pass. We can continue this migration for everything else in follow up PRs. This commit is mostly to establish the pattern and add the python space access methods. * Remove unused import * Add path to avoid StandardGate conversion in circuit_to_dag * Add fast path through dag_to_circuit --- crates/circuit/src/circuit_data.rs | 11 +- crates/circuit/src/circuit_instruction.rs | 55 +++++++- crates/circuit/src/dag_node.rs | 119 ++++++++++++++---- crates/circuit/src/imports.rs | 1 + crates/circuit/src/operations.rs | 43 ++++++- qiskit/converters/circuit_to_dag.py | 25 +++- qiskit/converters/dag_to_circuit.py | 23 +++- qiskit/dagcircuit/dagcircuit.py | 21 ++++ .../passes/synthesis/unitary_synthesis.py | 74 ++++++++--- .../passes/utils/check_gate_direction.py | 8 +- 10 files changed, 313 insertions(+), 67 deletions(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 10e0691021a1..f10911cc440f 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -18,7 +18,7 @@ use crate::circuit_instruction::{ convert_py_to_operation_type, CircuitInstruction, ExtraInstructionAttributes, OperationInput, PackedInstruction, }; -use crate::imports::{BUILTIN_LIST, QUBIT}; +use crate::imports::{BUILTIN_LIST, DEEPCOPY, QUBIT}; use crate::interner::{IndexedInterner, Interner, InternerKey}; use crate::operations::{Operation, OperationType, Param, StandardGate}; use crate::parameter_table::{ParamEntry, ParamTable, GLOBAL_PHASE_INDEX}; @@ -488,20 +488,17 @@ impl CircuitData { res.param_table.clone_from(&self.param_table); if deepcopy { - let deepcopy = py - .import_bound(intern!(py, "copy"))? - .getattr(intern!(py, "deepcopy"))?; for inst in &mut res.data { match &mut inst.op { OperationType::Standard(_) => {} OperationType::Gate(ref mut op) => { - op.gate = deepcopy.call1((&op.gate,))?.unbind(); + op.gate = DEEPCOPY.get_bound(py).call1((&op.gate,))?.unbind(); } OperationType::Instruction(ref mut op) => { - op.instruction = deepcopy.call1((&op.instruction,))?.unbind(); + op.instruction = DEEPCOPY.get_bound(py).call1((&op.instruction,))?.unbind(); } OperationType::Operation(ref mut op) => { - op.operation = deepcopy.call1((&op.operation,))?.unbind(); + op.operation = DEEPCOPY.get_bound(py).call1((&op.operation,))?.unbind(); } }; #[cfg(feature = "cache_pygates")] diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index ffa6bb0c652c..d6516722fbac 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -13,6 +13,7 @@ #[cfg(feature = "cache_pygates")] use std::cell::RefCell; +use numpy::IntoPyArray; use pyo3::basic::CompareOp; use pyo3::exceptions::{PyDeprecationWarning, PyValueError}; use pyo3::prelude::*; @@ -25,7 +26,9 @@ use crate::imports::{ SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, WARNINGS_WARN, }; use crate::interner::Index; -use crate::operations::{OperationType, Param, PyGate, PyInstruction, PyOperation, StandardGate}; +use crate::operations::{ + Operation, OperationType, Param, PyGate, PyInstruction, PyOperation, StandardGate, +}; /// These are extra mutable attributes for a circuit instruction's state. In general we don't /// typically deal with this in rust space and the majority of the time they're not used in Python @@ -407,6 +410,56 @@ impl CircuitInstruction { }) } + #[getter] + fn _raw_op(&self, py: Python) -> PyObject { + self.operation.clone().into_py(py) + } + + /// Returns the Instruction name corresponding to the op for this node + #[getter] + fn get_name(&self, py: Python) -> PyObject { + self.operation.name().to_object(py) + } + + #[getter] + fn get_params(&self, py: Python) -> PyObject { + self.params.to_object(py) + } + + #[getter] + fn matrix(&self, py: Python) -> Option { + let matrix = self.operation.matrix(&self.params); + matrix.map(|mat| mat.into_pyarray_bound(py).into()) + } + + #[getter] + fn label(&self) -> Option<&str> { + self.extra_attrs + .as_ref() + .and_then(|attrs| attrs.label.as_deref()) + } + + #[getter] + fn condition(&self, py: Python) -> Option { + self.extra_attrs + .as_ref() + .and_then(|attrs| attrs.condition.as_ref().map(|x| x.clone_ref(py))) + } + + #[getter] + fn duration(&self, py: Python) -> Option { + self.extra_attrs + .as_ref() + .and_then(|attrs| attrs.duration.as_ref().map(|x| x.clone_ref(py))) + } + + #[getter] + fn unit(&self) -> Option<&str> { + self.extra_attrs + .as_ref() + .and_then(|attrs| attrs.unit.as_deref()) + } + /// Creates a shallow copy with the given fields replaced. /// /// Returns: diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index c8b6a4c8b082..ffd7920a36fd 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -15,9 +15,11 @@ use crate::circuit_instruction::{ ExtraInstructionAttributes, }; use crate::operations::Operation; +use numpy::IntoPyArray; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PySequence, PyString, PyTuple}; -use pyo3::{intern, PyObject, PyResult}; +use pyo3::{intern, IntoPy, PyObject, PyResult}; +use smallvec::smallvec; /// Parent class for DAGOpNode, DAGInNode, and DAGOutNode. #[pyclass(module = "qiskit._accelerate.circuit", subclass)] @@ -70,12 +72,19 @@ pub struct DAGOpNode { #[pymethods] impl DAGOpNode { + #[allow(clippy::too_many_arguments)] #[new] + #[pyo3(signature = (op, qargs=None, cargs=None, params=smallvec![], label=None, duration=None, unit=None, condition=None, dag=None))] fn new( py: Python, - op: PyObject, + op: crate::circuit_instruction::OperationInput, qargs: Option<&Bound>, cargs: Option<&Bound>, + params: smallvec::SmallVec<[crate::operations::Param; 3]>, + label: Option, + duration: Option, + unit: Option, + condition: Option, dag: Option<&Bound>, ) -> PyResult<(Self, DAGNode)> { let qargs = @@ -110,34 +119,16 @@ impl DAGOpNode { } None => qargs.str()?.into_any(), }; - let res = convert_py_to_operation_type(py, op.clone_ref(py))?; - let extra_attrs = if res.label.is_some() - || res.duration.is_some() - || res.unit.is_some() - || res.condition.is_some() - { - Some(Box::new(ExtraInstructionAttributes { - label: res.label, - duration: res.duration, - unit: res.unit, - condition: res.condition, - })) - } else { - None - }; + let mut instruction = CircuitInstruction::py_new( + py, op, None, None, params, label, duration, unit, condition, + )?; + instruction.qubits = qargs.into(); + instruction.clbits = cargs.into(); Ok(( DAGOpNode { - instruction: CircuitInstruction { - operation: res.operation, - qubits: qargs.unbind(), - clbits: cargs.unbind(), - params: res.params, - extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: Some(op), - }, + instruction, sort_key: sort_key.unbind(), }, DAGNode { _node_id: -1 }, @@ -219,6 +210,77 @@ impl DAGOpNode { self.instruction.operation.name().to_object(py) } + #[getter] + fn get_params(&self, py: Python) -> PyObject { + self.instruction.params.to_object(py) + } + + #[getter] + fn matrix(&self, py: Python) -> Option { + let matrix = self.instruction.operation.matrix(&self.instruction.params); + matrix.map(|mat| mat.into_pyarray_bound(py).into()) + } + + #[getter] + fn label(&self) -> Option<&str> { + self.instruction + .extra_attrs + .as_ref() + .and_then(|attrs| attrs.label.as_deref()) + } + + #[getter] + fn condition(&self, py: Python) -> Option { + self.instruction + .extra_attrs + .as_ref() + .and_then(|attrs| attrs.condition.as_ref().map(|x| x.clone_ref(py))) + } + + #[getter] + fn duration(&self, py: Python) -> Option { + self.instruction + .extra_attrs + .as_ref() + .and_then(|attrs| attrs.duration.as_ref().map(|x| x.clone_ref(py))) + } + + #[getter] + fn unit(&self) -> Option<&str> { + self.instruction + .extra_attrs + .as_ref() + .and_then(|attrs| attrs.unit.as_deref()) + } + + #[setter] + fn set_label(&mut self, val: Option) { + match self.instruction.extra_attrs.as_mut() { + Some(attrs) => attrs.label = val, + None => { + if val.is_some() { + self.instruction.extra_attrs = Some(Box::new( + crate::circuit_instruction::ExtraInstructionAttributes { + label: val, + duration: None, + unit: None, + condition: None, + }, + )) + } + } + }; + if let Some(attrs) = &self.instruction.extra_attrs { + if attrs.label.is_none() + && attrs.duration.is_none() + && attrs.unit.is_none() + && attrs.condition.is_none() + { + self.instruction.extra_attrs = None; + } + } + } + /// Sets the Instruction name corresponding to the op for this node #[setter] fn set_name(&mut self, py: Python, new_name: PyObject) -> PyResult<()> { @@ -229,6 +291,11 @@ impl DAGOpNode { Ok(()) } + #[getter] + fn _raw_op(&self, py: Python) -> PyObject { + self.instruction.operation.clone().into_py(py) + } + /// Returns a representation of the DAGOpNode fn __repr__(&self, py: Python) -> PyResult { Ok(format!( diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 53fee34f486e..554f553d9427 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -71,6 +71,7 @@ pub static SINGLETON_GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.singleton", "SingletonGate"); pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.singleton", "SingletonControlledGate"); +pub static DEEPCOPY: ImportOnceCell = ImportOnceCell::new("copy", "deepcopy"); pub static WARNINGS_WARN: ImportOnceCell = ImportOnceCell::new("warnings", "warn"); diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index df15b4abb415..8935e72e0ad5 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -13,7 +13,7 @@ use std::f64::consts::PI; use crate::circuit_data::CircuitData; -use crate::imports::{PARAMETER_EXPRESSION, QUANTUM_CIRCUIT}; +use crate::imports::{DEEPCOPY, PARAMETER_EXPRESSION, QUANTUM_CIRCUIT}; use crate::{gate_matrix, Qubit}; use ndarray::{aview2, Array2}; @@ -35,6 +35,17 @@ pub enum OperationType { Operation(PyOperation), } +impl IntoPy for OperationType { + fn into_py(self, py: Python) -> PyObject { + match self { + Self::Standard(gate) => gate.into_py(py), + Self::Instruction(inst) => inst.into_py(py), + Self::Gate(gate) => gate.into_py(py), + Self::Operation(op) => op.into_py(py), + } + } +} + impl Operation for OperationType { fn name(&self) -> &str { match self { @@ -1418,6 +1429,16 @@ impl PyInstruction { instruction, } } + + fn __deepcopy__(&self, py: Python, _memo: PyObject) -> PyResult { + Ok(PyInstruction { + qubits: self.qubits, + clbits: self.clbits, + params: self.params, + op_name: self.op_name.clone(), + instruction: DEEPCOPY.get_bound(py).call1((&self.instruction,))?.unbind(), + }) + } } impl Operation for PyInstruction { @@ -1497,6 +1518,16 @@ impl PyGate { gate, } } + + fn __deepcopy__(&self, py: Python, _memo: PyObject) -> PyResult { + Ok(PyGate { + qubits: self.qubits, + clbits: self.clbits, + params: self.params, + op_name: self.op_name.clone(), + gate: DEEPCOPY.get_bound(py).call1((&self.gate,))?.unbind(), + }) + } } impl Operation for PyGate { @@ -1589,6 +1620,16 @@ impl PyOperation { operation, } } + + fn __deepcopy__(&self, py: Python, _memo: PyObject) -> PyResult { + Ok(PyOperation { + qubits: self.qubits, + clbits: self.clbits, + params: self.params, + op_name: self.op_name.clone(), + operation: DEEPCOPY.get_bound(py).call1((&self.operation,))?.unbind(), + }) + } } impl Operation for PyOperation { diff --git a/qiskit/converters/circuit_to_dag.py b/qiskit/converters/circuit_to_dag.py index b2c1df2a037b..88d9c72f1d61 100644 --- a/qiskit/converters/circuit_to_dag.py +++ b/qiskit/converters/circuit_to_dag.py @@ -13,7 +13,8 @@ """Helper function for converting a circuit to a dag""" import copy -from qiskit.dagcircuit.dagcircuit import DAGCircuit +from qiskit.dagcircuit.dagcircuit import DAGCircuit, DAGOpNode +from qiskit._accelerate.circuit import StandardGate def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_order=None): @@ -93,10 +94,24 @@ def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_ord dagcircuit.add_creg(register) for instruction in circuit.data: - op = instruction.operation - if copy_operations: - op = copy.deepcopy(op) - dagcircuit.apply_operation_back(op, instruction.qubits, instruction.clbits, check=False) + if not isinstance(instruction._raw_op, StandardGate): + op = instruction.operation + if copy_operations: + op = copy.deepcopy(op) + dagcircuit.apply_operation_back(op, instruction.qubits, instruction.clbits, check=False) + else: + node = DAGOpNode( + instruction._raw_op, + qargs=instruction.qubits, + cargs=instruction.clbits, + params=instruction.params, + label=instruction.label, + duration=instruction.duration, + unit=instruction.unit, + condition=instruction.condition, + dag=dagcircuit, + ) + dagcircuit._apply_op_node_back(node) dagcircuit.duration = circuit.duration dagcircuit.unit = circuit.unit diff --git a/qiskit/converters/dag_to_circuit.py b/qiskit/converters/dag_to_circuit.py index ede026c247c9..3667c2183eae 100644 --- a/qiskit/converters/dag_to_circuit.py +++ b/qiskit/converters/dag_to_circuit.py @@ -14,6 +14,7 @@ import copy from qiskit.circuit import QuantumCircuit, CircuitInstruction +from qiskit._accelerate.circuit import StandardGate def dag_to_circuit(dag, copy_operations=True): @@ -71,10 +72,24 @@ def dag_to_circuit(dag, copy_operations=True): circuit.calibrations = dag.calibrations for node in dag.topological_op_nodes(): - op = node.op - if copy_operations: - op = copy.deepcopy(op) - circuit._append(CircuitInstruction(op, node.qargs, node.cargs)) + if not isinstance(node._raw_op, StandardGate): + op = node.op + if copy_operations: + op = copy.deepcopy(op) + circuit._append(CircuitInstruction(op, node.qargs, node.cargs)) + else: + circuit._append( + CircuitInstruction( + node._raw_op, + node.qargs, + node.cargs, + params=node.params, + label=node.label, + duration=node.duration, + unit=node.unit, + condition=node.condition, + ) + ) circuit.duration = dag.duration circuit.unit = dag.unit diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 944e2df625b0..a0d2c42be17e 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -717,6 +717,27 @@ def copy_empty_like(self, *, vars_mode: _VarsMode = "alike"): return target_dag + def _apply_op_node_back(self, node: DAGOpNode): + additional = () + if _may_have_additional_wires(node): + # This is the slow path; most of the time, this won't happen. + additional = set(_additional_wires(node)).difference(node.cargs) + + node._node_id = self._multi_graph.add_node(node) + self._increment_op(node) + + # Add new in-edges from predecessors of the output nodes to the + # operation node while deleting the old in-edges of the output nodes + # and adding new edges from the operation node to each output node + self._multi_graph.insert_node_on_in_edges_multiple( + node._node_id, + [ + self.output_map[bit]._node_id + for bits in (node.qargs, node.cargs, additional) + for bit in bits + ], + ) + def apply_operation_back( self, op: Operation, diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 7db48d6d1395..ab7c5e04649f 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -32,17 +32,17 @@ from qiskit.transpiler import CouplingMap, Target from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError -from qiskit.dagcircuit.dagcircuit import DAGCircuit +from qiskit.dagcircuit.dagcircuit import DAGCircuit, DAGOpNode from qiskit.synthesis.one_qubit import one_qubit_decompose from qiskit.transpiler.passes.optimization.optimize_1q_decomposition import _possible_decomposers from qiskit.synthesis.two_qubit.xx_decompose import XXDecomposer, XXEmbodiments from qiskit.synthesis.two_qubit.two_qubit_decompose import ( TwoQubitBasisDecomposer, TwoQubitWeylDecomposition, - GATE_NAME_MAP, ) from qiskit.quantum_info import Operator -from qiskit.circuit import ControlFlowOp, Gate, Parameter +from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES +from qiskit.circuit import Gate, Parameter from qiskit.circuit.library.standard_gates import ( iSwapGate, CXGate, @@ -50,6 +50,17 @@ RXXGate, RZXGate, ECRGate, + RXGate, + SXGate, + XGate, + RZGate, + UGate, + PhaseGate, + U1Gate, + U2Gate, + U3Gate, + RYGate, + RGate, ) from qiskit.transpiler.passes.synthesis import plugin from qiskit.transpiler.passes.optimization.optimize_1q_decomposition import ( @@ -60,6 +71,22 @@ from qiskit.exceptions import QiskitError +GATE_NAME_MAP = { + "cx": CXGate._standard_gate, + "rx": RXGate._standard_gate, + "sx": SXGate._standard_gate, + "x": XGate._standard_gate, + "rz": RZGate._standard_gate, + "u": UGate._standard_gate, + "p": PhaseGate._standard_gate, + "u1": U1Gate._standard_gate, + "u2": U2Gate._standard_gate, + "u3": U3Gate._standard_gate, + "ry": RYGate._standard_gate, + "r": RGate._standard_gate, +} + + KAK_GATE_NAMES = { "cx": CXGate(), "cz": CZGate(), @@ -479,7 +506,9 @@ def _run_main_loop( self, dag, qubit_indices, plugin_method, plugin_kwargs, default_method, default_kwargs ): """Inner loop for the optimizer, after all DAG-independent set-up has been completed.""" - for node in dag.op_nodes(ControlFlowOp): + for node in dag.op_nodes(): + if node.name not in CONTROL_FLOW_OP_NAMES: + continue node.op = node.op.replace_blocks( [ dag_to_circuit( @@ -502,9 +531,9 @@ def _run_main_loop( out_dag = dag.copy_empty_like() for node in dag.topological_op_nodes(): - if node.op.name == "unitary" and len(node.qargs) >= self._min_qubits: + if node.name == "unitary" and len(node.qargs) >= self._min_qubits: synth_dag = None - unitary = node.op.to_matrix() + unitary = node.matrix n_qubits = len(node.qargs) if ( plugin_method.max_qubits is not None and n_qubits > plugin_method.max_qubits @@ -519,35 +548,41 @@ def _run_main_loop( ) synth_dag = method.run(unitary, **kwargs) if synth_dag is None: - out_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) + out_dag._apply_op_node_back(node) continue if isinstance(synth_dag, DAGCircuit): qubit_map = dict(zip(synth_dag.qubits, node.qargs)) for node in synth_dag.topological_op_nodes(): - out_dag.apply_operation_back( - node.op, (qubit_map[x] for x in node.qargs), check=False - ) + node.qargs = tuple(qubit_map[x] for x in node.qargs) + out_dag._apply_op_node_back(node) out_dag.global_phase += synth_dag.global_phase else: node_list, global_phase, gate = synth_dag qubits = node.qargs + user_gate_node = DAGOpNode(gate) for ( op_name, params, qargs, ) in node_list: if op_name == "USER_GATE": - op = gate + node = DAGOpNode( + user_gate_node._raw_op, + params=user_gate_node.params, + qargs=tuple(qubits[x] for x in qargs), + dag=out_dag, + ) else: - op = GATE_NAME_MAP[op_name](*params) - out_dag.apply_operation_back( - op, - (qubits[x] for x in qargs), - check=False, - ) + node = DAGOpNode( + GATE_NAME_MAP[op_name], + params=params, + qargs=tuple(qubits[x] for x in qargs), + dag=out_dag, + ) + out_dag._apply_op_node_back(node) out_dag.global_phase += global_phase else: - out_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) + out_dag._apply_op_node_back(node) return out_dag @@ -1008,5 +1043,6 @@ def _reversed_synth_su4(self, su4_mat, decomposer2q, approximation_degree): flip_bits = out_dag.qubits[::-1] for node in synth_circ.topological_op_nodes(): qubits = tuple(flip_bits[synth_circ.find_bit(x).index] for x in node.qargs) - out_dag.apply_operation_back(node.op, qubits, check=False) + node = DAGOpNode(node._raw_op, qargs=qubits, params=node.params) + out_dag._apply_op_node_back(node) return out_dag diff --git a/qiskit/transpiler/passes/utils/check_gate_direction.py b/qiskit/transpiler/passes/utils/check_gate_direction.py index 1ddfd40124b5..e797be95c4a1 100644 --- a/qiskit/transpiler/passes/utils/check_gate_direction.py +++ b/qiskit/transpiler/passes/utils/check_gate_direction.py @@ -12,7 +12,7 @@ """Check if the gates follow the right direction with respect to the coupling map.""" -from qiskit.circuit import ControlFlowOp +from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES from qiskit.converters import circuit_to_dag from qiskit.transpiler.basepasses import AnalysisPass @@ -39,7 +39,7 @@ def _coupling_map_visit(self, dag, wire_map, edges=None): edges = self.coupling_map.get_edges() # Don't include directives to avoid things like barrier, which are assumed always supported. for node in dag.op_nodes(include_directives=False): - if isinstance(node.op, ControlFlowOp): + if node.name in CONTROL_FLOW_OP_NAMES: for block in node.op.blocks: inner_wire_map = { inner: wire_map[outer] for outer, inner in zip(node.qargs, block.qubits) @@ -57,7 +57,7 @@ def _coupling_map_visit(self, dag, wire_map, edges=None): def _target_visit(self, dag, wire_map): # Don't include directives to avoid things like barrier, which are assumed always supported. for node in dag.op_nodes(include_directives=False): - if isinstance(node.op, ControlFlowOp): + if node.name in CONTROL_FLOW_OP_NAMES: for block in node.op.blocks: inner_wire_map = { inner: wire_map[outer] for outer, inner in zip(node.qargs, block.qubits) @@ -65,7 +65,7 @@ def _target_visit(self, dag, wire_map): if not self._target_visit(circuit_to_dag(block), inner_wire_map): return False elif len(node.qargs) == 2 and not self.target.instruction_supported( - node.op.name, (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) + node.name, (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) ): return False return True From b399ec30d69d8e0681e8dfab8f83816c77b96f37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:00:47 +0000 Subject: [PATCH 44/89] Bump rustworkx-core from 0.15.0 to 0.15.1 (#12708) Bumps [rustworkx-core](https://github.com/Qiskit/rustworkx) from 0.15.0 to 0.15.1. - [Release notes](https://github.com/Qiskit/rustworkx/releases) - [Commits](https://github.com/Qiskit/rustworkx/compare/0.15.0...0.15.1) --- updated-dependencies: - dependency-name: rustworkx-core 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 6ce26b3baea5..a69ed15a19db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1392,9 +1392,9 @@ checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "rustworkx-core" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2b9aa5926b35dd3029530aef27eac0926b544c78f8e8f1aad4d37854b132fe9" +checksum = "ef8108bdaf5b590d2ea261c6ca9b1795cbf253d0733b2e209b7990c95ed23843" dependencies = [ "ahash 0.8.11", "fixedbitset", From 283a880329d2fd4a3058c49e58829d63a803c8c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:06:12 +0000 Subject: [PATCH 45/89] Bump thiserror from 1.0.59 to 1.0.61 (#12707) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.59 to 1.0.61. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.59...1.0.61) --- updated-dependencies: - dependency-name: thiserror 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 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a69ed15a19db..125ce6abe876 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1527,18 +1527,18 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", From c674913e688dba7bbe531fbd9e2255e5e2cc7a4d Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 2 Jul 2024 17:09:30 +0200 Subject: [PATCH 46/89] Oxidize `synth_permutation_acg` (#12543) * Move utility functions _inverse_pattern and _get_ordered_swap to Rust * fix formatting and pylint issues * Changed input type to `PyArrayLike1` * Refactor `permutation.rs`, clean up imports, fix coverage error * fix docstring for `_inverse_pattern` Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * fix docstring for `_get_ordered_swap` Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * remove pymodule nesting * remove explicit `AllowTypeChange` * Move input validation out of `_inverse_pattern` and `_get_ordered_swap` * oxidization attempt no. 1 * working version! maybe faster possible... * move more into rust & fix clones * layouting & comments * dangling comment * circuit construction in rust * remove dangling code * more lint * add reno * drop redundant Ok(expect()) * Implements Shelly's suggestions * simplify code a little --------- Co-authored-by: jpacold Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> --- .../src/synthesis/permutation/mod.rs | 24 ++++++ .../src/synthesis/permutation/utils.rs | 73 ++++++++++++++++++- .../synthesis/permutation/permutation_full.py | 22 +----- .../permutation/permutation_utils.py | 34 +-------- .../notes/oxidize-acg-0294a87c0d5974fa.yaml | 5 ++ 5 files changed, 104 insertions(+), 54 deletions(-) create mode 100644 releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs index bf0ff97848f2..0e3900a4b8bf 100644 --- a/crates/accelerate/src/synthesis/permutation/mod.rs +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -59,10 +59,34 @@ pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1) -> PyRes ) } +#[pyfunction] +#[pyo3(signature = (pattern))] +fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1) -> PyResult { + let inverted = utils::invert(&pattern.as_array()); + let view = inverted.view(); + let num_qubits = view.len(); + let cycles = utils::pattern_to_cycles(&view); + let swaps = utils::decompose_cycles(&cycles); + + CircuitData::from_standard_gates( + py, + num_qubits as u32, + swaps.iter().map(|(i, j)| { + ( + StandardGate::SwapGate, + smallvec![], + smallvec![Qubit(*i as u32), Qubit(*j as u32)], + ) + }), + Param::Float(0.0), + ) +} + #[pymodule] pub fn permutation(m: &Bound) -> PyResult<()> { m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; m.add_function(wrap_pyfunction!(_synth_permutation_basic, m)?)?; + m.add_function(wrap_pyfunction!(_synth_permutation_acg, m)?)?; Ok(()) } diff --git a/crates/accelerate/src/synthesis/permutation/utils.rs b/crates/accelerate/src/synthesis/permutation/utils.rs index a78088bfbfa9..620ce4628741 100644 --- a/crates/accelerate/src/synthesis/permutation/utils.rs +++ b/crates/accelerate/src/synthesis/permutation/utils.rs @@ -15,6 +15,8 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use std::vec::Vec; +use qiskit_circuit::slice::{PySequenceIndex, PySequenceIndexError, SequenceIndex}; + pub fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { let n = pattern.len(); let mut seen: Vec = vec![false; n]; @@ -63,19 +65,19 @@ pub fn invert(pattern: &ArrayView1) -> Array1 { /// then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``); /// if the input permutation consists of several disjoint cycles, then each cycle /// is essentially treated independently. -pub fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { +pub fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(usize, usize)> { let mut permutation: Vec = pattern.iter().map(|&x| x as usize).collect(); let mut index_map = invert(pattern); let n = permutation.len(); - let mut swaps: Vec<(i64, i64)> = Vec::with_capacity(n); + let mut swaps: Vec<(usize, usize)> = Vec::with_capacity(n); for ii in 0..n { let val = permutation[ii]; if val == ii { continue; } let jj = index_map[ii]; - swaps.push((ii as i64, jj as i64)); + swaps.push((ii, jj)); (permutation[ii], permutation[jj]) = (permutation[jj], permutation[ii]); index_map[val] = jj; index_map[ii] = ii; @@ -84,3 +86,68 @@ pub fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { swaps[..].reverse(); swaps } + +/// Explore cycles in a permutation pattern. This is probably best explained in an +/// example: let a pattern be [1, 2, 3, 0, 4, 6, 5], then it contains the two +/// cycles [1, 2, 3, 0] and [6, 5]. The index [4] does not perform a permutation and does +/// therefore not create a cycle. +pub fn pattern_to_cycles(pattern: &ArrayView1) -> Vec> { + // vector keeping track of which elements in the permutation pattern have been visited + let mut explored: Vec = vec![false; pattern.len()]; + + // vector to store the cycles + let mut cycles: Vec> = Vec::new(); + + for pos in pattern { + let mut cycle: Vec = Vec::new(); + + // follow the cycle until we reached an entry we saw before + let mut i = *pos; + while !explored[i] { + cycle.push(i); + explored[i] = true; + i = pattern[i]; + } + // cycles must have more than 1 element + if cycle.len() > 1 { + cycles.push(cycle); + } + } + + cycles +} + +/// Periodic (or Python-like) access to a vector. +/// Util used below in ``decompose_cycles``. +#[inline] +fn pget(vec: &Vec, index: isize) -> Result { + let SequenceIndex::Int(wrapped) = PySequenceIndex::Int(index).with_len(vec.len())? else {unreachable!()}; + Ok(vec[wrapped]) +} + +/// Given a disjoint cycle decomposition of a permutation pattern (see the function +/// ``pattern_to_cycles``), decomposes every cycle into a series of SWAPs to implement it. +/// In combination with ``pattern_to_cycle``, this function allows to implement a +/// full permutation pattern by applying SWAP gates on the returned index-pairs. +pub fn decompose_cycles(cycles: &Vec>) -> Vec<(usize, usize)> { + let mut swaps: Vec<(usize, usize)> = Vec::new(); + + for cycle in cycles { + let length = cycle.len() as isize; + + for idx in 0..(length - 1) / 2 { + swaps.push(( + pget(cycle, idx - 1).unwrap(), + pget(cycle, length - 3 - idx).unwrap(), + )); + } + for idx in 0..length / 2 { + swaps.push(( + pget(cycle, idx - 1).unwrap(), + pget(cycle, length - 2 - idx).unwrap(), + )); + } + } + + swaps +} diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index c280065c2a57..2fd892a0427e 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -16,11 +16,9 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit._accelerate.synthesis.permutation import _synth_permutation_basic -from .permutation_utils import ( - _inverse_pattern, - _pattern_to_cycles, - _decompose_cycles, +from qiskit._accelerate.synthesis.permutation import ( + _synth_permutation_basic, + _synth_permutation_acg, ) @@ -77,16 +75,4 @@ def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircui *Routing Permutations on Graphs Via Matchings.*, `(Full paper) `_ """ - - num_qubits = len(pattern) - qc = QuantumCircuit(num_qubits) - - # invert pattern (Qiskit notation is opposite) - cur_pattern = _inverse_pattern(pattern) - cycles = _pattern_to_cycles(cur_pattern) - swaps = _decompose_cycles(cycles) - - for swap in swaps: - qc.swap(swap[0], swap[1]) - - return qc + return QuantumCircuit._from_circuit_data(_synth_permutation_acg(pattern)) diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index 4520e18f4d06..a8d18b8a8196 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -13,36 +13,4 @@ """Utility functions for handling permutations.""" # pylint: disable=unused-import -from qiskit._accelerate.synthesis.permutation import ( - _inverse_pattern, - _validate_permutation, -) - - -def _pattern_to_cycles(pattern): - """Given a permutation pattern, creates its disjoint cycle decomposition.""" - nq = len(pattern) - explored = [False] * nq - cycles = [] - for i in pattern: - cycle = [] - while not explored[i]: - cycle.append(i) - explored[i] = True - i = pattern[i] - if len(cycle) >= 2: - cycles.append(cycle) - return cycles - - -def _decompose_cycles(cycles): - """Given a disjoint cycle decomposition, decomposes every cycle into a SWAP - circuit of depth 2.""" - swap_list = [] - for cycle in cycles: - m = len(cycle) - for i in range((m - 1) // 2): - swap_list.append((cycle[i - 1], cycle[m - 3 - i])) - for i in range(m // 2): - swap_list.append((cycle[i - 1], cycle[m - 2 - i])) - return swap_list +from qiskit._accelerate.synthesis.permutation import _inverse_pattern, _validate_permutation diff --git a/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml b/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml new file mode 100644 index 000000000000..6bd6761e0355 --- /dev/null +++ b/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml @@ -0,0 +1,5 @@ +--- +upgrade_synthesis: + - | + Port :func:`.synth_permutation_acg`, used to synthesize qubit permutations, to Rust. + This produces an approximate 3x performance improvement on 1000 qubit circuits. From 3fb175390d5fa007150fb76a23b914059010788a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 2 Jul 2024 15:13:48 -0400 Subject: [PATCH 47/89] Fix cargo clippy and rustfmt for Rust 1.79 (#12710) After the recently merged #12543 when working with Rust 1.79 cargo fmt makes a small formatting change that rust 1.70 wouldn't and clippy makes flags a &Vec<_> that should be a slice &[_] instead. This commit makes these two small chagnes so they're not an issue for people building with the latest stable version of rust, not just our MSRV. --- crates/accelerate/src/synthesis/permutation/utils.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/synthesis/permutation/utils.rs b/crates/accelerate/src/synthesis/permutation/utils.rs index 620ce4628741..47a9e1c3a7a9 100644 --- a/crates/accelerate/src/synthesis/permutation/utils.rs +++ b/crates/accelerate/src/synthesis/permutation/utils.rs @@ -120,8 +120,10 @@ pub fn pattern_to_cycles(pattern: &ArrayView1) -> Vec> { /// Periodic (or Python-like) access to a vector. /// Util used below in ``decompose_cycles``. #[inline] -fn pget(vec: &Vec, index: isize) -> Result { - let SequenceIndex::Int(wrapped) = PySequenceIndex::Int(index).with_len(vec.len())? else {unreachable!()}; +fn pget(vec: &[usize], index: isize) -> Result { + let SequenceIndex::Int(wrapped) = PySequenceIndex::Int(index).with_len(vec.len())? else { + unreachable!() + }; Ok(vec[wrapped]) } From bfc69a499c035bc3b8af31b37ba0469d7e89e67a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 3 Jul 2024 00:18:40 -0400 Subject: [PATCH 48/89] Use rust gates for Optimize1QGatesDecomposition (#12650) * Use rust gates for Optimize1QGatesDecomposition This commit moves to using rust gates for the Optimize1QGatesDecomposition transpiler pass. It takes in a sequence of runs (which are a list of DAGOpNodes) from the python side of the transpiler pass which are generated from DAGCircuit.collect_1q_runs() (which in the future should be moved to rust after #12550 merges). The rust portion of the pass now iterates over each run, performs the matrix multiplication to compute the unitary of the run, then synthesizes that unitary, computes the estimated error of the circuit synthesis and returns a tuple of the circuit sequence in terms of rust StandardGate enums. The python portion of the code then takes those sequences and does inplace substitution of each run with the sequence returned from rust. Once #12550 merges we should be able to move the input collect_1q_runs() call and perform the output node substitions in rust making the full pass execute in the rust domain without any python interaction. Additionally, the OneQubitEulerDecomposer class is updated to use rust for circuit generation instead of doing this python side. The internal changes done to use rust gates in the transpiler pass meant we were half way to this already by emitting rust StandardGates instead of python gate objects. The dag handling is still done in Python however until #12550 merges. This also includes an implementation of the r gate, I temporarily added this to unblock this effort as it was the only gate missing needed to complete this. We can rebase this if a standalone implementation of the gate merges before this. * Cache target decompositions for each qubit Previously this PR was re-computing the target bases to synthesize with for each run found in the circuit. But in cases where there were multiple runs repeated on a qubit this was unecessary work. Prior to moving this code to rust there was already caching code to make this optimization, but the rust path short circuited around this. This commit fixes this so we're caching the target bases for each qubit and only computing it once. * Optimize rust implementation slightly * Avoid extra allocations by inlining matrix multiplication * Remove unnecessary comment * Remove stray code block * Add import path for rust gate * Use rust gate in circuit constructor * Remove duplicated op_name getter and just use existing name getter * Apply suggestions from code review Co-authored-by: John Lapeyre * Simplify construction of target_basis_vec * Fix rebase issue * Update crates/accelerate/src/euler_one_qubit_decomposer.rs Co-authored-by: John Lapeyre * Update crates/accelerate/src/euler_one_qubit_decomposer.rs --------- Co-authored-by: John Lapeyre --- .../src/euler_one_qubit_decomposer.rs | 301 +++++++++++++++--- crates/accelerate/src/two_qubit_decompose.rs | 19 +- crates/circuit/src/dag_node.rs | 50 ++- crates/circuit/src/operations.rs | 6 + qiskit/dagcircuit/dagcircuit.py | 57 ++-- .../one_qubit/one_qubit_decompose.py | 50 ++- .../optimization/optimize_1q_decomposition.py | 105 +++--- 7 files changed, 431 insertions(+), 157 deletions(-) diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 01725269bb84..8c3a87ce51ec 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -18,7 +18,6 @@ use num_complex::{Complex64, ComplexFloat}; use smallvec::{smallvec, SmallVec}; use std::cmp::Ordering; use std::f64::consts::PI; -use std::ops::Deref; use std::str::FromStr; use pyo3::exceptions::PyValueError; @@ -31,8 +30,12 @@ use ndarray::prelude::*; use numpy::PyReadonlyArray2; use pyo3::pybacked::PyBackedStr; +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::dag_node::DAGOpNode; +use qiskit_circuit::operations::{Operation, Param, StandardGate}; use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex}; use qiskit_circuit::util::c64; +use qiskit_circuit::Qubit; pub const ANGLE_ZERO_EPSILON: f64 = 1e-12; @@ -68,12 +71,12 @@ impl OneQubitGateErrorMap { #[pyclass(sequence)] pub struct OneQubitGateSequence { - pub gates: Vec<(String, SmallVec<[f64; 3]>)>, + pub gates: Vec<(StandardGate, SmallVec<[f64; 3]>)>, #[pyo3(get)] pub global_phase: f64, } -type OneQubitGateSequenceState = (Vec<(String, SmallVec<[f64; 3]>)>, f64); +type OneQubitGateSequenceState = (Vec<(StandardGate, SmallVec<[f64; 3]>)>, f64); #[pymethods] impl OneQubitGateSequence { @@ -115,15 +118,15 @@ fn circuit_kak( phi: f64, lam: f64, phase: f64, - k_gate: &str, - a_gate: &str, + k_gate: StandardGate, + a_gate: StandardGate, simplify: bool, atol: Option, ) -> OneQubitGateSequence { let mut lam = lam; let mut theta = theta; let mut phi = phi; - let mut circuit: Vec<(String, SmallVec<[f64; 3]>)> = Vec::with_capacity(3); + let mut circuit: Vec<(StandardGate, SmallVec<[f64; 3]>)> = Vec::with_capacity(3); let mut atol = match atol { Some(atol) => atol, None => ANGLE_ZERO_EPSILON, @@ -139,7 +142,7 @@ fn circuit_kak( // slippage coming from _mod_2pi injecting multiples of 2pi. lam = mod_2pi(lam, atol); if lam.abs() > atol { - circuit.push((String::from(k_gate), smallvec![lam])); + circuit.push((k_gate, smallvec![lam])); global_phase += lam / 2.; } return OneQubitGateSequence { @@ -160,13 +163,13 @@ fn circuit_kak( lam = mod_2pi(lam, atol); if lam.abs() > atol { global_phase += lam / 2.; - circuit.push((String::from(k_gate), smallvec![lam])); + circuit.push((k_gate, smallvec![lam])); } - circuit.push((String::from(a_gate), smallvec![theta])); + circuit.push((a_gate, smallvec![theta])); phi = mod_2pi(phi, atol); if phi.abs() > atol { global_phase += phi / 2.; - circuit.push((String::from(k_gate), smallvec![phi])); + circuit.push((k_gate, smallvec![phi])); } OneQubitGateSequence { gates: circuit, @@ -190,7 +193,7 @@ fn circuit_u3( let phi = mod_2pi(phi, atol); let lam = mod_2pi(lam, atol); if !simplify || theta.abs() > atol || phi.abs() > atol || lam.abs() > atol { - circuit.push((String::from("u3"), smallvec![theta, phi, lam])); + circuit.push((StandardGate::U3Gate, smallvec![theta, phi, lam])); } OneQubitGateSequence { gates: circuit, @@ -217,16 +220,16 @@ fn circuit_u321( if theta.abs() < atol { let tot = mod_2pi(phi + lam, atol); if tot.abs() > atol { - circuit.push((String::from("u1"), smallvec![tot])); + circuit.push((StandardGate::U1Gate, smallvec![tot])); } } else if (theta - PI / 2.).abs() < atol { circuit.push(( - String::from("u2"), + StandardGate::U2Gate, smallvec![mod_2pi(phi, atol), mod_2pi(lam, atol)], )); } else { circuit.push(( - String::from("u3"), + StandardGate::U3Gate, smallvec![theta, mod_2pi(phi, atol), mod_2pi(lam, atol)], )); } @@ -255,7 +258,7 @@ fn circuit_u( let phi = mod_2pi(phi, atol); let lam = mod_2pi(lam, atol); if theta.abs() > atol || phi.abs() > atol || lam.abs() > atol { - circuit.push((String::from("u"), smallvec![theta, phi, lam])); + circuit.push((StandardGate::UGate, smallvec![theta, phi, lam])); } OneQubitGateSequence { gates: circuit, @@ -358,7 +361,7 @@ fn circuit_rr( // This can be expressed as a single R gate if theta.abs() > atol { circuit.push(( - String::from("r"), + StandardGate::RGate, smallvec![theta, mod_2pi(PI / 2. + phi, atol)], )); } @@ -366,12 +369,12 @@ fn circuit_rr( // General case: use two R gates if (theta - PI).abs() > atol { circuit.push(( - String::from("r"), + StandardGate::RGate, smallvec![theta - PI, mod_2pi(PI / 2. - lam, atol)], )); } circuit.push(( - String::from("r"), + StandardGate::RGate, smallvec![PI, mod_2pi(0.5 * (phi - lam + PI), atol)], )); } @@ -393,10 +396,46 @@ pub fn generate_circuit( atol: Option, ) -> PyResult { let res = match target_basis { - EulerBasis::ZYZ => circuit_kak(theta, phi, lam, phase, "rz", "ry", simplify, atol), - EulerBasis::ZXZ => circuit_kak(theta, phi, lam, phase, "rz", "rx", simplify, atol), - EulerBasis::XZX => circuit_kak(theta, phi, lam, phase, "rx", "rz", simplify, atol), - EulerBasis::XYX => circuit_kak(theta, phi, lam, phase, "rx", "ry", simplify, atol), + EulerBasis::ZYZ => circuit_kak( + theta, + phi, + lam, + phase, + StandardGate::RZGate, + StandardGate::RYGate, + simplify, + atol, + ), + EulerBasis::ZXZ => circuit_kak( + theta, + phi, + lam, + phase, + StandardGate::RZGate, + StandardGate::RXGate, + simplify, + atol, + ), + EulerBasis::XZX => circuit_kak( + theta, + phi, + lam, + phase, + StandardGate::RXGate, + StandardGate::RZGate, + simplify, + atol, + ), + EulerBasis::XYX => circuit_kak( + theta, + phi, + lam, + phase, + StandardGate::RXGate, + StandardGate::RYGate, + simplify, + atol, + ), EulerBasis::U3 => circuit_u3(theta, phi, lam, phase, simplify, atol), EulerBasis::U321 => circuit_u321(theta, phi, lam, phase, simplify, atol), EulerBasis::U => circuit_u(theta, phi, lam, phase, simplify, atol), @@ -411,11 +450,13 @@ pub fn generate_circuit( let fnz = |circuit: &mut OneQubitGateSequence, phi: f64| { let phi = mod_2pi(phi, inner_atol); if phi.abs() > inner_atol { - circuit.gates.push((String::from("p"), smallvec![phi])); + circuit + .gates + .push((StandardGate::PhaseGate, smallvec![phi])); } }; let fnx = |circuit: &mut OneQubitGateSequence| { - circuit.gates.push((String::from("sx"), SmallVec::new())); + circuit.gates.push((StandardGate::SXGate, SmallVec::new())); }; circuit_psx_gen( @@ -441,12 +482,12 @@ pub fn generate_circuit( let fnz = |circuit: &mut OneQubitGateSequence, phi: f64| { let phi = mod_2pi(phi, inner_atol); if phi.abs() > inner_atol { - circuit.gates.push((String::from("rz"), smallvec![phi])); + circuit.gates.push((StandardGate::RZGate, smallvec![phi])); circuit.global_phase += phi / 2.; } }; let fnx = |circuit: &mut OneQubitGateSequence| { - circuit.gates.push((String::from("sx"), SmallVec::new())); + circuit.gates.push((StandardGate::SXGate, SmallVec::new())); }; circuit_psx_gen( theta, @@ -471,12 +512,14 @@ pub fn generate_circuit( let fnz = |circuit: &mut OneQubitGateSequence, phi: f64| { let phi = mod_2pi(phi, inner_atol); if phi.abs() > inner_atol { - circuit.gates.push((String::from("u1"), smallvec![phi])); + circuit.gates.push((StandardGate::U1Gate, smallvec![phi])); } }; let fnx = |circuit: &mut OneQubitGateSequence| { circuit.global_phase += PI / 4.; - circuit.gates.push((String::from("rx"), smallvec![PI / 2.])); + circuit + .gates + .push((StandardGate::RXGate, smallvec![PI / 2.])); }; circuit_psx_gen( theta, @@ -501,15 +544,15 @@ pub fn generate_circuit( let fnz = |circuit: &mut OneQubitGateSequence, phi: f64| { let phi = mod_2pi(phi, inner_atol); if phi.abs() > inner_atol { - circuit.gates.push((String::from("rz"), smallvec![phi])); + circuit.gates.push((StandardGate::RZGate, smallvec![phi])); circuit.global_phase += phi / 2.; } }; let fnx = |circuit: &mut OneQubitGateSequence| { - circuit.gates.push((String::from("sx"), SmallVec::new())); + circuit.gates.push((StandardGate::SXGate, SmallVec::new())); }; let fnxpi = |circuit: &mut OneQubitGateSequence| { - circuit.gates.push((String::from("x"), SmallVec::new())); + circuit.gates.push((StandardGate::XGate, SmallVec::new())); }; circuit_psx_gen( theta, @@ -633,7 +676,7 @@ fn compare_error_fn( let fidelity_product: f64 = circuit .gates .iter() - .map(|x| 1. - err_map.get(&x.0).unwrap_or(&0.)) + .map(|gate| 1. - err_map.get(gate.0.name()).unwrap_or(&0.)) .product(); (1. - fidelity_product, circuit.gates.len()) } @@ -642,6 +685,28 @@ fn compare_error_fn( } fn compute_error( + gates: &[(StandardGate, SmallVec<[f64; 3]>)], + error_map: Option<&OneQubitGateErrorMap>, + qubit: usize, +) -> (f64, usize) { + match error_map { + Some(err_map) => { + let num_gates = gates.len(); + let gate_fidelities: f64 = gates + .iter() + .map(|gate| 1. - err_map.error_map[qubit].get(gate.0.name()).unwrap_or(&0.)) + .product(); + (1. - gate_fidelities, num_gates) + } + None => (gates.len() as f64, gates.len()), + } +} + +fn compute_error_term(gate: &str, error_map: &OneQubitGateErrorMap, qubit: usize) -> f64 { + 1. - error_map.error_map[qubit].get(gate).unwrap_or(&0.) +} + +fn compute_error_str( gates: &[(String, SmallVec<[f64; 3]>)], error_map: Option<&OneQubitGateErrorMap>, qubit: usize, @@ -651,7 +716,7 @@ fn compute_error( let num_gates = gates.len(); let gate_fidelities: f64 = gates .iter() - .map(|x| 1. - err_map.error_map[qubit].get(&x.0).unwrap_or(&0.)) + .map(|gate| compute_error_term(gate.0.as_str(), err_map, qubit)) .product(); (1. - gate_fidelities, num_gates) } @@ -670,11 +735,20 @@ pub fn compute_error_one_qubit_sequence( #[pyfunction] pub fn compute_error_list( - circuit: Vec<(String, SmallVec<[f64; 3]>)>, + circuit: Vec>, qubit: usize, error_map: Option<&OneQubitGateErrorMap>, ) -> (f64, usize) { - compute_error(&circuit, error_map, qubit) + let circuit_list: Vec<(String, SmallVec<[f64; 3]>)> = circuit + .iter() + .map(|node| { + ( + node.instruction.operation.name().to_string(), + smallvec![], // Params not needed in this path + ) + }) + .collect(); + compute_error_str(&circuit_list, error_map, qubit) } #[pyfunction] @@ -687,15 +761,13 @@ pub fn unitary_to_gate_sequence( simplify: bool, atol: Option, ) -> PyResult> { - let mut target_basis_vec: Vec = Vec::with_capacity(target_basis_list.len()); - for basis in target_basis_list { - let basis_enum = EulerBasis::__new__(basis.deref())?; - target_basis_vec.push(basis_enum) - } - let unitary_mat = unitary.as_array(); + let target_basis_vec: PyResult> = target_basis_list + .iter() + .map(|basis| EulerBasis::__new__(basis)) + .collect(); Ok(unitary_to_gate_sequence_inner( - unitary_mat, - &target_basis_vec, + unitary.as_array(), + &target_basis_vec?, qubit, error_map, simplify, @@ -725,6 +797,46 @@ pub fn unitary_to_gate_sequence_inner( }) } +#[pyfunction] +#[pyo3(signature = (unitary, target_basis_list, qubit, error_map=None, simplify=true, atol=None))] +pub fn unitary_to_circuit( + py: Python, + unitary: PyReadonlyArray2, + target_basis_list: Vec, + qubit: usize, + error_map: Option<&OneQubitGateErrorMap>, + simplify: bool, + atol: Option, +) -> PyResult> { + let target_basis_vec: PyResult> = target_basis_list + .iter() + .map(|basis| EulerBasis::__new__(basis)) + .collect(); + let circuit_sequence = unitary_to_gate_sequence_inner( + unitary.as_array(), + &target_basis_vec?, + qubit, + error_map, + simplify, + atol, + ); + Ok(circuit_sequence.map(|seq| { + CircuitData::from_standard_gates( + py, + 1, + seq.gates.into_iter().map(|(gate, params)| { + ( + gate, + params.into_iter().map(Param::Float).collect(), + smallvec![Qubit(0)], + ) + }), + Param::Float(seq.global_phase), + ) + .expect("Unexpected Qiskit python bug") + })) +} + #[inline] pub fn det_one_qubit(mat: ArrayView2) -> Complex64 { mat[[0, 0]] * mat[[1, 1]] - mat[[0, 1]] * mat[[1, 0]] @@ -853,6 +965,106 @@ pub fn params_zxz(unitary: PyReadonlyArray2) -> [f64; 4] { params_zxz_inner(mat) } +type OptimizeDecompositionReturn = Option<((f64, usize), (f64, usize), OneQubitGateSequence)>; + +#[pyfunction] +pub fn optimize_1q_gates_decomposition( + runs: Vec>>, + qubits: Vec, + bases: Vec>, + simplify: bool, + error_map: Option<&OneQubitGateErrorMap>, + atol: Option, +) -> Vec { + runs.iter() + .enumerate() + .map(|(index, raw_run)| -> OptimizeDecompositionReturn { + let mut error = match error_map { + Some(_) => 1., + None => raw_run.len() as f64, + }; + let qubit = qubits[index]; + let operator = &raw_run + .iter() + .map(|node| { + if let Some(err_map) = error_map { + error *= + compute_error_term(node.instruction.operation.name(), err_map, qubit) + } + node.instruction + .operation + .matrix(&node.instruction.params) + .expect("No matrix defined for operation") + }) + .fold( + [ + [Complex64::new(1., 0.), Complex64::new(0., 0.)], + [Complex64::new(0., 0.), Complex64::new(1., 0.)], + ], + |mut operator, node| { + matmul_1q(&mut operator, node); + operator + }, + ); + let old_error = if error_map.is_some() { + (1. - error, raw_run.len()) + } else { + (error, raw_run.len()) + }; + let target_basis_vec: Vec = bases[index] + .iter() + .map(|basis| EulerBasis::__new__(basis).unwrap()) + .collect(); + unitary_to_gate_sequence_inner( + aview2(operator), + &target_basis_vec, + qubit, + error_map, + simplify, + atol, + ) + .map(|out_seq| { + let new_error = compute_error_one_qubit_sequence(&out_seq, qubit, error_map); + (old_error, new_error, out_seq) + }) + }) + .collect() +} + +fn matmul_1q(operator: &mut [[Complex64; 2]; 2], other: Array2) { + *operator = [ + [ + other[[0, 0]] * operator[0][0] + other[[0, 1]] * operator[1][0], + other[[0, 0]] * operator[0][1] + other[[0, 1]] * operator[1][1], + ], + [ + other[[1, 0]] * operator[0][0] + other[[1, 1]] * operator[1][0], + other[[1, 0]] * operator[0][1] + other[[1, 1]] * operator[1][1], + ], + ]; +} + +#[pyfunction] +pub fn collect_1q_runs_filter(py: Python, node: PyObject) -> bool { + let op_node = node.extract::>(py); + match op_node { + Ok(node) => { + node.instruction.operation.num_qubits() == 1 + && node.instruction.operation.num_clbits() == 0 + && node + .instruction + .operation + .matrix(&node.instruction.params) + .is_some() + && match &node.instruction.extra_attrs { + None => true, + Some(attrs) => attrs.condition.is_none(), + } + } + Err(_) => false, + } +} + #[pymodule] pub fn euler_one_qubit_decomposer(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(params_zyz))?; @@ -863,8 +1075,11 @@ pub fn euler_one_qubit_decomposer(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(params_u1x))?; m.add_wrapped(wrap_pyfunction!(generate_circuit))?; m.add_wrapped(wrap_pyfunction!(unitary_to_gate_sequence))?; + m.add_wrapped(wrap_pyfunction!(unitary_to_circuit))?; m.add_wrapped(wrap_pyfunction!(compute_error_one_qubit_sequence))?; m.add_wrapped(wrap_pyfunction!(compute_error_list))?; + m.add_wrapped(wrap_pyfunction!(optimize_1q_gates_decomposition))?; + m.add_wrapped(wrap_pyfunction!(collect_1q_runs_filter))?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index 37061d5159f4..568206925c2b 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -52,6 +52,7 @@ 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::operations::Operation; use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex}; use qiskit_circuit::util::{c64, GateArray1Q, GateArray2Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM}; @@ -1045,7 +1046,7 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c2r.gates { - gate_sequence.push((gate.0, gate.1, smallvec![0])) + gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![0])) } global_phase += c2r.global_phase; let c2l = unitary_to_gate_sequence_inner( @@ -1058,7 +1059,7 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c2l.gates { - gate_sequence.push((gate.0, gate.1, smallvec![1])) + gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![1])) } global_phase += c2l.global_phase; self.weyl_gate( @@ -1077,7 +1078,7 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c1r.gates { - gate_sequence.push((gate.0, gate.1, smallvec![0])) + gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![0])) } global_phase += c2r.global_phase; let c1l = unitary_to_gate_sequence_inner( @@ -1090,7 +1091,7 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c1l.gates { - gate_sequence.push((gate.0, gate.1, smallvec![1])) + gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![1])) } Ok(TwoQubitGateSequence { gates: gate_sequence, @@ -1459,7 +1460,7 @@ impl TwoQubitBasisDecomposer { if let Some(sequence) = sequence { *global_phase += sequence.global_phase; for gate in sequence.gates { - gates.push((gate.0, gate.1, smallvec![qubit])); + gates.push((gate.0.name().to_string(), gate.1, smallvec![qubit])); } } } @@ -1847,13 +1848,13 @@ impl TwoQubitBasisDecomposer { for i in 0..best_nbasis as usize { if let Some(euler_decomp) = &euler_decompositions[2 * i] { for gate in &euler_decomp.gates { - gates.push((gate.0.clone(), gate.1.clone(), smallvec![0])); + gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![0])); } global_phase += euler_decomp.global_phase } if let Some(euler_decomp) = &euler_decompositions[2 * i + 1] { for gate in &euler_decomp.gates { - gates.push((gate.0.clone(), gate.1.clone(), smallvec![1])); + gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![1])); } global_phase += euler_decomp.global_phase } @@ -1861,13 +1862,13 @@ impl TwoQubitBasisDecomposer { } if let Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize] { for gate in &euler_decomp.gates { - gates.push((gate.0.clone(), gate.1.clone(), smallvec![0])); + gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![0])); } global_phase += euler_decomp.global_phase } if let Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize + 1] { for gate in &euler_decomp.gates { - gates.push((gate.0.clone(), gate.1.clone(), smallvec![1])); + gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![1])); } global_phase += euler_decomp.global_phase } diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index ffd7920a36fd..44ba5f7a6bf0 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -18,7 +18,7 @@ use crate::operations::Operation; use numpy::IntoPyArray; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PySequence, PyString, PyTuple}; -use pyo3::{intern, IntoPy, PyObject, PyResult}; +use pyo3::{intern, IntoPy, PyObject, PyResult, ToPyObject}; use smallvec::smallvec; /// Parent class for DAGOpNode, DAGInNode, and DAGOutNode. @@ -135,6 +135,50 @@ impl DAGOpNode { )) } + #[staticmethod] + fn from_instruction( + py: Python, + instruction: CircuitInstruction, + dag: Option<&Bound>, + ) -> PyResult { + let qargs = instruction.qubits.clone_ref(py).into_bound(py); + let cargs = instruction.clbits.clone_ref(py).into_bound(py); + + let sort_key = match dag { + Some(dag) => { + let cache = dag + .getattr(intern!(py, "_key_cache"))? + .downcast_into_exact::()?; + let cache_key = PyTuple::new_bound(py, [&qargs, &cargs]); + match cache.get_item(&cache_key)? { + Some(key) => key, + None => { + let indices: PyResult> = qargs + .iter() + .chain(cargs.iter()) + .map(|bit| { + dag.call_method1(intern!(py, "find_bit"), (bit,))? + .getattr(intern!(py, "index")) + }) + .collect(); + let index_strs: Vec<_> = + indices?.into_iter().map(|i| format!("{:04}", i)).collect(); + let key = PyString::new_bound(py, index_strs.join(",").as_str()); + cache.set_item(&cache_key, &key)?; + key.into_any() + } + } + } + None => qargs.str()?.into_any(), + }; + let base = PyClassInitializer::from(DAGNode { _node_id: -1 }); + let sub = base.add_subclass(DAGOpNode { + instruction, + sort_key: sort_key.unbind(), + }); + Ok(Py::new(py, sub)?.to_object(py)) + } + fn __reduce__(slf: PyRef, py: Python) -> PyResult { let state = (slf.as_ref()._node_id, &slf.sort_key); Ok(( @@ -206,8 +250,8 @@ impl DAGOpNode { /// Returns the Instruction name corresponding to the op for this node #[getter] - fn get_name(&self, py: Python) -> PyObject { - self.instruction.operation.name().to_object(py) + fn get_name(&self) -> &str { + self.instruction.operation.name() } #[getter] diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 8935e72e0ad5..77458e2fc35f 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -242,6 +242,12 @@ pub enum StandardGate { RZXGate = 52, } +impl ToPyObject for StandardGate { + fn to_object(&self, py: Python) -> PyObject { + self.into_py(py) + } +} + // TODO: replace all 34s (placeholders) with actual number static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, // 0-9 diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index a0d2c42be17e..b93a90e47f7b 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -54,6 +54,7 @@ from qiskit.dagcircuit.dagnode import DAGNode, DAGOpNode, DAGInNode, DAGOutNode from qiskit.circuit.bit import Bit from qiskit.pulse import Schedule +from qiskit._accelerate.euler_one_qubit_decomposer import collect_1q_runs_filter BitLocations = namedtuple("BitLocations", ("index", "registers")) # The allowable arguments to :meth:`DAGCircuit.copy_empty_like`'s ``vars_mode``. @@ -642,17 +643,17 @@ def _check_wires(self, args: Iterable[Bit | expr.Var], amap: dict[Bit | expr.Var if wire not in amap: raise DAGCircuitError(f"wire {wire} not found in {amap}") - def _increment_op(self, op): - if op.name in self._op_names: - self._op_names[op.name] += 1 + def _increment_op(self, op_name): + if op_name in self._op_names: + self._op_names[op_name] += 1 else: - self._op_names[op.name] = 1 + self._op_names[op_name] = 1 - def _decrement_op(self, op): - if self._op_names[op.name] == 1: - del self._op_names[op.name] + def _decrement_op(self, op_name): + if self._op_names[op_name] == 1: + del self._op_names[op_name] else: - self._op_names[op.name] -= 1 + self._op_names[op_name] -= 1 def copy_empty_like(self, *, vars_mode: _VarsMode = "alike"): """Return a copy of self with the same structure but empty. @@ -724,7 +725,7 @@ def _apply_op_node_back(self, node: DAGOpNode): additional = set(_additional_wires(node)).difference(node.cargs) node._node_id = self._multi_graph.add_node(node) - self._increment_op(node) + self._increment_op(node.name) # Add new in-edges from predecessors of the output nodes to the # operation node while deleting the old in-edges of the output nodes @@ -780,7 +781,7 @@ def apply_operation_back( node = DAGOpNode(op=op, qargs=qargs, cargs=cargs, dag=self) node._node_id = self._multi_graph.add_node(node) - self._increment_op(op) + self._increment_op(op.name) # Add new in-edges from predecessors of the output nodes to the # operation node while deleting the old in-edges of the output nodes @@ -832,7 +833,7 @@ def apply_operation_front( node = DAGOpNode(op=op, qargs=qargs, cargs=cargs, dag=self) node._node_id = self._multi_graph.add_node(node) - self._increment_op(op) + self._increment_op(op.name) # Add new out-edges to successors of the input nodes from the # operation node while deleting the old out-edges of the input nodes @@ -1379,10 +1380,10 @@ def replace_block_with_op( "Replacing the specified node block would introduce a cycle" ) from ex - self._increment_op(op) + self._increment_op(op.name) for nd in node_block: - self._decrement_op(nd.op) + self._decrement_op(nd.name) return new_node @@ -1593,7 +1594,7 @@ def edge_weight_map(wire): node_map = self._multi_graph.substitute_node_with_subgraph( node._node_id, in_dag._multi_graph, edge_map_fn, filter_fn, edge_weight_map ) - self._decrement_op(node.op) + self._decrement_op(node.name) variable_mapper = _classical_resource_map.VariableMapper( self.cregs.values(), wire_map, add_register=self.add_creg @@ -1624,7 +1625,7 @@ def edge_weight_map(wire): new_node = DAGOpNode(m_op, qargs=m_qargs, cargs=m_cargs, dag=self) new_node._node_id = new_node_index self._multi_graph[new_node_index] = new_node - self._increment_op(new_node.op) + self._increment_op(new_node.name) return {k: self._multi_graph[v] for k, v in node_map.items()} @@ -1696,17 +1697,17 @@ def substitute_node(self, node: DAGOpNode, op, inplace: bool = False, propagate_ if inplace: if op.name != node.op.name: - self._increment_op(op) - self._decrement_op(node.op) + self._increment_op(op.name) + self._decrement_op(node.name) node.op = op return node new_node = copy.copy(node) new_node.op = op self._multi_graph[node._node_id] = new_node - if op.name != node.op.name: - self._increment_op(op) - self._decrement_op(node.op) + if op.name != node.name: + self._increment_op(op.name) + self._decrement_op(node.name) return new_node def separable_circuits( @@ -1987,7 +1988,7 @@ def remove_op_node(self, node): self._multi_graph.remove_node_retain_edges( node._node_id, use_outgoing=False, condition=lambda edge1, edge2: edge1 == edge2 ) - self._decrement_op(node.op) + self._decrement_op(node.name) def remove_ancestors_of(self, node): """Remove all of the ancestor operation nodes of node.""" @@ -2152,19 +2153,7 @@ def filter_fn(node): def collect_1q_runs(self) -> list[list[DAGOpNode]]: """Return a set of non-conditional runs of 1q "op" nodes.""" - - def filter_fn(node): - return ( - isinstance(node, DAGOpNode) - and len(node.qargs) == 1 - and len(node.cargs) == 0 - and isinstance(node.op, Gate) - and hasattr(node.op, "__array__") - and getattr(node.op, "condition", None) is None - and not node.op.is_parameterized() - ) - - return rx.collect_runs(self._multi_graph, filter_fn) + return rx.collect_runs(self._multi_graph, collect_1q_runs_filter) def collect_2q_runs(self): """Return a set of non-conditional runs of 2q "op" nodes.""" diff --git a/qiskit/synthesis/one_qubit/one_qubit_decompose.py b/qiskit/synthesis/one_qubit/one_qubit_decompose.py index c84db761b7f0..f60f20f9524e 100644 --- a/qiskit/synthesis/one_qubit/one_qubit_decompose.py +++ b/qiskit/synthesis/one_qubit/one_qubit_decompose.py @@ -161,29 +161,19 @@ def build_circuit(self, gates, global_phase) -> QuantumCircuit | DAGCircuit: if len(gates) > 0 and isinstance(gates[0], tuple): lookup_gate = True - if self.use_dag: - from qiskit.dagcircuit import dagcircuit - - dag = dagcircuit.DAGCircuit() - dag.global_phase = global_phase - dag.add_qubits(qr) - for gate_entry in gates: - if lookup_gate: - gate = NAME_MAP[gate_entry[0]](*gate_entry[1]) - else: - gate = gate_entry - - dag.apply_operation_back(gate, (qr[0],), check=False) - return dag - else: - circuit = QuantumCircuit(qr, global_phase=global_phase) - for gate_entry in gates: - if lookup_gate: - gate = NAME_MAP[gate_entry[0]](*gate_entry[1]) - else: - gate = gate_entry - circuit._append(gate, [qr[0]], []) - return circuit + from qiskit.dagcircuit import dagcircuit + + dag = dagcircuit.DAGCircuit() + dag.global_phase = global_phase + dag.add_qubits(qr) + for gate_entry in gates: + if lookup_gate: + gate = NAME_MAP[gate_entry[0].name](*gate_entry[1]) + else: + gate = gate_entry.name + + dag.apply_operation_back(gate, (qr[0],), check=False) + return dag def __call__( self, @@ -225,11 +215,17 @@ def __call__( return self._decompose(unitary, simplify=simplify, atol=atol) def _decompose(self, unitary, simplify=True, atol=DEFAULT_ATOL): - circuit_sequence = euler_one_qubit_decomposer.unitary_to_gate_sequence( - unitary, [self.basis], 0, None, simplify, atol + if self.use_dag: + circuit_sequence = euler_one_qubit_decomposer.unitary_to_gate_sequence( + unitary, [self.basis], 0, None, simplify, atol + ) + circuit = self.build_circuit(circuit_sequence, circuit_sequence.global_phase) + return circuit + return QuantumCircuit._from_circuit_data( + euler_one_qubit_decomposer.unitary_to_circuit( + unitary, [self.basis], 0, None, simplify, atol + ) ) - circuit = self.build_circuit(circuit_sequence, circuit_sequence.global_phase) - return circuit @property def basis(self): diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 3f8d07839c0f..04d95312aa6d 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -33,6 +33,7 @@ XGate, ) from qiskit.circuit import Qubit +from qiskit.circuit.quantumcircuitdata import CircuitInstruction from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.dagcircuit.dagnode import DAGOpNode @@ -110,16 +111,7 @@ def _build_error_map(self): else: return None - def _resynthesize_run(self, matrix, qubit=None): - """ - Re-synthesizes one 2x2 `matrix`, typically extracted via `dag.collect_1q_runs`. - - Returns the newly synthesized circuit in the indicated basis, or None - if no synthesis routine applied. - - When multiple synthesis options are available, it prefers the one with the lowest - error when the circuit is applied to `qubit`. - """ + def _get_decomposer(self, qubit=None): # include path for when target exists but target.num_qubits is None (BasicSimulator) if self._target is not None and self._target.num_qubits is not None: if qubit is not None: @@ -133,6 +125,19 @@ def _resynthesize_run(self, matrix, qubit=None): decomposers = _possible_decomposers(available_1q_basis) else: decomposers = self._global_decomposers + return decomposers + + def _resynthesize_run(self, matrix, qubit=None): + """ + Re-synthesizes one 2x2 `matrix`, typically extracted via `dag.collect_1q_runs`. + + Returns the newly synthesized circuit in the indicated basis, or None + if no synthesis routine applied. + + When multiple synthesis options are available, it prefers the one with the lowest + error when the circuit is applied to `qubit`. + """ + decomposers = self._get_decomposer(qubit) best_synth_circuit = euler_one_qubit_decomposer.unitary_to_gate_sequence( matrix, @@ -149,10 +154,13 @@ def _gate_sequence_to_dag(self, best_synth_circuit): out_dag.global_phase = best_synth_circuit.global_phase for gate_name, angles in best_synth_circuit: - out_dag.apply_operation_back(NAME_MAP[gate_name](*angles), qubits, check=False) + op = CircuitInstruction(gate_name, qubits=qubits, params=angles) + out_dag.apply_operation_back(op.operation, qubits, check=False) return out_dag - def _substitution_checks(self, dag, old_run, new_circ, basis, qubit): + def _substitution_checks( + self, dag, old_run, new_circ, basis, qubit, old_error=None, new_error=None + ): """ Returns `True` when it is recommended to replace `old_run` with `new_circ` over `basis`. """ @@ -176,11 +184,14 @@ def _substitution_checks(self, dag, old_run, new_circ, basis, qubit): # if we're outside of the basis set, we're obligated to logically decompose. # if we're outside of the set of gates for which we have physical definitions, # then we _try_ to decompose, using the results if we see improvement. - new_error = 0.0 - old_error = 0.0 if not uncalibrated_and_not_basis_p: - new_error = self._error(new_circ, qubit) - old_error = self._error(old_run, qubit) + if new_error is None: + new_error = self._error(new_circ, qubit) + if old_error is None: + old_error = self._error(old_run, qubit) + else: + new_error = 0.0 + old_error = 0.0 return ( uncalibrated_and_not_basis_p @@ -198,32 +209,47 @@ def run(self, dag): Returns: DAGCircuit: the optimized DAG. """ - runs = dag.collect_1q_runs() - for run in runs: + runs = [] + qubits = [] + bases = [] + for run in dag.collect_1q_runs(): qubit = dag.find_bit(run[0].qargs[0]).index - operator = run[0].op.to_matrix() - for node in run[1:]: - operator = node.op.to_matrix().dot(operator) - best_circuit_sequence = self._resynthesize_run(operator, qubit) - + runs.append(run) + qubits.append(qubit) + bases.append(self._get_decomposer(qubit)) + best_sequences = euler_one_qubit_decomposer.optimize_1q_gates_decomposition( + runs, qubits, bases, simplify=True, error_map=self.error_map + ) + for index, best_circuit_sequence in enumerate(best_sequences): + run = runs[index] + qubit = qubits[index] if self._target is None: basis = self._basis_gates else: basis = self._target.operation_names_for_qargs((qubit,)) - - if best_circuit_sequence is not None and self._substitution_checks( - dag, run, best_circuit_sequence, basis, qubit - ): - for gate_name, angles in best_circuit_sequence: - op = NAME_MAP[gate_name](*angles) - node = DAGOpNode(NAME_MAP[gate_name](*angles), run[0].qargs, dag=dag) - node._node_id = dag._multi_graph.add_node(node) - dag._increment_op(op) - dag._multi_graph.insert_node_on_in_edges(node._node_id, run[0]._node_id) - dag.global_phase += best_circuit_sequence.global_phase - # Delete the other nodes in the run - for current_node in run: - dag.remove_op_node(current_node) + if best_circuit_sequence is not None: + (old_error, new_error, best_circuit_sequence) = best_circuit_sequence + if self._substitution_checks( + dag, + run, + best_circuit_sequence, + basis, + qubit, + old_error=old_error, + new_error=new_error, + ): + first_node_id = run[0]._node_id + qubit = run[0].qargs + for gate, angles in best_circuit_sequence: + op = CircuitInstruction(gate, qubits=qubit, params=angles) + node = DAGOpNode.from_instruction(op, dag=dag) + node._node_id = dag._multi_graph.add_node(node) + dag._increment_op(gate.name) + dag._multi_graph.insert_node_on_in_edges(node._node_id, first_node_id) + dag.global_phase += best_circuit_sequence.global_phase + # Delete the other nodes in the run + for current_node in run: + dag.remove_op_node(current_node) return dag @@ -241,10 +267,7 @@ def _error(self, circuit, qubit): circuit, qubit, self.error_map ) else: - circuit_list = [(x.op.name, []) for x in circuit] - return euler_one_qubit_decomposer.compute_error_list( - circuit_list, qubit, self.error_map - ) + return euler_one_qubit_decomposer.compute_error_list(circuit, qubit, self.error_map) def _possible_decomposers(basis_set): From 0f585bd75d3a06445960cc4d19439e6b931094cc Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 3 Jul 2024 09:42:41 +0300 Subject: [PATCH 49/89] Port `synth_clifford_greedy` to Rust (#12601) * starting to experiment * porting code * messy code porting * printing statements to enable debugging * fixes * fixing phase * removing some of the printing statements * fixing inaccuracy for cost computation * Moving some of the functionality to SymplecticMatrix class * reducing the number of warnings * formatting * replacing expensive adjoint and compose operations for symplectic matrices by significantly cheaper in-place prepend operations * resolving merge conflicts * cleanup * code cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * using fast lookup * cleanup * clippy * including params in gate_seq to avoid mapping * removing unnecessary inner function * cleanup * renaming * changes on the python side * reno * adding error handling * improved error handling * removing redundant Ok(Some(...)) * using random_clifford in tests * reorganizing clifford code * fixes * formatting * improved error handling * do not panic * formatting * Applying refactoring suggestions d/utils.rs from code review * release notes update * adding comment --- .../synthesis/clifford/greedy_synthesis.rs | 441 ++++++++++++++++++ .../accelerate/src/synthesis/clifford/mod.rs | 48 ++ .../src/synthesis/clifford/utils.rs | 289 ++++++++++++ crates/accelerate/src/synthesis/linear/mod.rs | 2 +- crates/accelerate/src/synthesis/mod.rs | 6 +- qiskit/__init__.py | 1 + .../clifford/clifford_decompose_greedy.py | 312 +------------ ...ynth-clifford-greedy-0739e9688bc4eedd.yaml | 6 + .../synthesis/test_clifford_sythesis.py | 4 +- 9 files changed, 801 insertions(+), 308 deletions(-) create mode 100644 crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs create mode 100644 crates/accelerate/src/synthesis/clifford/mod.rs create mode 100644 crates/accelerate/src/synthesis/clifford/utils.rs create mode 100644 releasenotes/notes/oxidize-synth-clifford-greedy-0739e9688bc4eedd.yaml diff --git a/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs b/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs new file mode 100644 index 000000000000..e53e25282005 --- /dev/null +++ b/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs @@ -0,0 +1,441 @@ +// 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 indexmap::IndexSet; +use ndarray::{s, ArrayView2}; +use smallvec::smallvec; + +use crate::synthesis::clifford::utils::CliffordGatesVec; +use crate::synthesis::clifford::utils::{adjust_final_pauli_gates, SymplecticMatrix}; +use qiskit_circuit::operations::StandardGate; +use qiskit_circuit::Qubit; + +/// Converts a pair of Paulis pauli_x and pauli_z acting on a specific qubit +/// to the corresponding index in [PauliPairsClass] or [SingleQubitGate] classes. +/// The input is given as a 4-tuple: (pauli_x stabilizer, pauli_x destabilizer, +/// pauli_z stabilizer, pauli_z destabilizer), and the output is an unsigned +/// integer from 0 to 15. +fn pauli_pair_to_index(xs: bool, xd: bool, zs: bool, zd: bool) -> usize { + ((xs as usize) << 3) | ((xd as usize) << 2) | ((zs as usize) << 1) | (zd as usize) +} + +/// The five classes of Pauli 2-qubit operators as described in the paper. +#[derive(Clone, Copy)] +enum PauliPairsClass { + ClassA, + ClassB, + ClassC, + ClassD, + ClassE, +} + +/// The 16 Pauli 2-qubit operators are divided into 5 equivalence classes +/// under the action of single-qubit Cliffords. +static PAULI_INDEX_TO_CLASS: [PauliPairsClass; 16] = [ + PauliPairsClass::ClassE, // 'II' + PauliPairsClass::ClassD, // 'IX' + PauliPairsClass::ClassD, // 'IZ' + PauliPairsClass::ClassD, // 'IY' + PauliPairsClass::ClassC, // 'XI' + PauliPairsClass::ClassB, // 'XX' + PauliPairsClass::ClassA, // 'XZ' + PauliPairsClass::ClassA, // 'XY' + PauliPairsClass::ClassC, // 'ZI' + PauliPairsClass::ClassA, // 'ZX' + PauliPairsClass::ClassB, // 'ZZ' + PauliPairsClass::ClassA, // 'ZY' + PauliPairsClass::ClassC, // 'YI' + PauliPairsClass::ClassA, // 'YX' + PauliPairsClass::ClassA, // 'YZ' + PauliPairsClass::ClassB, // 'YY' +]; + +/// Single-qubit Clifford gates modulo Paulis. +#[derive(Clone, Copy)] +enum SingleQubitGate { + GateI, + GateS, + GateH, + GateSH, + GateHS, + GateSHS, +} + +/// Maps pair of pauli operators to the single-qubit gate required +/// for the decoupling step. +static PAULI_INDEX_TO_1Q_GATE: [SingleQubitGate; 16] = [ + SingleQubitGate::GateI, // 'II' + SingleQubitGate::GateH, // 'IX' + SingleQubitGate::GateI, // 'IZ' + SingleQubitGate::GateSH, // 'IY' + SingleQubitGate::GateI, // 'XI' + SingleQubitGate::GateI, // 'XX' + SingleQubitGate::GateI, // 'XZ' + SingleQubitGate::GateSHS, // 'XY' + SingleQubitGate::GateH, // 'ZI' + SingleQubitGate::GateH, // 'ZX' + SingleQubitGate::GateH, // 'ZZ' + SingleQubitGate::GateSH, // 'ZY' + SingleQubitGate::GateS, // 'YI' + SingleQubitGate::GateHS, // 'YX' + SingleQubitGate::GateS, // 'YZ' + SingleQubitGate::GateS, // 'YY' +]; + +pub struct GreedyCliffordSynthesis<'a> { + /// The Clifford tableau to be synthesized. + tableau: ArrayView2<'a, bool>, + + /// The total number of qubits. + num_qubits: usize, + + /// Symplectic matrix being reduced. + symplectic_matrix: SymplecticMatrix, + + /// Unprocessed qubits. + unprocessed_qubits: IndexSet, +} + +impl GreedyCliffordSynthesis<'_> { + pub(crate) fn new(tableau: ArrayView2) -> Result, String> { + let tableau_shape = tableau.shape(); + if (tableau_shape[0] % 2 == 1) || (tableau_shape[1] != tableau_shape[0] + 1) { + return Err("The shape of the Clifford tableau is invalid".to_string()); + } + + let num_qubits = tableau_shape[0] / 2; + + // We are going to modify symplectic_matrix in-place until it + // becomes the identity. + let symplectic_matrix = SymplecticMatrix { + num_qubits, + smat: tableau.slice(s![.., 0..2 * num_qubits]).to_owned(), + }; + + let unprocessed_qubits: IndexSet = (0..num_qubits).collect(); + + Ok(GreedyCliffordSynthesis { + tableau, + num_qubits, + symplectic_matrix, + unprocessed_qubits, + }) + } + + /// Computes the CX cost of decoupling the symplectic matrix on the + /// given qubit. + fn compute_cost(&self, qubit: usize) -> Result { + let mut a_num = 0; + let mut b_num = 0; + let mut c_num = 0; + let mut d_num = 0; + + let mut qubit_is_in_a = false; + + for q in &self.unprocessed_qubits { + let pauli_pair_index = pauli_pair_to_index( + self.symplectic_matrix.smat[[*q, qubit + self.num_qubits]], + self.symplectic_matrix.smat[[*q + self.num_qubits, qubit + self.num_qubits]], + self.symplectic_matrix.smat[[*q, qubit]], + self.symplectic_matrix.smat[[*q + self.num_qubits, qubit]], + ); + let pauli_class = PAULI_INDEX_TO_CLASS[pauli_pair_index]; + + match pauli_class { + PauliPairsClass::ClassA => { + a_num += 1; + if *q == qubit { + qubit_is_in_a = true; + } + } + PauliPairsClass::ClassB => { + b_num += 1; + } + PauliPairsClass::ClassC => { + c_num += 1; + } + PauliPairsClass::ClassD => { + d_num += 1; + } + PauliPairsClass::ClassE => {} + } + } + + if a_num % 2 == 0 { + return Err("Symplectic Gaussian elimination failed.".to_string()); + } + + let mut cnot_cost: usize = + 3 * (a_num - 1) / 2 + (b_num + 1) * ((b_num > 0) as usize) + c_num + d_num; + + if !qubit_is_in_a { + cnot_cost += 3; + } + + Ok(cnot_cost) + } + + /// Calculate a decoupling operator D: + /// D^{-1} * Ox * D = x1 + /// D^{-1} * Oz * D = z1 + /// and reduces the clifford such that it will act trivially on min_qubit. + fn decouple_qubit( + &mut self, + gate_seq: &mut CliffordGatesVec, + min_qubit: usize, + ) -> Result<(), String> { + let mut a_qubits = IndexSet::new(); + let mut b_qubits = IndexSet::new(); + let mut c_qubits = IndexSet::new(); + let mut d_qubits = IndexSet::new(); + + for qubit in &self.unprocessed_qubits { + let pauli_pair_index = pauli_pair_to_index( + self.symplectic_matrix.smat[[*qubit, min_qubit + self.num_qubits]], + self.symplectic_matrix.smat + [[*qubit + self.num_qubits, min_qubit + self.num_qubits]], + self.symplectic_matrix.smat[[*qubit, min_qubit]], + self.symplectic_matrix.smat[[*qubit + self.num_qubits, min_qubit]], + ); + + let single_qubit_gate = PAULI_INDEX_TO_1Q_GATE[pauli_pair_index]; + match single_qubit_gate { + SingleQubitGate::GateS => { + gate_seq.push(( + StandardGate::SGate, + smallvec![], + smallvec![Qubit(*qubit as u32)], + )); + self.symplectic_matrix.prepend_s(*qubit); + } + SingleQubitGate::GateH => { + gate_seq.push(( + StandardGate::HGate, + smallvec![], + smallvec![Qubit(*qubit as u32)], + )); + self.symplectic_matrix.prepend_h(*qubit); + } + SingleQubitGate::GateSH => { + gate_seq.push(( + StandardGate::SGate, + smallvec![], + smallvec![Qubit(*qubit as u32)], + )); + gate_seq.push(( + StandardGate::HGate, + smallvec![], + smallvec![Qubit(*qubit as u32)], + )); + self.symplectic_matrix.prepend_s(*qubit); + self.symplectic_matrix.prepend_h(*qubit); + } + SingleQubitGate::GateHS => { + gate_seq.push(( + StandardGate::HGate, + smallvec![], + smallvec![Qubit(*qubit as u32)], + )); + gate_seq.push(( + StandardGate::SGate, + smallvec![], + smallvec![Qubit(*qubit as u32)], + )); + self.symplectic_matrix.prepend_h(*qubit); + self.symplectic_matrix.prepend_s(*qubit); + } + SingleQubitGate::GateSHS => { + gate_seq.push(( + StandardGate::SGate, + smallvec![], + smallvec![Qubit(*qubit as u32)], + )); + gate_seq.push(( + StandardGate::HGate, + smallvec![], + smallvec![Qubit(*qubit as u32)], + )); + gate_seq.push(( + StandardGate::SGate, + smallvec![], + smallvec![Qubit(*qubit as u32)], + )); + self.symplectic_matrix.prepend_s(*qubit); + self.symplectic_matrix.prepend_h(*qubit); + self.symplectic_matrix.prepend_s(*qubit); + } + SingleQubitGate::GateI => {} + } + + let pauli_class = PAULI_INDEX_TO_CLASS[pauli_pair_index]; + match pauli_class { + PauliPairsClass::ClassA => { + a_qubits.insert(*qubit); + } + PauliPairsClass::ClassB => { + b_qubits.insert(*qubit); + } + PauliPairsClass::ClassC => { + c_qubits.insert(*qubit); + } + PauliPairsClass::ClassD => { + d_qubits.insert(*qubit); + } + PauliPairsClass::ClassE => {} + } + } + + if a_qubits.len() % 2 != 1 { + return Err("Symplectic Gaussian elimination failed.".to_string()); + } + + if !a_qubits.contains(&min_qubit) { + let qubit_a = a_qubits[0]; + gate_seq.push(( + StandardGate::SwapGate, + smallvec![], + smallvec![Qubit(min_qubit as u32), Qubit(qubit_a as u32)], + )); + self.symplectic_matrix.prepend_swap(min_qubit, qubit_a); + + if b_qubits.contains(&min_qubit) { + b_qubits.swap_remove(&min_qubit); + b_qubits.insert(qubit_a); + } else if c_qubits.contains(&min_qubit) { + c_qubits.swap_remove(&min_qubit); + c_qubits.insert(qubit_a); + } else if d_qubits.contains(&min_qubit) { + d_qubits.swap_remove(&min_qubit); + d_qubits.insert(qubit_a); + } + + a_qubits.swap_remove(&qubit_a); + a_qubits.insert(min_qubit); + } + + for qubit in c_qubits { + gate_seq.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit(min_qubit as u32), Qubit(qubit as u32)], + )); + self.symplectic_matrix.prepend_cx(min_qubit, qubit); + } + + for qubit in d_qubits { + gate_seq.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit(qubit as u32), Qubit(min_qubit as u32)], + )); + self.symplectic_matrix.prepend_cx(qubit, min_qubit); + } + + if b_qubits.len() > 1 { + let qubit_b = b_qubits[0]; + for qubit in &b_qubits[1..] { + gate_seq.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit(qubit_b as u32), Qubit(*qubit as u32)], + )); + self.symplectic_matrix.prepend_cx(qubit_b, *qubit); + } + } + + if !b_qubits.is_empty() { + let qubit_b = b_qubits[0]; + gate_seq.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit(min_qubit as u32), Qubit(qubit_b as u32)], + )); + self.symplectic_matrix.prepend_cx(min_qubit, qubit_b); + + gate_seq.push(( + StandardGate::HGate, + smallvec![], + smallvec![Qubit(qubit_b as u32)], + )); + self.symplectic_matrix.prepend_h(qubit_b); + + gate_seq.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit(qubit_b as u32), Qubit(min_qubit as u32)], + )); + self.symplectic_matrix.prepend_cx(qubit_b, min_qubit); + } + + let a_len: usize = (a_qubits.len() - 1) / 2; + if a_len > 0 { + a_qubits.swap_remove(&min_qubit); + } + + for qubit in 0..a_len { + gate_seq.push(( + StandardGate::CXGate, + smallvec![], + smallvec![ + Qubit(a_qubits[2 * qubit + 1] as u32), + Qubit(a_qubits[2 * qubit] as u32) + ], + )); + self.symplectic_matrix + .prepend_cx(a_qubits[2 * qubit + 1], a_qubits[2 * qubit]); + + gate_seq.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit(a_qubits[2 * qubit] as u32), Qubit(min_qubit as u32)], + )); + self.symplectic_matrix + .prepend_cx(a_qubits[2 * qubit], min_qubit); + + gate_seq.push(( + StandardGate::CXGate, + smallvec![], + smallvec![ + Qubit(min_qubit as u32), + Qubit(a_qubits[2 * qubit + 1] as u32) + ], + )); + self.symplectic_matrix + .prepend_cx(min_qubit, a_qubits[2 * qubit + 1]); + } + + Ok(()) + } + + /// The main synthesis function. + pub(crate) fn run(&mut self) -> Result<(usize, CliffordGatesVec), String> { + let mut clifford_gates = CliffordGatesVec::new(); + + while !self.unprocessed_qubits.is_empty() { + let costs: Vec<(usize, usize)> = self + .unprocessed_qubits + .iter() + .map(|q| self.compute_cost(*q).map(|cost| (cost, *q))) + .collect::, _>>()?; + + let min_cost_qubit = costs.iter().min_by_key(|(cost, _)| cost).unwrap().1; + + self.decouple_qubit(&mut clifford_gates, min_cost_qubit)?; + + self.unprocessed_qubits.swap_remove(&min_cost_qubit); + } + + adjust_final_pauli_gates(&mut clifford_gates, self.tableau, self.num_qubits)?; + + Ok((self.num_qubits, clifford_gates)) + } +} diff --git a/crates/accelerate/src/synthesis/clifford/mod.rs b/crates/accelerate/src/synthesis/clifford/mod.rs new file mode 100644 index 000000000000..6772228acf88 --- /dev/null +++ b/crates/accelerate/src/synthesis/clifford/mod.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. + +mod greedy_synthesis; +mod utils; + +use crate::synthesis::clifford::greedy_synthesis::GreedyCliffordSynthesis; +use crate::QiskitError; +use numpy::PyReadonlyArray2; +use pyo3::prelude::*; +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::operations::Param; + +/// Create a circuit that synthesizes a given Clifford operator represented as a tableau. +/// +/// This is an implementation of the "greedy Clifford compiler" presented in +/// Appendix A of the paper "Clifford Circuit Optimization with Templates and Symbolic +/// Pauli Gates" by Bravyi, Shaydulin, Hu, and Maslov (2021), ``__. +/// +/// This method typically yields better CX cost compared to the Aaronson-Gottesman method. +/// +/// Note that this function only implements the greedy Clifford compiler and not the +/// templates and symbolic Pauli gates optimizations that are also described in the paper. +#[pyfunction] +#[pyo3(signature = (clifford))] +fn synth_clifford_greedy(py: Python, clifford: PyReadonlyArray2) -> PyResult { + let tableau = clifford.as_array(); + let mut greedy_synthesis = + GreedyCliffordSynthesis::new(tableau.view()).map_err(QiskitError::new_err)?; + let (num_qubits, clifford_gates) = greedy_synthesis.run().map_err(QiskitError::new_err)?; + + CircuitData::from_standard_gates(py, num_qubits as u32, clifford_gates, Param::Float(0.0)) +} + +#[pymodule] +pub fn clifford(m: &Bound) -> PyResult<()> { + m.add_function(wrap_pyfunction!(synth_clifford_greedy, m)?)?; + Ok(()) +} diff --git a/crates/accelerate/src/synthesis/clifford/utils.rs b/crates/accelerate/src/synthesis/clifford/utils.rs new file mode 100644 index 000000000000..766d84ed179d --- /dev/null +++ b/crates/accelerate/src/synthesis/clifford/utils.rs @@ -0,0 +1,289 @@ +// 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::synthesis::linear::utils::calc_inverse_matrix_inner; +use ndarray::{azip, s, Array1, Array2, ArrayView2}; +use qiskit_circuit::operations::{Param, StandardGate}; +use qiskit_circuit::Qubit; +use smallvec::{smallvec, SmallVec}; + +/// Symplectic matrix. +/// Currently this class is internal to the synthesis library. +pub struct SymplecticMatrix { + /// Number of qubits. + pub num_qubits: usize, + /// Matrix with dimensions (2 * num_qubits) x (2 * num_qubits). + pub smat: Array2, +} + +/// Clifford. +/// Currently this class is internal to the synthesis library and +/// has a very different functionality from Qiskit's python-based +/// Clifford class. +pub struct Clifford { + /// Number of qubits. + pub num_qubits: usize, + /// Matrix with dimensions (2 * num_qubits) x (2 * num_qubits + 1). + pub tableau: Array2, +} + +impl SymplecticMatrix { + /// Modifies the matrix in-place by appending S-gate + #[allow(dead_code)] + pub fn append_s(&mut self, qubit: usize) { + let (x, mut z) = self + .smat + .multi_slice_mut((s![.., qubit], s![.., self.num_qubits + qubit])); + azip!((z in &mut z, &x in &x) *z ^= x); + } + + /// Modifies the matrix in-place by prepending S-gate + pub fn prepend_s(&mut self, qubit: usize) { + let (x, mut z) = self + .smat + .multi_slice_mut((s![self.num_qubits + qubit, ..], s![qubit, ..])); + azip!((z in &mut z, &x in &x) *z ^= x); + } + + /// Modifies the matrix in-place by appending H-gate + #[allow(dead_code)] + pub fn append_h(&mut self, qubit: usize) { + let (mut x, mut z) = self + .smat + .multi_slice_mut((s![.., qubit], s![.., self.num_qubits + qubit])); + azip!((x in &mut x, z in &mut z) (*x, *z) = (*z, *x)); + } + + /// Modifies the matrix in-place by prepending H-gate + pub fn prepend_h(&mut self, qubit: usize) { + let (mut x, mut z) = self + .smat + .multi_slice_mut((s![qubit, ..], s![self.num_qubits + qubit, ..])); + azip!((x in &mut x, z in &mut z) (*x, *z) = (*z, *x)); + } + + /// Modifies the matrix in-place by appending SWAP-gate + #[allow(dead_code)] + pub fn append_swap(&mut self, qubit0: usize, qubit1: usize) { + let (mut x0, mut z0, mut x1, mut z1) = self.smat.multi_slice_mut(( + s![.., qubit0], + s![.., self.num_qubits + qubit0], + s![.., qubit1], + s![.., self.num_qubits + qubit1], + )); + azip!((x0 in &mut x0, x1 in &mut x1) (*x0, *x1) = (*x1, *x0)); + azip!((z0 in &mut z0, z1 in &mut z1) (*z0, *z1) = (*z1, *z0)); + } + + /// Modifies the matrix in-place by prepending SWAP-gate + pub fn prepend_swap(&mut self, qubit0: usize, qubit1: usize) { + let (mut x0, mut z0, mut x1, mut z1) = self.smat.multi_slice_mut(( + s![qubit0, ..], + s![self.num_qubits + qubit0, ..], + s![qubit1, ..], + s![self.num_qubits + qubit1, ..], + )); + azip!((x0 in &mut x0, x1 in &mut x1) (*x0, *x1) = (*x1, *x0)); + azip!((z0 in &mut z0, z1 in &mut z1) (*z0, *z1) = (*z1, *z0)); + } + + /// Modifies the matrix in-place by appending CX-gate + #[allow(dead_code)] + pub fn append_cx(&mut self, qubit0: usize, qubit1: usize) { + let (x0, mut z0, mut x1, z1) = self.smat.multi_slice_mut(( + s![.., qubit0], + s![.., self.num_qubits + qubit0], + s![.., qubit1], + s![.., self.num_qubits + qubit1], + )); + azip!((x1 in &mut x1, &x0 in &x0) *x1 ^= x0); + azip!((z0 in &mut z0, &z1 in &z1) *z0 ^= z1); + } + + /// Modifies the matrix in-place by prepending CX-gate + pub fn prepend_cx(&mut self, qubit0: usize, qubit1: usize) { + let (x0, mut z0, mut x1, z1) = self.smat.multi_slice_mut(( + s![qubit1, ..], + s![self.num_qubits + qubit1, ..], + s![qubit0, ..], + s![self.num_qubits + qubit0, ..], + )); + azip!((x1 in &mut x1, &x0 in &x0) *x1 ^= x0); + azip!((z0 in &mut z0, &z1 in &z1) *z0 ^= z1); + } +} + +impl Clifford { + /// Modifies the tableau in-place by appending S-gate + pub fn append_s(&mut self, qubit: usize) { + let (x, mut z, mut p) = self.tableau.multi_slice_mut(( + s![.., qubit], + s![.., self.num_qubits + qubit], + s![.., 2 * self.num_qubits], + )); + + azip!((p in &mut p, &x in &x, &z in &z) *p ^= x & z); + azip!((z in &mut z, &x in &x) *z ^= x); + } + + /// Modifies the tableau in-place by appending Sdg-gate + #[allow(dead_code)] + pub fn append_sdg(&mut self, qubit: usize) { + let (x, mut z, mut p) = self.tableau.multi_slice_mut(( + s![.., qubit], + s![.., self.num_qubits + qubit], + s![.., 2 * self.num_qubits], + )); + + azip!((p in &mut p, &x in &x, &z in &z) *p ^= x & !z); + azip!((z in &mut z, &x in &x) *z ^= x); + } + + /// Modifies the tableau in-place by appending H-gate + pub fn append_h(&mut self, qubit: usize) { + let (mut x, mut z, mut p) = self.tableau.multi_slice_mut(( + s![.., qubit], + s![.., self.num_qubits + qubit], + s![.., 2 * self.num_qubits], + )); + + azip!((p in &mut p, &x in &x, &z in &z) *p ^= x & z); + azip!((x in &mut x, z in &mut z) (*x, *z) = (*z, *x)); + } + + /// Modifies the tableau in-place by appending SWAP-gate + pub fn append_swap(&mut self, qubit0: usize, qubit1: usize) { + let (mut x0, mut z0, mut x1, mut z1) = self.tableau.multi_slice_mut(( + s![.., qubit0], + s![.., self.num_qubits + qubit0], + s![.., qubit1], + s![.., self.num_qubits + qubit1], + )); + azip!((x0 in &mut x0, x1 in &mut x1) (*x0, *x1) = (*x1, *x0)); + azip!((z0 in &mut z0, z1 in &mut z1) (*z0, *z1) = (*z1, *z0)); + } + + /// Modifies the tableau in-place by appending CX-gate + pub fn append_cx(&mut self, qubit0: usize, qubit1: usize) { + let (x0, mut z0, mut x1, z1, mut p) = self.tableau.multi_slice_mut(( + s![.., qubit0], + s![.., self.num_qubits + qubit0], + s![.., qubit1], + s![.., self.num_qubits + qubit1], + s![.., 2 * self.num_qubits], + )); + azip!((p in &mut p, &x0 in &x0, &z0 in &z0, &x1 in &x1, &z1 in &z1) *p ^= (x1 ^ z0 ^ true) & z1 & x0); + azip!((x1 in &mut x1, &x0 in &x0) *x1 ^= x0); + azip!((z0 in &mut z0, &z1 in &z1) *z0 ^= z1); + } + + /// Creates a Clifford from a given sequence of Clifford gates. + /// In essence, starts from the identity tableau and modifies it + /// based on the gates in the sequence. + pub fn from_gate_sequence( + gate_seq: &CliffordGatesVec, + num_qubits: usize, + ) -> Result { + // create the identity + let mut clifford = Clifford { + num_qubits, + tableau: Array2::from_shape_fn((2 * num_qubits, 2 * num_qubits + 1), |(i, j)| i == j), + }; + + gate_seq + .iter() + .try_for_each(|(gate, _params, qubits)| match *gate { + StandardGate::SGate => { + clifford.append_s(qubits[0].0 as usize); + Ok(()) + } + StandardGate::HGate => { + clifford.append_h(qubits[0].0 as usize); + Ok(()) + } + StandardGate::CXGate => { + clifford.append_cx(qubits[0].0 as usize, qubits[1].0 as usize); + Ok(()) + } + StandardGate::SwapGate => { + clifford.append_swap(qubits[0].0 as usize, qubits[1].0 as usize); + Ok(()) + } + _ => Err(format!("Unsupported gate {:?}", gate)), + })?; + Ok(clifford) + } +} + +/// A sequence of Clifford gates. +/// Represents the return type of Clifford synthesis algorithms. +pub type CliffordGatesVec = Vec<(StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>)>; + +/// Given a sequence of Clifford gates that correctly implements the symplectic matrix +/// of the target clifford tableau, adds the Pauli gates to also match the phase of +/// the tableau. +pub fn adjust_final_pauli_gates( + gate_seq: &mut CliffordGatesVec, + target_tableau: ArrayView2, + num_qubits: usize, +) -> Result<(), String> { + // simulate the clifford circuit that we have constructed + let simulated_clifford = Clifford::from_gate_sequence(gate_seq, num_qubits)?; + + // compute the phase difference + let target_phase = target_tableau.column(2 * num_qubits); + let sim_phase = simulated_clifford.tableau.column(2 * num_qubits); + + let delta_phase: Vec = target_phase + .iter() + .zip(sim_phase.iter()) + .map(|(&a, &b)| a ^ b) + .collect(); + + // compute inverse of the symplectic matrix + let smat = target_tableau.slice(s![.., ..2 * num_qubits]); + let smat_inv = calc_inverse_matrix_inner(smat, false)?; + + // compute smat_inv * delta_phase + let arr1 = smat_inv.map(|v| *v as usize); + let vec2: Vec = delta_phase.into_iter().map(|v| v as usize).collect(); + let arr2 = Array1::from(vec2); + let delta_phase_pre = arr1.dot(&arr2).map(|v| v % 2 == 1); + + // add pauli gates + for qubit in 0..num_qubits { + if delta_phase_pre[qubit] && delta_phase_pre[qubit + num_qubits] { + // println!("=> Adding Y-gate on {}", qubit); + gate_seq.push(( + StandardGate::YGate, + smallvec![], + smallvec![Qubit(qubit as u32)], + )); + } else if delta_phase_pre[qubit] { + // println!("=> Adding Z-gate on {}", qubit); + gate_seq.push(( + StandardGate::ZGate, + smallvec![], + smallvec![Qubit(qubit as u32)], + )); + } else if delta_phase_pre[qubit + num_qubits] { + // println!("=> Adding X-gate on {}", qubit); + gate_seq.push(( + StandardGate::XGate, + smallvec![], + smallvec![Qubit(qubit as u32)], + )); + } + } + + Ok(()) +} diff --git a/crates/accelerate/src/synthesis/linear/mod.rs b/crates/accelerate/src/synthesis/linear/mod.rs index 2fa158ea761f..b184a170fa5f 100644 --- a/crates/accelerate/src/synthesis/linear/mod.rs +++ b/crates/accelerate/src/synthesis/linear/mod.rs @@ -14,7 +14,7 @@ use crate::QiskitError; use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2, PyReadwriteArray2}; use pyo3::prelude::*; -mod utils; +pub mod utils; #[pyfunction] #[pyo3(signature = (mat, ncols=None, full_elim=false))] diff --git a/crates/accelerate/src/synthesis/mod.rs b/crates/accelerate/src/synthesis/mod.rs index db28751437f6..1b9908ef80cf 100644 --- a/crates/accelerate/src/synthesis/mod.rs +++ b/crates/accelerate/src/synthesis/mod.rs @@ -10,7 +10,8 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -mod linear; +mod clifford; +pub mod linear; mod permutation; use pyo3::prelude::*; @@ -18,7 +19,8 @@ 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))?; + m.add_wrapped(wrap_pymodule!(permutation::permutation))?; + m.add_wrapped(wrap_pymodule!(clifford::clifford))?; Ok(()) } diff --git a/qiskit/__init__.py b/qiskit/__init__.py index aca555da8cb8..9aaa7a76a68e 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -82,6 +82,7 @@ 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 +sys.modules["qiskit._accelerate.synthesis.clifford"] = _accelerate.synthesis.clifford from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/synthesis/clifford/clifford_decompose_greedy.py b/qiskit/synthesis/clifford/clifford_decompose_greedy.py index 784e6706d62e..0a679a8a7a6f 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_greedy.py +++ b/qiskit/synthesis/clifford/clifford_decompose_greedy.py @@ -12,22 +12,16 @@ """ Circuit synthesis for the Clifford class. """ -# pylint: disable=invalid-name # --------------------------------------------------------------------- # Synthesis based on Bravyi et. al. greedy clifford compiler # --------------------------------------------------------------------- - -import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.exceptions import QiskitError -from qiskit.quantum_info import Clifford, Pauli -from qiskit.quantum_info.operators.symplectic.clifford_circuits import ( - _append_cx, - _append_h, - _append_s, - _append_swap, +from qiskit.quantum_info import Clifford + +from qiskit._accelerate.synthesis.clifford import ( + synth_clifford_greedy as synth_clifford_greedy_inner, ) @@ -56,296 +50,8 @@ def synth_clifford_greedy(clifford: Clifford) -> QuantumCircuit: *Clifford Circuit Optimization with Templates and Symbolic Pauli Gates*, `arXiv:2105.02291 [quant-ph] `_ """ - - num_qubits = clifford.num_qubits - circ = QuantumCircuit(num_qubits, name=str(clifford)) - qubit_list = list(range(num_qubits)) - clifford_cpy = clifford.copy() - - # Reducing the original Clifford to identity - # via symplectic Gaussian elimination - while len(qubit_list) > 0: - # Calculate the adjoint of clifford_cpy without the phase - clifford_adj = clifford_cpy.copy() - tmp = clifford_adj.destab_x.copy() - clifford_adj.destab_x = clifford_adj.stab_z.T - clifford_adj.destab_z = clifford_adj.destab_z.T - clifford_adj.stab_x = clifford_adj.stab_x.T - clifford_adj.stab_z = tmp.T - - list_greedy_cost = [] - for qubit in qubit_list: - pauli_x = Pauli("I" * (num_qubits - qubit - 1) + "X" + "I" * qubit) - pauli_x = pauli_x.evolve(clifford_adj, frame="s") - - pauli_z = Pauli("I" * (num_qubits - qubit - 1) + "Z" + "I" * qubit) - pauli_z = pauli_z.evolve(clifford_adj, frame="s") - list_pairs = [] - pauli_count = 0 - - # Compute the CNOT cost in order to find the qubit with the minimal cost - for i in qubit_list: - typeq = _from_pair_paulis_to_type(pauli_x, pauli_z, i) - list_pairs.append(typeq) - pauli_count += 1 - cost = _compute_greedy_cost(list_pairs) - list_greedy_cost.append([cost, qubit]) - - _, min_qubit = (sorted(list_greedy_cost))[0] - - # Gaussian elimination step for the qubit with minimal CNOT cost - pauli_x = Pauli("I" * (num_qubits - min_qubit - 1) + "X" + "I" * min_qubit) - pauli_x = pauli_x.evolve(clifford_adj, frame="s") - - pauli_z = Pauli("I" * (num_qubits - min_qubit - 1) + "Z" + "I" * min_qubit) - pauli_z = pauli_z.evolve(clifford_adj, frame="s") - - # Compute the decoupling operator of cliff_ox and cliff_oz - decouple_circ, decouple_cliff = _calc_decoupling( - pauli_x, pauli_z, qubit_list, min_qubit, num_qubits, clifford_cpy - ) - circ = circ.compose(decouple_circ) - - # Now the clifford acts trivially on min_qubit - clifford_cpy = decouple_cliff.adjoint().compose(clifford_cpy) - qubit_list.remove(min_qubit) - - # Add the phases (Pauli gates) to the Clifford circuit - for qubit in range(num_qubits): - stab = clifford_cpy.stab_phase[qubit] - destab = clifford_cpy.destab_phase[qubit] - if destab and stab: - circ.y(qubit) - elif not destab and stab: - circ.x(qubit) - elif destab and not stab: - circ.z(qubit) - - return circ - - -# --------------------------------------------------------------------- -# Helper functions for Bravyi et. al. greedy clifford compiler -# --------------------------------------------------------------------- - -# Global arrays of the 16 pairs of Pauli operators -# divided into 5 equivalence classes under the action of single-qubit Cliffords - -# Class A - canonical representative is 'XZ' -A_class = [ - [[False, True], [True, True]], # 'XY' - [[False, True], [True, False]], # 'XZ' - [[True, True], [False, True]], # 'YX' - [[True, True], [True, False]], # 'YZ' - [[True, False], [False, True]], # 'ZX' - [[True, False], [True, True]], -] # 'ZY' - -# Class B - canonical representative is 'XX' -B_class = [ - [[True, False], [True, False]], # 'ZZ' - [[False, True], [False, True]], # 'XX' - [[True, True], [True, True]], -] # 'YY' - -# Class C - canonical representative is 'XI' -C_class = [ - [[True, False], [False, False]], # 'ZI' - [[False, True], [False, False]], # 'XI' - [[True, True], [False, False]], -] # 'YI' - -# Class D - canonical representative is 'IZ' -D_class = [ - [[False, False], [False, True]], # 'IX' - [[False, False], [True, False]], # 'IZ' - [[False, False], [True, True]], -] # 'IY' - -# Class E - only 'II' -E_class = [[[False, False], [False, False]]] # 'II' - - -def _from_pair_paulis_to_type(pauli_x, pauli_z, qubit): - """Converts a pair of Paulis pauli_x and pauli_z into a type""" - - type_x = [pauli_x.z[qubit], pauli_x.x[qubit]] - type_z = [pauli_z.z[qubit], pauli_z.x[qubit]] - return [type_x, type_z] - - -def _compute_greedy_cost(list_pairs): - """Compute the CNOT cost of one step of the algorithm""" - - A_num = 0 - B_num = 0 - C_num = 0 - D_num = 0 - - for pair in list_pairs: - if pair in A_class: - A_num += 1 - elif pair in B_class: - B_num += 1 - elif pair in C_class: - C_num += 1 - elif pair in D_class: - D_num += 1 - - if (A_num % 2) == 0: - raise QiskitError("Symplectic Gaussian elimination fails.") - - # Calculate the CNOT cost - cost = 3 * (A_num - 1) / 2 + (B_num + 1) * (B_num > 0) + C_num + D_num - if list_pairs[0] not in A_class: # additional SWAP - cost += 3 - - return cost - - -def _calc_decoupling(pauli_x, pauli_z, qubit_list, min_qubit, num_qubits, cliff): - """Calculate a decoupling operator D: - D^{-1} * Ox * D = x1 - D^{-1} * Oz * D = z1 - and reduce the clifford such that it will act trivially on min_qubit - """ - - circ = QuantumCircuit(num_qubits) - - # decouple_cliff is initialized to an identity clifford - decouple_cliff = cliff.copy() - num_qubits = decouple_cliff.num_qubits - decouple_cliff.phase = np.zeros(2 * num_qubits) - decouple_cliff.symplectic_matrix = np.eye(2 * num_qubits) - - qubit0 = min_qubit # The qubit for the symplectic Gaussian elimination - - # Reduce the pair of Paulis to a representative in the equivalence class - # ['XZ', 'XX', 'XI', 'IZ', 'II'] by adding single-qubit gates - for qubit in qubit_list: - - typeq = _from_pair_paulis_to_type(pauli_x, pauli_z, qubit) - - if typeq in [ - [[True, True], [False, False]], # 'YI' - [[True, True], [True, True]], # 'YY' - [[True, True], [True, False]], - ]: # 'YZ': - circ.s(qubit) - _append_s(decouple_cliff, qubit) - - elif typeq in [ - [[True, False], [False, False]], # 'ZI' - [[True, False], [True, False]], # 'ZZ' - [[True, False], [False, True]], # 'ZX' - [[False, False], [False, True]], - ]: # 'IX' - circ.h(qubit) - _append_h(decouple_cliff, qubit) - - elif typeq in [ - [[False, False], [True, True]], # 'IY' - [[True, False], [True, True]], - ]: # 'ZY' - circ.s(qubit) - circ.h(qubit) - _append_s(decouple_cliff, qubit) - _append_h(decouple_cliff, qubit) - - elif typeq == [[True, True], [False, True]]: # 'YX' - circ.h(qubit) - circ.s(qubit) - _append_h(decouple_cliff, qubit) - _append_s(decouple_cliff, qubit) - - elif typeq == [[False, True], [True, True]]: # 'XY' - circ.s(qubit) - circ.h(qubit) - circ.s(qubit) - _append_s(decouple_cliff, qubit) - _append_h(decouple_cliff, qubit) - _append_s(decouple_cliff, qubit) - - # Reducing each pair of Paulis (except of qubit0) to 'II' - # by adding two-qubit gates and single-qubit gates - A_qubits = [] - B_qubits = [] - C_qubits = [] - D_qubits = [] - - for qubit in qubit_list: - typeq = _from_pair_paulis_to_type(pauli_x, pauli_z, qubit) - if typeq in A_class: - A_qubits.append(qubit) - elif typeq in B_class: - B_qubits.append(qubit) - elif typeq in C_class: - C_qubits.append(qubit) - elif typeq in D_class: - D_qubits.append(qubit) - - if len(A_qubits) % 2 != 1: - raise QiskitError("Symplectic Gaussian elimination fails.") - - if qubit0 not in A_qubits: # SWAP qubit0 and qubitA - qubitA = A_qubits[0] - circ.swap(qubit0, qubitA) - _append_swap(decouple_cliff, qubit0, qubitA) - if qubit0 in B_qubits: - B_qubits.remove(qubit0) - B_qubits.append(qubitA) - A_qubits.remove(qubitA) - A_qubits.append(qubit0) - elif qubit0 in C_qubits: - C_qubits.remove(qubit0) - C_qubits.append(qubitA) - A_qubits.remove(qubitA) - A_qubits.append(qubit0) - elif qubit0 in D_qubits: - D_qubits.remove(qubit0) - D_qubits.append(qubitA) - A_qubits.remove(qubitA) - A_qubits.append(qubit0) - else: - A_qubits.remove(qubitA) - A_qubits.append(qubit0) - - # Reduce pairs in Class C to 'II' - for qubit in C_qubits: - circ.cx(qubit0, qubit) - _append_cx(decouple_cliff, qubit0, qubit) - - # Reduce pairs in Class D to 'II' - for qubit in D_qubits: - circ.cx(qubit, qubit0) - _append_cx(decouple_cliff, qubit, qubit0) - - # Reduce pairs in Class B to 'II' - if len(B_qubits) > 1: - for qubit in B_qubits[1:]: - qubitB = B_qubits[0] - circ.cx(qubitB, qubit) - _append_cx(decouple_cliff, qubitB, qubit) - - if len(B_qubits) > 0: - qubitB = B_qubits[0] - circ.cx(qubit0, qubitB) - circ.h(qubitB) - circ.cx(qubitB, qubit0) - _append_cx(decouple_cliff, qubit0, qubitB) - _append_h(decouple_cliff, qubitB) - _append_cx(decouple_cliff, qubitB, qubit0) - - # Reduce pairs in Class A (except of qubit0) to 'II' - Alen = int((len(A_qubits) - 1) / 2) - if Alen > 0: - A_qubits.remove(qubit0) - for qubit in range(Alen): - circ.cx(A_qubits[2 * qubit + 1], A_qubits[2 * qubit]) - circ.cx(A_qubits[2 * qubit], qubit0) - circ.cx(qubit0, A_qubits[2 * qubit + 1]) - _append_cx(decouple_cliff, A_qubits[2 * qubit + 1], A_qubits[2 * qubit]) - _append_cx(decouple_cliff, A_qubits[2 * qubit], qubit0) - _append_cx(decouple_cliff, qubit0, A_qubits[2 * qubit + 1]) - - return circ, decouple_cliff + circuit = QuantumCircuit._from_circuit_data( + synth_clifford_greedy_inner(clifford.tableau.astype(bool)) + ) + circuit.name = str(clifford) + return circuit diff --git a/releasenotes/notes/oxidize-synth-clifford-greedy-0739e9688bc4eedd.yaml b/releasenotes/notes/oxidize-synth-clifford-greedy-0739e9688bc4eedd.yaml new file mode 100644 index 000000000000..044fb7d44315 --- /dev/null +++ b/releasenotes/notes/oxidize-synth-clifford-greedy-0739e9688bc4eedd.yaml @@ -0,0 +1,6 @@ +--- +upgrade_synthesis: + - | + The function :func:`.synth_clifford_greedy` that synthesizes :class:`.Clifford` operators + was ported to Rust, leading to a significant increase in performance for all numbers of + qubits. For Cliffords over 50 qubits, the speedup is on the order of 1000 times. diff --git a/test/python/synthesis/test_clifford_sythesis.py b/test/python/synthesis/test_clifford_sythesis.py index 887f1af5ad99..8ca11f1ef251 100644 --- a/test/python/synthesis/test_clifford_sythesis.py +++ b/test/python/synthesis/test_clifford_sythesis.py @@ -16,6 +16,7 @@ import numpy as np from ddt import ddt from qiskit.circuit.random import random_clifford_circuit +from qiskit.quantum_info import random_clifford from qiskit.quantum_info.operators import Clifford from qiskit.synthesis.clifford import ( synth_clifford_full, @@ -99,8 +100,7 @@ def test_synth_greedy(self, num_qubits): rng = np.random.default_rng(1234) samples = 50 for _ in range(samples): - circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) - target = Clifford(circ) + target = random_clifford(num_qubits, rng) synth_circ = synth_clifford_greedy(target) value = Clifford(synth_circ) self.assertEqual(value, target) From ba486b739daf8ba410f7c0943fb035d689110dc2 Mon Sep 17 00:00:00 2001 From: Hirmay Sandesara <56473003+Hirmay@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:17:55 +0530 Subject: [PATCH 50/89] Add Uniform State Preparation (#12112) * Update state_preparation.py * Update state_preparation.py * Add files via upload * Update __init__.py * Update test_generalized_uniform_superposition_gate.py * Update test_generalized_uniform_superposition_gate.py * Update test_gate_definitions.py * made the format consistent with StatePreparation class * Put description and arguments in init * replaced assert with raise ValueError * small mistake in Returns * added test cases for ValueError cases * Update state_preparation.py * incorporate Steve's helpful suggestions * small bug * test_case fix * function for returning M * oops..forgot "" marks * Update state_preparation.py * removed get methods for num_qubit and M * added power of 2 condition * included test function when num_qubits is None * blacked init * blacked state_prep * blacked test_generalized_uniform_superposition_gate.py * reblacked state_prep * reblacked test_generalized_uniform_superposition_gate.py * shorterned max line length * reblacked state_prep * reblacked state_prep * for pyline * pylinted state_preparation.py * pylinted test_generalized_uniform_superposition_gate.py * pylinted test_gate_definitions.py * pylinted test_generalized_uniform_superposition_gate.py * Added GeneralizedUniformSuperposition gate Added GeneralizedUniformSuperposition gate class to the StatePreparation file in Circuit library. * modified: releasenotes/notes/generalized-uniform-superposition-gate-3bd95ffdf05ef18c.yaml * Updated release notes based on Steve's suggestions * Update release note * implemented the changes * fixed error * Update test_uniform_superposition_gate.py * Update uniform-superposition-gate-3bd95ffdf05ef18c.yaml * Update uniform-superposition-gate-3bd95ffdf05ef18c.yaml * Update qiskit/circuit/library/data_preparation/state_preparation.py Sounds good! Co-authored-by: Julien Gacon * Update qiskit/circuit/library/data_preparation/state_preparation.py Okay! Co-authored-by: Julien Gacon * Update qiskit/circuit/library/data_preparation/state_preparation.py oh that's interesting...I didn't know that. Thanks for the info! Co-authored-by: Julien Gacon * Update releasenotes/notes/uniform-superposition-gate-3bd95ffdf05ef18c.yaml Ahhh...that's nice! Co-authored-by: Julien Gacon * Update releasenotes/notes/uniform-superposition-gate-3bd95ffdf05ef18c.yaml You're quite right, will help for others who might look at the code in future. Co-authored-by: Luciano Bello * Update state_preparation.py incorporated Julien's changes! * Update test_uniform_superposition_gate.py Incorporated Julien's optimization suggestion. * Update uniform-superposition-gate-3bd95ffdf05ef18c.yaml implemented both reviewers' suggestions. * Update uniform-superposition-gate-3bd95ffdf05ef18c.yaml removed SV24 * Update state_preparation.py * Update state_preparation.py * Update test_uniform_superposition_gate.py * Update uniform-superposition-gate-3bd95ffdf05ef18c.yaml * Update qiskit/circuit/library/data_preparation/state_preparation.py Co-authored-by: Julien Gacon * blacked state_preparation.py * Update test_uniform_superposition_gate.py * pylinted state_preparation.py * removed transpile test_uniform_superposition_gate.py --------- Co-authored-by: Julien Gacon Co-authored-by: Luciano Bello --- .../library/data_preparation/__init__.py | 11 ++- .../data_preparation/state_preparation.py | 96 +++++++++++++++++- ...m-superposition-gate-3bd95ffdf05ef18c.yaml | 27 +++++ test/python/circuit/test_gate_definitions.py | 1 + .../test_uniform_superposition_gate.py | 98 +++++++++++++++++++ 5 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/uniform-superposition-gate-3bd95ffdf05ef18c.yaml create mode 100644 test/python/circuit/test_uniform_superposition_gate.py diff --git a/qiskit/circuit/library/data_preparation/__init__.py b/qiskit/circuit/library/data_preparation/__init__.py index 38611c911fa4..192308a3a7f5 100644 --- a/qiskit/circuit/library/data_preparation/__init__.py +++ b/qiskit/circuit/library/data_preparation/__init__.py @@ -41,7 +41,14 @@ from .pauli_feature_map import PauliFeatureMap from .z_feature_map import ZFeatureMap from .zz_feature_map import ZZFeatureMap -from .state_preparation import StatePreparation +from .state_preparation import StatePreparation, UniformSuperpositionGate from .initializer import Initialize -__all__ = ["PauliFeatureMap", "ZFeatureMap", "ZZFeatureMap", "StatePreparation", "Initialize"] +__all__ = [ + "PauliFeatureMap", + "ZFeatureMap", + "ZZFeatureMap", + "StatePreparation", + "UniformSuperpositionGate", + "Initialize", +] diff --git a/qiskit/circuit/library/data_preparation/state_preparation.py b/qiskit/circuit/library/data_preparation/state_preparation.py index 1d9ad7f7b082..26c37334cfe4 100644 --- a/qiskit/circuit/library/data_preparation/state_preparation.py +++ b/qiskit/circuit/library/data_preparation/state_preparation.py @@ -25,7 +25,9 @@ from qiskit.circuit.library.standard_gates.s import SGate, SdgGate from qiskit.circuit.library.generalized_gates import Isometry from qiskit.circuit.exceptions import CircuitError -from qiskit.quantum_info.states.statevector import Statevector # pylint: disable=cyclic-import +from qiskit.quantum_info.states.statevector import ( + Statevector, +) # pylint: disable=cyclic-import _EPS = 1e-10 # global variable used to chop very small numbers to zero @@ -240,3 +242,95 @@ def validate_parameter(self, parameter): def _return_repeat(self, exponent: float) -> "Gate": return Gate(name=f"{self.name}*{exponent}", num_qubits=self.num_qubits, params=[]) + + +class UniformSuperpositionGate(Gate): + r"""Implements a uniform superposition state. + + This gate is used to create the uniform superposition state + :math:`\frac{1}{\sqrt{M}} \sum_{j=0}^{M-1} |j\rangle` when it acts on an input + state :math:`|0...0\rangle`. Note, that `M` is not required to be + a power of 2, in which case the uniform superposition could be + prepared by a single layer of Hadamard gates. + + .. note:: + + This class uses the Shukla-Vedula algorithm [1], which only needs + :math:`O(\log_2 (M))` qubits and :math:`O(\log_2 (M))` gates, + to prepare the superposition. + + **References:** + [1]: A. Shukla and P. Vedula (2024), An efficient quantum algorithm for preparation + of uniform quantum superposition states, `Quantum Inf Process 23, 38 + `_. + """ + + def __init__( + self, + num_superpos_states: int = 2, + num_qubits: Optional[int] = None, + ): + r""" + Args: + num_superpos_states (int): + A positive integer M = num_superpos_states (> 1) representing the number of computational + basis states with an amplitude of 1/sqrt(M) in the uniform superposition + state (:math:`\frac{1}{\sqrt{M}} \sum_{j=0}^{M-1} |j\rangle`, where + :math:`1< M <= 2^n`). Note that the remaining (:math:`2^n - M`) computational basis + states have zero amplitudes. Here M need not be an integer power of 2. + + num_qubits (int): + A positive integer representing the number of qubits used. If num_qubits is None + or is not specified, then num_qubits is set to ceil(log2(num_superpos_states)). + + Raises: + ValueError: num_qubits must be an integer greater than or equal to log2(num_superpos_states). + + """ + if num_superpos_states <= 1: + raise ValueError("num_superpos_states must be a positive integer greater than 1.") + if num_qubits is None: + num_qubits = int(math.ceil(math.log2(num_superpos_states))) + else: + if not (isinstance(num_qubits, int) and (num_qubits >= math.log2(num_superpos_states))): + raise ValueError( + "num_qubits must be an integer greater than or equal to log2(num_superpos_states)." + ) + super().__init__("USup", num_qubits, [num_superpos_states]) + + def _define(self): + + qc = QuantumCircuit(self._num_qubits) + + num_superpos_states = self.params[0] + + if ( + num_superpos_states & (num_superpos_states - 1) + ) == 0: # if num_superpos_states is an integer power of 2 + m = int(math.log2(num_superpos_states)) + qc.h(range(m)) + self.definition = qc + return + + n_value = [int(x) for x in reversed(np.binary_repr(num_superpos_states))] + k = len(n_value) + l_value = [index for (index, item) in enumerate(n_value) if item == 1] # Locations of '1's + + qc.x(l_value[1:k]) + m_current_value = 2 ** l_value[0] + theta = -2 * np.arccos(np.sqrt(m_current_value / num_superpos_states)) + + if l_value[0] > 0: # if num_superpos_states is even + qc.h(range(l_value[0])) + qc.ry(theta, l_value[1]) + qc.ch(l_value[1], range(l_value[0], l_value[1]), ctrl_state="0") + + for m in range(1, len(l_value) - 1): + theta = -2 * np.arccos( + np.sqrt(2 ** l_value[m] / (num_superpos_states - m_current_value)) + ) + qc.cry(theta, l_value[m], l_value[m + 1], ctrl_state="0") + qc.ch(l_value[m + 1], range(l_value[m], l_value[m + 1]), ctrl_state="0") + m_current_value = m_current_value + 2 ** l_value[m] + + self.definition = qc diff --git a/releasenotes/notes/uniform-superposition-gate-3bd95ffdf05ef18c.yaml b/releasenotes/notes/uniform-superposition-gate-3bd95ffdf05ef18c.yaml new file mode 100644 index 000000000000..6017979748ea --- /dev/null +++ b/releasenotes/notes/uniform-superposition-gate-3bd95ffdf05ef18c.yaml @@ -0,0 +1,27 @@ +--- +features: + - | + Implemented :class:`.UniformSuperpositionGate` class, which allows + the creation of a uniform superposition state using + the Shukla-Vedula algorithm. This feature facilitates the + creation of quantum circuits that produce a uniform superposition + state :math:`\frac{1}{\sqrt{M}} \sum_{j=0}^{M-1} |j\rangle`, where + :math:`M` is a positive integer representing the number of + computational basis states with an amplitude of + :math:`\frac{1}{\sqrt{M}}`. This implementation supports the + efficient creation of uniform superposition states, + requiring only :math:`O(\log_2 (M))` qubits and + :math:`O(\log_2 (M))` gates. Usage example: + + .. code-block:: python + + from qiskit import QuantumCircuit + from qiskit.circuit.library.data_preparation import UniformSuperpositionGate + + M = 5 + num_qubits = 3 + usp_gate = UniformSuperpositionGate(M, num_qubits) + qc = QuantumCircuit(num_qubits) + qc.append(usp_gate, list(range(num_qubits))) + + qc.draw() diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 38bf7046cae5..c5df22a0e8a1 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -283,6 +283,7 @@ class TestGateEquivalenceEqual(QiskitTestCase): "ClassicalFunction", "ClassicalElement", "StatePreparation", + "UniformSuperpositionGate", "LinearFunction", "PermutationGate", "Commuting2qBlock", diff --git a/test/python/circuit/test_uniform_superposition_gate.py b/test/python/circuit/test_uniform_superposition_gate.py new file mode 100644 index 000000000000..019d6144ac5e --- /dev/null +++ b/test/python/circuit/test_uniform_superposition_gate.py @@ -0,0 +1,98 @@ +# 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. + +""" +Uniform Superposition Gate test. +""" + +import unittest +import math +from test import QiskitTestCase +import numpy as np +from ddt import ddt, data + +from qiskit import QuantumCircuit +from qiskit.quantum_info import Operator, Statevector + +from qiskit.circuit.library.data_preparation import ( + UniformSuperpositionGate, +) + + +@ddt +class TestUniformSuperposition(QiskitTestCase): + """Test initialization with UniformSuperpositionGate class""" + + @data(2, 3, 5) + def test_uniform_superposition_gate(self, num_superpos_states): + """Test Uniform Superposition Gate""" + n = int(math.ceil(math.log2(num_superpos_states))) + desired_sv = (1 / np.sqrt(num_superpos_states)) * np.array( + [1.0] * num_superpos_states + [0.0] * (2**n - num_superpos_states) + ) + gate = UniformSuperpositionGate(num_superpos_states, n) + actual_sv = Statevector(gate) + np.testing.assert_allclose(desired_sv, actual_sv) + + @data(2, 3, 5, 13) + def test_inverse_uniform_superposition_gate(self, num_superpos_states): + """Test Inverse Uniform Superposition Gate""" + n = int(math.ceil(math.log2(num_superpos_states))) + gate = UniformSuperpositionGate(num_superpos_states, n) + qc = QuantumCircuit(n) + qc.append(gate, list(range(n))) + qc.append(gate.inverse(annotated=True), list(range(n))) + actual_unitary_matrix = np.array(Operator(qc).data) + desired_unitary_matrix = np.eye(2**n) + np.testing.assert_allclose(desired_unitary_matrix, actual_unitary_matrix, atol=1e-14) + + @data(-2, -1, 0, 1) + def test_incompatible_num_superpos_states(self, num_superpos_states): + """Test error raised if num_superpos_states not valid""" + n = 1 + with self.assertRaises(ValueError): + UniformSuperpositionGate(num_superpos_states, n) + + @data(1, 2, 3, 4) + def test_incompatible_int_num_superpos_states_and_qubit_args(self, n): + """Test error raised if number of qubits not compatible with integer + state num_superpos_states (n >= log2(num_superpos_states) )""" + num_superpos_states = 50 + with self.assertRaises(ValueError): + UniformSuperpositionGate(num_superpos_states, n) + + @data(2, 3, 5) + def test_extra_qubits(self, num_superpos_states): + """Tests for cases where n >= log2(num_superpos_states)""" + num_extra_qubits = 2 + n = int(math.ceil(math.log2(num_superpos_states))) + num_extra_qubits + desired_sv = (1 / np.sqrt(num_superpos_states)) * np.array( + [1.0] * num_superpos_states + [0.0] * (2**n - num_superpos_states) + ) + gate = UniformSuperpositionGate(num_superpos_states, n) + actual_sv = Statevector(gate) + np.testing.assert_allclose(desired_sv, actual_sv) + + @data(2, 3, 5) + def test_no_qubit_args(self, num_superpos_states): + """Test Uniform Superposition Gate without passing the number of qubits as an argument""" + n = int(math.ceil(math.log2(num_superpos_states))) + desired_sv = (1 / np.sqrt(num_superpos_states)) * np.array( + [1.0] * num_superpos_states + [0.0] * (2**n - num_superpos_states) + ) + gate = UniformSuperpositionGate(num_superpos_states) + actual_sv = Statevector(gate) + np.testing.assert_allclose(desired_sv, actual_sv) + + +if __name__ == "__main__": + unittest.main() From 419f40e9e72b4981a6536d08d6b0fa91c13021d5 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 3 Jul 2024 14:04:47 +0300 Subject: [PATCH 51/89] fixing synthesis release notes (#12715) --- releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml | 2 +- releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml | 2 +- .../notes/oxidize-synth-clifford-greedy-0739e9688bc4eedd.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml b/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml index 6bd6761e0355..532d3e8fa55e 100644 --- a/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml +++ b/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml @@ -1,5 +1,5 @@ --- -upgrade_synthesis: +features_synthesis: - | Port :func:`.synth_permutation_acg`, used to synthesize qubit permutations, to Rust. This produces an approximate 3x performance improvement on 1000 qubit circuits. diff --git a/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml b/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml index e770aa1ca31b..bd8c969be934 100644 --- a/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml +++ b/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml @@ -1,4 +1,4 @@ --- -upgrade_synthesis: +features_synthesis: - | Port :func:`.synth_permutation_basic`, used to synthesize qubit permutations, to Rust. diff --git a/releasenotes/notes/oxidize-synth-clifford-greedy-0739e9688bc4eedd.yaml b/releasenotes/notes/oxidize-synth-clifford-greedy-0739e9688bc4eedd.yaml index 044fb7d44315..ea492e29a7fc 100644 --- a/releasenotes/notes/oxidize-synth-clifford-greedy-0739e9688bc4eedd.yaml +++ b/releasenotes/notes/oxidize-synth-clifford-greedy-0739e9688bc4eedd.yaml @@ -1,5 +1,5 @@ --- -upgrade_synthesis: +features_synthesis: - | The function :func:`.synth_clifford_greedy` that synthesizes :class:`.Clifford` operators was ported to Rust, leading to a significant increase in performance for all numbers of From 9571ea1b3aea4c3ff89ee71f03b31b53d5bfa49a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 3 Jul 2024 10:00:10 -0400 Subject: [PATCH 52/89] Avoid Python op creation in commutative cancellation (#12701) * Avoid Python op creation in commutative cancellation This commit updates the commutative cancellation and commutation analysis transpiler pass. It builds off of #12692 to adjust access patterns in the python transpiler path to avoid eagerly creating a Python space operation object. The goal of this PR is to mitigate the performance regression on these passes introduced by the extra conversion cost of #12459. * Remove stray print * Don't add __array__ to DAGOpNode or CircuitInstruction --- crates/circuit/src/circuit_instruction.rs | 6 ++++ crates/circuit/src/dag_node.rs | 30 ++++++++++++++++ crates/circuit/src/operations.rs | 34 ++++++++++++++++-- qiskit/circuit/commutation_checker.py | 35 ++++++++++++++++++- .../optimization/commutation_analysis.py | 9 +---- .../optimization/commutative_cancellation.py | 14 ++++---- 6 files changed, 111 insertions(+), 17 deletions(-) diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index d6516722fbac..ed1c358cbc5b 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -460,6 +460,12 @@ impl CircuitInstruction { .and_then(|attrs| attrs.unit.as_deref()) } + pub fn is_parameterized(&self) -> bool { + self.params + .iter() + .any(|x| matches!(x, Param::ParameterExpression(_))) + } + /// Creates a shallow copy with the given fields replaced. /// /// Returns: diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index 44ba5f7a6bf0..55a40c83dc39 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -14,6 +14,7 @@ use crate::circuit_instruction::{ convert_py_to_operation_type, operation_type_to_py, CircuitInstruction, ExtraInstructionAttributes, }; +use crate::imports::QUANTUM_CIRCUIT; use crate::operations::Operation; use numpy::IntoPyArray; use pyo3::prelude::*; @@ -228,6 +229,16 @@ impl DAGOpNode { Ok(()) } + #[getter] + fn num_qubits(&self) -> u32 { + self.instruction.operation.num_qubits() + } + + #[getter] + fn num_clbits(&self) -> u32 { + self.instruction.operation.num_clbits() + } + #[getter] fn get_qargs(&self, py: Python) -> Py { self.instruction.qubits.clone_ref(py) @@ -259,6 +270,10 @@ impl DAGOpNode { self.instruction.params.to_object(py) } + pub fn is_parameterized(&self) -> bool { + self.instruction.is_parameterized() + } + #[getter] fn matrix(&self, py: Python) -> Option { let matrix = self.instruction.operation.matrix(&self.instruction.params); @@ -325,6 +340,21 @@ impl DAGOpNode { } } + #[getter] + fn definition<'py>(&self, py: Python<'py>) -> PyResult>> { + let definition = self + .instruction + .operation + .definition(&self.instruction.params); + definition + .map(|data| { + QUANTUM_CIRCUIT + .get_bound(py) + .call_method1(intern!(py, "_from_circuit_data"), (data,)) + }) + .transpose() + } + /// Sets the Instruction name corresponding to the op for this node #[setter] fn set_name(&mut self, py: Python, new_name: PyObject) -> PyResult<()> { diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 77458e2fc35f..3bfef81d29ce 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -715,8 +715,38 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::RXGate => todo!("Add when we have R"), - Self::RYGate => todo!("Add when we have R"), + Self::RXGate => Python::with_gil(|py| -> Option { + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::RGate, + smallvec![theta.clone(), FLOAT_ZERO], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::RYGate => Python::with_gil(|py| -> Option { + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::RGate, + smallvec![theta.clone(), Param::Float(PI / 2.0)], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::RZGate => Python::with_gil(|py| -> Option { let theta = ¶ms[0]; Some( diff --git a/qiskit/circuit/commutation_checker.py b/qiskit/circuit/commutation_checker.py index e758674829f8..79f04a65714d 100644 --- a/qiskit/circuit/commutation_checker.py +++ b/qiskit/circuit/commutation_checker.py @@ -21,6 +21,7 @@ from qiskit.circuit.operation import Operation from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES from qiskit.quantum_info.operators import Operator +from qiskit._accelerate.circuit import StandardGate _skipped_op_names = {"measure", "reset", "delay", "initialize"} _no_cache_op_names = {"annotated"} @@ -57,6 +58,23 @@ def __init__(self, standard_gate_commutations: dict = None, cache_max_entries: i self._cache_miss = 0 self._cache_hit = 0 + def commute_nodes( + self, + op1, + op2, + max_num_qubits: int = 3, + ) -> bool: + """Checks if two DAGOpNodes commute.""" + qargs1 = op1.qargs + cargs1 = op2.cargs + if not isinstance(op1._raw_op, StandardGate): + op1 = op1.op + qargs2 = op2.qargs + cargs2 = op2.cargs + if not isinstance(op2._raw_op, StandardGate): + op2 = op2.op + return self.commute(op1, qargs1, cargs1, op2, qargs2, cargs2, max_num_qubits) + def commute( self, op1: Operation, @@ -255,9 +273,15 @@ def is_commutation_skipped(op, qargs, max_num_qubits): if getattr(op, "is_parameterized", False) and op.is_parameterized(): return True + from qiskit.dagcircuit.dagnode import DAGOpNode + # we can proceed if op has defined: to_operator, to_matrix and __array__, or if its definition can be # recursively resolved by operations that have a matrix. We check this by constructing an Operator. - if (hasattr(op, "to_matrix") and hasattr(op, "__array__")) or hasattr(op, "to_operator"): + if ( + isinstance(op, DAGOpNode) + or (hasattr(op, "to_matrix") and hasattr(op, "__array__")) + or hasattr(op, "to_operator") + ): return False return False @@ -409,6 +433,15 @@ def _commute_matmul( first_qarg = tuple(qarg[q] for q in first_qargs) second_qarg = tuple(qarg[q] for q in second_qargs) + from qiskit.dagcircuit.dagnode import DAGOpNode + + # If we have a DAGOpNode here we've received a StandardGate definition from + # rust and we can manually pull the matrix to use for the Operators + if isinstance(first_ops, DAGOpNode): + first_ops = first_ops.matrix + if isinstance(second_op, DAGOpNode): + second_op = second_op.matrix + # try to generate an Operator out of op, if this succeeds we can determine commutativity, otherwise # return false try: diff --git a/qiskit/transpiler/passes/optimization/commutation_analysis.py b/qiskit/transpiler/passes/optimization/commutation_analysis.py index eddb659f0a25..61c77de552b9 100644 --- a/qiskit/transpiler/passes/optimization/commutation_analysis.py +++ b/qiskit/transpiler/passes/optimization/commutation_analysis.py @@ -72,14 +72,7 @@ def run(self, dag): does_commute = ( isinstance(current_gate, DAGOpNode) and isinstance(prev_gate, DAGOpNode) - and self.comm_checker.commute( - current_gate.op, - current_gate.qargs, - current_gate.cargs, - prev_gate.op, - prev_gate.qargs, - prev_gate.cargs, - ) + and self.comm_checker.commute_nodes(current_gate, prev_gate) ) if not does_commute: break diff --git a/qiskit/transpiler/passes/optimization/commutative_cancellation.py b/qiskit/transpiler/passes/optimization/commutative_cancellation.py index 4c6c487a0ea3..5c0b7317aabd 100644 --- a/qiskit/transpiler/passes/optimization/commutative_cancellation.py +++ b/qiskit/transpiler/passes/optimization/commutative_cancellation.py @@ -24,7 +24,7 @@ from qiskit.circuit.library.standard_gates.rx import RXGate from qiskit.circuit.library.standard_gates.p import PhaseGate from qiskit.circuit.library.standard_gates.rz import RZGate -from qiskit.circuit import ControlFlowOp +from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES _CUTOFF_PRECISION = 1e-5 @@ -138,14 +138,14 @@ def run(self, dag): total_phase = 0.0 for current_node in run: if ( - getattr(current_node.op, "condition", None) is not None + current_node.condition is not None or len(current_node.qargs) != 1 or current_node.qargs[0] != run_qarg ): raise RuntimeError("internal error") if current_node.name in ["p", "u1", "rz", "rx"]: - current_angle = float(current_node.op.params[0]) + current_angle = float(current_node.params[0]) elif current_node.name in ["z", "x"]: current_angle = np.pi elif current_node.name == "t": @@ -159,8 +159,8 @@ def run(self, dag): # Compose gates total_angle = current_angle + total_angle - if current_node.op.definition: - total_phase += current_node.op.definition.global_phase + if current_node.definition: + total_phase += current_node.definition.global_phase # Replace the data of the first node in the run if cancel_set_key[0] == "z_rotation": @@ -200,7 +200,9 @@ def _handle_control_flow_ops(self, dag): """ pass_manager = PassManager([CommutationAnalysis(), self]) - for node in dag.op_nodes(ControlFlowOp): + for node in dag.op_nodes(): + if node.name not in CONTROL_FLOW_OP_NAMES: + continue mapped_blocks = [] for block in node.op.blocks: new_circ = pass_manager.run(block) From 5a4d598966f4ffea84dfc7871543d3e6268f90ce Mon Sep 17 00:00:00 2001 From: Shraddha Aangiras <63237790+shraddha-aangiras@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:54:31 +0530 Subject: [PATCH 53/89] Fixed typo leading to missing examples (#12689) --- qiskit/transpiler/layout.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index 4117e2987bb6..bece19671794 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -454,7 +454,7 @@ class TranspileLayout: qubits in the circuit as it fits the circuit to the target backend. For example, let the input circuit be: - .. plot: + .. plot:: :include-source: from qiskit.circuit import QuantumCircuit, QuantumRegister @@ -469,7 +469,7 @@ class TranspileLayout: Suppose that during the layout stage the transpiler reorders the qubits to be: - .. plot: + .. plot:: :include-source: from qiskit import QuantumCircuit @@ -497,7 +497,7 @@ class TranspileLayout: the transpiler needs to insert swap gates, and the output circuit becomes: - .. plot: + .. plot:: :include-source: from qiskit import QuantumCircuit From eed8f452457c8e9fc93cded53a467a4b1a2ba506 Mon Sep 17 00:00:00 2001 From: Hwa Date: Wed, 3 Jul 2024 23:53:25 +0800 Subject: [PATCH 54/89] Add warning about Tweedledum support in ClassicalFunction (#12652) * Add warning about Tweedledum support in ClassicalFunction * Update qiskit/circuit/classicalfunction/__init__.py Co-authored-by: Luciano Bello * Update __init__.py line break to comply to pylint --------- Co-authored-by: Luciano Bello --- qiskit/circuit/classicalfunction/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qiskit/circuit/classicalfunction/__init__.py b/qiskit/circuit/classicalfunction/__init__.py index a072d910f97a..b045227b167e 100644 --- a/qiskit/circuit/classicalfunction/__init__.py +++ b/qiskit/circuit/classicalfunction/__init__.py @@ -51,6 +51,14 @@ def grover_oracle(a: Int1, b: Int1, c: Int1, d: Int1) -> Int1: Following Qiskit's little-endian bit ordering convention, the left-most bit (``a``) is the most significant bit and the right-most bit (``d``) is the least significant bit. +.. warning:: + + The functionality of `qiskit.circuit.classicalfunction` requires `tweedledum`, + which isn't available on all platforms (up to Python version 3.11). + See `tweedledum installation guide + `_ + for more details. + Supplementary Information ========================= From a897d707f76205615a2403fdc9aad8cf9a9f3673 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 3 Jul 2024 12:26:15 -0400 Subject: [PATCH 55/89] Accept `Option<&str>` instead of `&Option`, etc (#12593) * Accept Option<&str> instead of &Option, etc In a few places, this removes unnecessary object copies. Accept a wider range of input types, and more idiomatic input types in a few functions. This affects code added in the gates-in-rust PR. The great majority of the changes here were obsoleted by #12594. The original commit has been cherry picked on top of main. * Remove unnecessary as_ref() --- crates/circuit/src/circuit_data.rs | 8 +------- crates/circuit/src/parameter_table.rs | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index f10911cc440f..501645415874 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -1048,13 +1048,7 @@ impl CircuitData { Ok(PySet::new_bound(py, self.param_table.uuid_map.values())?.unbind()) } - pub fn pop_param( - &mut self, - py: Python, - uuid: u128, - name: String, - default: PyObject, - ) -> PyObject { + pub fn pop_param(&mut self, py: Python, uuid: u128, name: &str, default: PyObject) -> PyObject { match self.param_table.pop(uuid, name) { Some(res) => res.into_py(py), None => default.clone_ref(py), diff --git a/crates/circuit/src/parameter_table.rs b/crates/circuit/src/parameter_table.rs index 48c779eed3a3..9e5b31245391 100644 --- a/crates/circuit/src/parameter_table.rs +++ b/crates/circuit/src/parameter_table.rs @@ -153,8 +153,8 @@ impl ParamTable { self.uuid_map.clear(); } - pub fn pop(&mut self, key: u128, name: String) -> Option { - self.names.remove(&name); + pub fn pop(&mut self, key: u128, name: &str) -> Option { + self.names.remove(name); self.uuid_map.remove(&key); self.table.remove(&key) } From 51bf91e6ebdfef20eca0667eb51687119f10ea4b Mon Sep 17 00:00:00 2001 From: Shraddha Aangiras <63237790+shraddha-aangiras@users.noreply.github.com> Date: Wed, 3 Jul 2024 23:34:10 +0530 Subject: [PATCH 56/89] Fixed typo in RZZGate example eqn (#12678) Co-authored-by: Luciano Bello --- qiskit/circuit/library/standard_gates/rzz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/standard_gates/rzz.py b/qiskit/circuit/library/standard_gates/rzz.py index 119dd370e20c..554ad4954a31 100644 --- a/qiskit/circuit/library/standard_gates/rzz.py +++ b/qiskit/circuit/library/standard_gates/rzz.py @@ -72,7 +72,7 @@ class RZZGate(Gate): .. math:: - R_{ZZ}(\theta = \pi) = - Z \otimes Z + R_{ZZ}(\theta = \pi) = - i Z \otimes Z .. math:: From 2c3ddf1e6f81c497297b1d3531f20ea62b4cb620 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 3 Jul 2024 16:56:55 -0400 Subject: [PATCH 57/89] Optimize DAGCircuit.collect_1q_runs() filter function (#12719) In the recently merged #12650 a new rust function was added for the filter function of the collect_1q_runs() method's internal filter function. As part of this we need to do exclude any non-DAGOpNode dag nodes passed to the filter function. This was previously implemented using the pyo3 extract() method so that if the pyobject can be coerced into a DAGOpNode for later use we continue but if it errors we know the input object is not a DAGOpNode and return false. However, as we don't need to return the error to python we should be using downcast instead of extract (as per the pyo3 performance guide [1]) to avoid the error conversion cost. This was an oversight in #12650 and I only used extract() out of habit. [1] https://pyo3.rs/v0.22.0/performance#extract-versus-downcast --- crates/accelerate/src/euler_one_qubit_decomposer.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 8c3a87ce51ec..24c4f6e87c2a 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -1045,10 +1045,11 @@ fn matmul_1q(operator: &mut [[Complex64; 2]; 2], other: Array2) { } #[pyfunction] -pub fn collect_1q_runs_filter(py: Python, node: PyObject) -> bool { - let op_node = node.extract::>(py); +pub fn collect_1q_runs_filter(node: &Bound) -> bool { + let op_node = node.downcast::(); match op_node { - Ok(node) => { + Ok(bound_node) => { + let node = bound_node.borrow(); node.instruction.operation.num_qubits() == 1 && node.instruction.operation.num_clbits() == 0 && node From 63b459eb125949fb7602a8617b56c71e1500f0be Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 4 Jul 2024 11:15:10 +0200 Subject: [PATCH 58/89] set a dummy QISKIT_SETTINGS so tests do not interact with local env (#12463) * mock QISKIT_SETTINGS to avoid interfering with my own env * set a dummy QISKIT_SETTINGS to avoid interfering with local env * apply to all of them * revert --- test/utils/base.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/utils/base.py b/test/utils/base.py index ce9509709bad..98f2fae9b7ff 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -287,6 +287,17 @@ def setUp(self): self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) self.useFixture(fixtures.LoggerFixture(nuke_handlers=False, level=None)) + # a dummy setting config to make sure it does not intervene + # with the test runner environment. See https://github.com/Qiskit/qiskit/pull/12463 + self._mock_setting = unittest.mock.patch.dict( + os.environ, {"QISKIT_SETTINGS": "dummy_setting.conf"} + ) + self._mock_setting.start() + + def tearDown(self): + super().tearDown() + self._mock_setting.stop() + def dicts_almost_equal(dict1, dict2, delta=None, places=None, default_value=0): """Test if two dictionaries with numeric values are almost equal. From 722a5bc7ef627149d575d886e9796849b8a92589 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 4 Jul 2024 13:49:35 +0200 Subject: [PATCH 59/89] Oxidize `synth_cnot_count_full_pmh` (#12588) * port PMH synth to Rust * Raise an error if the section size is invalid Co-authored-by: Abdalla01001 Co-authored-by: Tarun-Kumar07 * Review comments of Shelly & Sasha --------- Co-authored-by: Abdalla01001 Co-authored-by: Tarun-Kumar07 --- crates/accelerate/src/synthesis/linear/mod.rs | 2 + crates/accelerate/src/synthesis/linear/pmh.rs | 195 ++++++++++++++++++ qiskit/synthesis/linear/cnot_synth.py | 117 ++--------- .../notes/oxidize-pmh-ec74e4002510eaad.yaml | 17 ++ .../python/synthesis/test_linear_synthesis.py | 36 ++++ 5 files changed, 272 insertions(+), 95 deletions(-) create mode 100644 crates/accelerate/src/synthesis/linear/pmh.rs create mode 100644 releasenotes/notes/oxidize-pmh-ec74e4002510eaad.yaml diff --git a/crates/accelerate/src/synthesis/linear/mod.rs b/crates/accelerate/src/synthesis/linear/mod.rs index b184a170fa5f..49dfeefd3869 100644 --- a/crates/accelerate/src/synthesis/linear/mod.rs +++ b/crates/accelerate/src/synthesis/linear/mod.rs @@ -14,6 +14,7 @@ use crate::QiskitError; use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2, PyReadwriteArray2}; use pyo3::prelude::*; +mod pmh; pub mod utils; #[pyfunction] @@ -186,5 +187,6 @@ pub fn linear(m: &Bound) -> PyResult<()> { 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))?; + m.add_wrapped(wrap_pyfunction!(pmh::synth_cnot_count_full_pmh))?; Ok(()) } diff --git a/crates/accelerate/src/synthesis/linear/pmh.rs b/crates/accelerate/src/synthesis/linear/pmh.rs new file mode 100644 index 000000000000..d7283fd580e4 --- /dev/null +++ b/crates/accelerate/src/synthesis/linear/pmh.rs @@ -0,0 +1,195 @@ +// 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 hashbrown::HashMap; +use ndarray::{s, Array1, Array2, ArrayViewMut2, Axis}; +use numpy::PyReadonlyArray2; +use smallvec::smallvec; +use std::cmp; + +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::operations::{Param, StandardGate}; +use qiskit_circuit::Qubit; + +use pyo3::prelude::*; + +use super::utils::_add_row_or_col; + +/// This helper function allows transposed access to a matrix. +fn _index(transpose: bool, i: usize, j: usize) -> (usize, usize) { + if transpose { + (j, i) + } else { + (i, j) + } +} + +fn _ceil_fraction(numerator: usize, denominator: usize) -> usize { + let mut fraction = numerator / denominator; + if numerator % denominator > 0 { + fraction += 1; + } + fraction +} + +/// This function is a helper function of the algorithm for optimal synthesis +/// of linear reversible circuits (the Patel–Markov–Hayes algorithm). It works +/// like gaussian elimination, except that it works a lot faster, and requires +/// fewer steps (and therefore fewer CNOTs). It takes the matrix and +/// splits it into sections of size section_size. Then it eliminates all non-zero +/// sub-rows within each section, which are the same as a non-zero sub-row +/// above. Once this has been done, it continues with normal gaussian elimination. +/// The benefit is that with small section sizes, most of the sub-rows will +/// be cleared in the first step, resulting in a factor ``section_size`` fewer row row operations +/// during Gaussian elimination. +/// +/// The algorithm is described in detail in the following paper +/// "Optimal synthesis of linear reversible circuits." +/// Patel, Ketan N., Igor L. Markov, and John P. Hayes. +/// Quantum Information & Computation 8.3 (2008): 282-294. +/// +/// Note: +/// This implementation tweaks the Patel, Markov, and Hayes algorithm by adding +/// a "back reduce" which adds rows below the pivot row with a high degree of +/// overlap back to it. The intuition is to avoid a high-weight pivot row +/// increasing the weight of lower rows. +/// +/// Args: +/// matrix: square matrix, describing a linear quantum circuit +/// section_size: the section size the matrix columns are divided into +/// +/// Returns: +/// A vector of CX locations (control, target) that need to be applied. +fn lower_cnot_synth( + mut matrix: ArrayViewMut2, + section_size: usize, + transpose: bool, +) -> Vec<(usize, usize)> { + // The vector of CNOTs to be applied. Called ``circuit`` here for consistency with the paper. + let mut circuit: Vec<(usize, usize)> = Vec::new(); + let cutoff = 1; + + // to apply to the transposed matrix, we can just set axis = 1 + let row_axis = if transpose { Axis(1) } else { Axis(0) }; + + // get number of columns (same as rows) and the number of sections + let n = matrix.raw_dim()[0]; + let num_sections = _ceil_fraction(n, section_size); + + // iterate over the columns + for section in 1..num_sections + 1 { + // store sub section row patterns here, which we saw already + let mut patterns: HashMap, usize> = HashMap::new(); + let section_slice = s![(section - 1) * section_size..cmp::min(section * section_size, n)]; + + // iterate over the rows (note we only iterate from the diagonal downwards) + for row_idx in (section - 1) * section_size..n { + // we need to keep track of the rows we saw already, called ``pattern`` here + let pattern: Array1 = matrix + .index_axis(row_axis, row_idx) + .slice(section_slice) + .to_owned(); + + // skip if the row is empty (i.e. all elements are false) + if pattern.iter().any(|&el| el) { + if patterns.contains_key(&pattern) { + // store CX location + circuit.push((patterns[&pattern], row_idx)); + // remove the row + _add_row_or_col(matrix.view_mut(), &transpose, patterns[&pattern], row_idx); + } else { + // if we have not seen this pattern yet, keep track of it + patterns.insert(pattern, row_idx); + } + } + } + + // gaussian eliminate the remainder of the section + for col_idx in (section - 1) * section_size..cmp::min(section * section_size, n) { + let mut diag_el = matrix[[col_idx, col_idx]]; + + for r in col_idx + 1..n { + if matrix[_index(transpose, r, col_idx)] { + if !diag_el { + _add_row_or_col(matrix.view_mut(), &transpose, r, col_idx); + circuit.push((r, col_idx)); + diag_el = true + } + _add_row_or_col(matrix.view_mut(), &transpose, col_idx, r); + circuit.push((col_idx, r)); + } + + // back-reduce to the pivot row: this one-line-magic checks if the logical AND + // between the two target rows has more ``true`` elements is larger than the cutoff + if matrix + .index_axis(row_axis, col_idx) + .iter() + .zip(matrix.index_axis(row_axis, r).iter()) + .map(|(&i, &j)| i & j) + .filter(|&x| x) + .count() + > cutoff + { + _add_row_or_col(matrix.view_mut(), &transpose, r, col_idx); + circuit.push((r, col_idx)); + } + } + } + } + circuit +} + +/// Synthesize a linear function, given by a boolean square matrix, into a circuit. +/// This function uses the Patel-Markov-Hayes algorithm, described in arXiv:quant-ph/0302002, +/// using section-wise elimination of the rows. +#[pyfunction] +#[pyo3(signature = (matrix, section_size=None))] +pub fn synth_cnot_count_full_pmh( + py: Python, + matrix: PyReadonlyArray2, + section_size: Option, +) -> PyResult { + let arrayview = matrix.as_array(); + let mut mat: Array2 = arrayview.to_owned(); + let num_qubits = mat.nrows(); // is a quadratic matrix + + // If given, use the user-specified input size. If None, we default to + // alpha * log2(num_qubits), as suggested in the paper, where the coefficient alpha + // is calibrated to minimize the upper bound on the number of row operations. + // In addition, we at least set a block size of 2, which, in practice, minimizes the bound + // until ~100 qubits. + let alpha = 0.56; + let blocksize = match section_size { + Some(section_size) => section_size as usize, + None => std::cmp::max(2, (alpha * (num_qubits as f64).log2()).floor() as usize), + }; + + // compute the synthesis for the lower triangular part of the matrix, and then + // apply it on the transposed part for the full synthesis + let lower_cnots = lower_cnot_synth(mat.view_mut(), blocksize, false); + let upper_cnots = lower_cnot_synth(mat.view_mut(), blocksize, true); + + // iterator over the gates + let instructions = upper_cnots + .iter() + .map(|(i, j)| (*j, *i)) + .chain(lower_cnots.into_iter().rev()) + .map(|(ctrl, target)| { + ( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit(ctrl as u32), Qubit(target as u32)], + ) + }); + + CircuitData::from_standard_gates(py, num_qubits as u32, instructions, Param::Float(0.0)) +} diff --git a/qiskit/synthesis/linear/cnot_synth.py b/qiskit/synthesis/linear/cnot_synth.py index 699523a7e75d..f900ff86d17e 100644 --- a/qiskit/synthesis/linear/cnot_synth.py +++ b/qiskit/synthesis/linear/cnot_synth.py @@ -17,33 +17,37 @@ """ from __future__ import annotations -import copy + import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.exceptions import QiskitError + +from qiskit._accelerate.synthesis.linear import synth_cnot_count_full_pmh as fast_pmh def synth_cnot_count_full_pmh( - state: list[list[bool]] | np.ndarray[bool], section_size: int = 2 + state: list[list[bool]] | np.ndarray[bool], section_size: int | None = None ) -> QuantumCircuit: - """ + r""" Synthesize linear reversible circuits for all-to-all architecture using Patel, Markov and Hayes method. This function is an implementation of the Patel, Markov and Hayes algorithm from [1] for optimal synthesis of linear reversible circuits for all-to-all architecture, - as specified by an :math:`n \\times n` matrix. + as specified by an :math:`n \times n` matrix. Args: - state: :math:`n \\times n` boolean invertible matrix, describing - the state of the input circuit + state: :math:`n \times n` boolean invertible matrix, describing + the state of the input circuit. section_size: The size of each section in the Patel–Markov–Hayes algorithm [1]. + If ``None`` it is chosen to be :math:`\max(2, \alpha\log_2(n))` with + :math:`\alpha = 0.56`, which approximately minimizes the upper bound on the number + of row operations given in [1] Eq. (3). Returns: - QuantumCircuit: a CX-only circuit implementing the linear transformation. + A CX-only circuit implementing the linear transformation. Raises: - QiskitError: when variable ``state`` isn't of type ``numpy.ndarray`` + ValueError: When ``section_size`` is larger than the number of columns. References: 1. Patel, Ketan N., Igor L. Markov, and John P. Hayes, @@ -51,92 +55,15 @@ def synth_cnot_count_full_pmh( Quantum Information & Computation 8.3 (2008): 282-294. `arXiv:quant-ph/0302002 [quant-ph] `_ """ - if not isinstance(state, (list, np.ndarray)): - raise QiskitError( - f"state should be of type list or numpy.ndarray, but was of the type {type(state)}" + normalized = np.asarray(state).astype(bool) + if section_size is not None and normalized.shape[1] < section_size: + raise ValueError( + f"The section_size ({section_size}) cannot be larger than the number of columns " + f"({normalized.shape[1]})." ) - state = np.array(state) - # Synthesize lower triangular part - [state, circuit_l] = _lwr_cnot_synth(state, section_size) - state = np.transpose(state) - # Synthesize upper triangular part - [state, circuit_u] = _lwr_cnot_synth(state, section_size) - circuit_l.reverse() - for i in circuit_u: - i.reverse() - # Convert the list into a circuit of C-NOT gates - circ = QuantumCircuit(state.shape[0]) - for i in circuit_u + circuit_l: - circ.cx(i[0], i[1]) - return circ - - -def _lwr_cnot_synth(state, section_size): - """ - This function is a helper function of the algorithm for optimal synthesis - of linear reversible circuits (the Patel–Markov–Hayes algorithm). It works - like gaussian elimination, except that it works a lot faster, and requires - fewer steps (and therefore fewer CNOTs). It takes the matrix "state" and - splits it into sections of size section_size. Then it eliminates all non-zero - sub-rows within each section, which are the same as a non-zero sub-row - above. Once this has been done, it continues with normal gaussian elimination. - The benefit is that with small section sizes (m), most of the sub-rows will - be cleared in the first step, resulting in a factor m fewer row row operations - during Gaussian elimination. - The algorithm is described in detail in the following paper - "Optimal synthesis of linear reversible circuits." - Patel, Ketan N., Igor L. Markov, and John P. Hayes. - Quantum Information & Computation 8.3 (2008): 282-294. - - Note: - This implementation tweaks the Patel, Markov, and Hayes algorithm by adding - a "back reduce" which adds rows below the pivot row with a high degree of - overlap back to it. The intuition is to avoid a high-weight pivot row - increasing the weight of lower rows. - - Args: - state (ndarray): n x n matrix, describing a linear quantum circuit - section_size (int): the section size the matrix columns are divided into - - Returns: - numpy.matrix: n by n matrix, describing the state of the output circuit - list: a k by 2 list of C-NOT operations that need to be applied - """ - circuit = [] - num_qubits = state.shape[0] - cutoff = 1 + # call Rust implementation with normalized input + circuit_data = fast_pmh(normalized, section_size) - # Iterate over column sections - for sec in range(1, int(np.floor(num_qubits / section_size) + 1)): - # Remove duplicate sub-rows in section sec - patt = {} - for row in range((sec - 1) * section_size, num_qubits): - sub_row_patt = copy.deepcopy(state[row, (sec - 1) * section_size : sec * section_size]) - if np.sum(sub_row_patt) == 0: - continue - if str(sub_row_patt) not in patt: - patt[str(sub_row_patt)] = row - else: - state[row, :] ^= state[patt[str(sub_row_patt)], :] - circuit.append([patt[str(sub_row_patt)], row]) - # Use gaussian elimination for remaining entries in column section - for col in range((sec - 1) * section_size, sec * section_size): - # Check if 1 on diagonal - diag_one = 1 - if state[col, col] == 0: - diag_one = 0 - # Remove ones in rows below column col - for row in range(col + 1, num_qubits): - if state[row, col] == 1: - if diag_one == 0: - state[col, :] ^= state[row, :] - circuit.append([row, col]) - diag_one = 1 - state[row, :] ^= state[col, :] - circuit.append([col, row]) - # Back reduce the pivot row using the current row - if sum(state[col, :] & state[row, :]) > cutoff: - state[col, :] ^= state[row, :] - circuit.append([row, col]) - return [state, circuit] + # construct circuit from the data + return QuantumCircuit._from_circuit_data(circuit_data) diff --git a/releasenotes/notes/oxidize-pmh-ec74e4002510eaad.yaml b/releasenotes/notes/oxidize-pmh-ec74e4002510eaad.yaml new file mode 100644 index 000000000000..b25adbec21f9 --- /dev/null +++ b/releasenotes/notes/oxidize-pmh-ec74e4002510eaad.yaml @@ -0,0 +1,17 @@ +--- +features_synthesis: + - | + Port :func:`.synth_cnot_full_pmh`, used to synthesize a linear function + into a CX network, to Rust. This produces approximately 44x speedup, + as measured on 100 qubit circuits. + - | + The function :func:`.synth_cnot_full_pmh` now allows choosing the + (heuristically) optimal ``section_size`` by setting it to ``None``. Then, a value is + chosen which attempts to minimize the upper bound on the number of CX gates, that is + :math:`\alpha \log_2(n)` where :math:`n` is the number of qubits and :math:`\alpha \approx 0.56`. +fixes: + - | + Fixed a bug in :func:`.synth_cnot_full_pmh` where providing a ``section_size`` that did + not divide the number of qubits without remainder could lead to wrong results. + Now any ``section_size`` (at most equal to the number of qubits) synthesizes the correct + circuit. For a (heuristically) optimal value, set ``section_size=None``. diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index bbfab20a30fc..3aec57647b22 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -139,6 +139,42 @@ def test_synth_lnn_kms(self, num_qubits): dist = abs(q0 - q1) self.assertEqual(dist, 1) + @data(None, 2, 4, 5) + def test_synth_full_pmh(self, section_size): + """Test the PMH synthesis on pseudo-random matrices.""" + num_qubits = 5 + rng = np.random.default_rng(1234) + num_trials = 10 + 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_count_full_pmh(mat, section_size) + self.assertEqual(LinearFunction(qc), LinearFunction(mat)) + + @data(5, 11) + def test_pmh_section_sizes(self, num_qubits): + """Test the PMH algorithm for different section sizes. + + Regression test of Qiskit/qiskit#12166. + """ + qc = QuantumCircuit(num_qubits) + for i in range(num_qubits - 1): + qc.cx(i, i + 1) + lin = LinearFunction(qc) + + for section_size in range(1, num_qubits + 1): + synthesized = synth_cnot_count_full_pmh(lin.linear, section_size=section_size) + with self.subTest(section_size=section_size): + self.assertEqual(lin, LinearFunction(synthesized)) + + def test_pmh_invalid_section_size(self): + """Test that a section size larger than the number of qubits raises an error.""" + mat = [[True, False], [True, False]] + with self.assertRaises(ValueError): + _ = synth_cnot_count_full_pmh(mat, section_size=3) + if __name__ == "__main__": unittest.main() From 7f1cc7fa980c848709262595bc0cf0c58fe0688f Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Fri, 5 Jul 2024 10:36:47 +0200 Subject: [PATCH 60/89] =?UTF-8?q?Revert=20"set=20a=20dummy=20QISKIT=5FSETT?= =?UTF-8?q?INGS=20so=20tests=20do=20not=20interact=20with=20local=20env=20?= =?UTF-8?q?(=E2=80=A6"=20(#12723)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 63b459eb125949fb7602a8617b56c71e1500f0be. --- test/utils/base.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/test/utils/base.py b/test/utils/base.py index 98f2fae9b7ff..ce9509709bad 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -287,17 +287,6 @@ def setUp(self): self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) self.useFixture(fixtures.LoggerFixture(nuke_handlers=False, level=None)) - # a dummy setting config to make sure it does not intervene - # with the test runner environment. See https://github.com/Qiskit/qiskit/pull/12463 - self._mock_setting = unittest.mock.patch.dict( - os.environ, {"QISKIT_SETTINGS": "dummy_setting.conf"} - ) - self._mock_setting.start() - - def tearDown(self): - super().tearDown() - self._mock_setting.stop() - def dicts_almost_equal(dict1, dict2, delta=None, places=None, default_value=0): """Test if two dictionaries with numeric values are almost equal. From bb60891a60ac20e5305a481418058e90ec31379f Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Fri, 5 Jul 2024 13:19:10 +0200 Subject: [PATCH 61/89] Simplify base TestCases (#12720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Merge base classes for testing * Update test/utils/base.py Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * 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> --- test/utils/base.py | 209 ++++++++++++++++++++++----------------------- 1 file changed, 100 insertions(+), 109 deletions(-) diff --git a/test/utils/base.py b/test/utils/base.py index ce9509709bad..e4dbd7f67072 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -14,7 +14,7 @@ """Base TestCases for the unit tests. -Implementors of unit tests for Terra are encouraged to subclass +Implementors of unit tests for Qiskit should subclass ``QiskitTestCase`` in order to take advantage of utility functions (for example, the environment variables for customizing different options), and the decorators in the ``decorators`` package. @@ -65,11 +65,97 @@ class BaseTestCase(unittest.TestCase): @enforce_subclasses_call(["setUp", "setUpClass", "tearDown", "tearDownClass"]) -class BaseQiskitTestCase(BaseTestCase): - """Additions for test cases for all Qiskit-family packages. +class QiskitTestCase(BaseTestCase): + """Additions for Qiskit test cases.""" - The additions here are intended for all packages, not just Terra. Terra-specific logic should - be in the Terra-specific classes.""" + @classmethod + def setUpClass(cls): + super().setUpClass() + # Set logging to file and stdout if the LOG_LEVEL envar is set. + cls.log = logging.getLogger(cls.__name__) + + if log_level := os.getenv("LOG_LEVEL"): + log_fmt = f"{cls.log.name}.%(funcName)s:%(levelname)s:%(asctime)s: %(message)s" + formatter = logging.Formatter(log_fmt) + file_handler = logging.FileHandler(f"{os.path.splitext(inspect.getfile(cls))[0]}.log") + file_handler.setFormatter(formatter) + cls.log.addHandler(file_handler) + + if os.getenv("STREAM_LOG"): + # Set up the stream handler. + stream_handler = logging.StreamHandler() + stream_handler.setFormatter(formatter) + cls.log.addHandler(stream_handler) + + # Set the logging level from the environment variable, defaulting + # to INFO if it is not a valid level. + level = logging._nameToLevel.get(log_level, logging.INFO) + cls.log.setLevel(level) + + 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( + "default", + category=DeprecationWarning, + # The `(?s)` magic is to force use of the `re.DOTALL` flag, because the Pandas message + # includes hard-break newlines all over the place. + message="(?s).*Pyarrow.*required dependency.*next major release of pandas", + module=r"seaborn(\..*)?", + ) + + # Safe to remove once https://github.com/Qiskit/qiskit-aer/pull/2179 is in a release version + # of Aer. + warnings.filterwarnings( + "default", + category=DeprecationWarning, + message="Treating CircuitInstruction as an iterable is deprecated", + module=r"qiskit_aer(\.[a-zA-Z0-9_]+)*", + ) + + allow_DeprecationWarning_modules = [ + "test.python.pulse.test_builder", + "test.python.pulse.test_block", + "test.python.quantum_info.operators.symplectic.test_legacy_pauli", + "qiskit.quantum_info.operators.pauli", + "pybobyqa", + "numba", + "qiskit.utils.measurement_error_mitigation", + "qiskit.circuit.library.standard_gates.x", + "qiskit.pulse.schedule", + "qiskit.pulse.instructions.instruction", + "qiskit.pulse.instructions.play", + "qiskit.pulse.library.parametric_pulses", + "qiskit.quantum_info.operators.symplectic.pauli", + ] + for mod in allow_DeprecationWarning_modules: + warnings.filterwarnings("default", category=DeprecationWarning, module=mod) + allow_DeprecationWarning_message = [ + r"elementwise comparison failed.*", + r"The jsonschema validation included in qiskit-terra.*", + r"The DerivativeBase.parameter_expression_grad method.*", + r"The property ``qiskit\.circuit\.bit\.Bit\.(register|index)`` is deprecated.*", + # Caused by internal scikit-learn scipy usage + r"The 'sym_pos' keyword is deprecated and should be replaced by using", + ] + for msg in allow_DeprecationWarning_message: + warnings.filterwarnings("default", category=DeprecationWarning, message=msg) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -99,6 +185,13 @@ def tearDown(self): ) self.__teardown_called = True + # Reset the default providers, as in practice they acts as a singleton + # due to importing the instances from the top-level qiskit namespace. + from qiskit.providers.basic_provider import BasicProvider + + with self.assertWarns(DeprecationWarning): + BasicProvider()._backends = BasicProvider()._verify_backends() + def assertQuantumCircuitEqual(self, qc1, qc2, msg=None): """Extra assertion method to give a better error message when two circuits are unequal.""" if qc1 == qc2: @@ -165,112 +258,10 @@ def set_parallel_env(name, value): os.environ["QISKIT_PARALLEL"] = parallel_default -class QiskitTestCase(BaseQiskitTestCase): - """Terra-specific extra functionality for test cases.""" - - def tearDown(self): - super().tearDown() - # Reset the default providers, as in practice they acts as a singleton - # due to importing the instances from the top-level qiskit namespace. - from qiskit.providers.basic_provider import BasicProvider - - with self.assertWarns(DeprecationWarning): - BasicProvider()._backends = BasicProvider()._verify_backends() - - @classmethod - def setUpClass(cls): - super().setUpClass() - # Set logging to file and stdout if the LOG_LEVEL envar is set. - cls.log = logging.getLogger(cls.__name__) - - if log_level := os.getenv("LOG_LEVEL"): - log_fmt = f"{cls.log.name}.%(funcName)s:%(levelname)s:%(asctime)s: %(message)s" - formatter = logging.Formatter(log_fmt) - file_handler = logging.FileHandler(f"{os.path.splitext(inspect.getfile(cls))[0]}.log") - file_handler.setFormatter(formatter) - cls.log.addHandler(file_handler) - - if os.getenv("STREAM_LOG"): - # Set up the stream handler. - stream_handler = logging.StreamHandler() - stream_handler.setFormatter(formatter) - cls.log.addHandler(stream_handler) - - # Set the logging level from the environment variable, defaulting - # to INFO if it is not a valid level. - level = logging._nameToLevel.get(log_level, logging.INFO) - cls.log.setLevel(level) - - 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( - "default", - category=DeprecationWarning, - # The `(?s)` magic is to force use of the `re.DOTALL` flag, because the Pandas message - # includes hard-break newlines all over the place. - message="(?s).*Pyarrow.*required dependency.*next major release of pandas", - module=r"seaborn(\..*)?", - ) - - # Safe to remove once https://github.com/Qiskit/qiskit-aer/pull/2179 is in a release version - # of Aer. - warnings.filterwarnings( - "default", - category=DeprecationWarning, - message="Treating CircuitInstruction as an iterable is deprecated", - module=r"qiskit_aer(\.[a-zA-Z0-9_]+)*", - ) - - allow_DeprecationWarning_modules = [ - "test.python.pulse.test_builder", - "test.python.pulse.test_block", - "test.python.quantum_info.operators.symplectic.test_legacy_pauli", - "qiskit.quantum_info.operators.pauli", - "pybobyqa", - "numba", - "qiskit.utils.measurement_error_mitigation", - "qiskit.circuit.library.standard_gates.x", - "qiskit.pulse.schedule", - "qiskit.pulse.instructions.instruction", - "qiskit.pulse.instructions.play", - "qiskit.pulse.library.parametric_pulses", - "qiskit.quantum_info.operators.symplectic.pauli", - ] - for mod in allow_DeprecationWarning_modules: - warnings.filterwarnings("default", category=DeprecationWarning, module=mod) - allow_DeprecationWarning_message = [ - r"elementwise comparison failed.*", - r"The jsonschema validation included in qiskit-terra.*", - r"The DerivativeBase.parameter_expression_grad method.*", - r"The property ``qiskit\.circuit\.bit\.Bit\.(register|index)`` is deprecated.*", - # Caused by internal scikit-learn scipy usage - r"The 'sym_pos' keyword is deprecated and should be replaced by using", - ] - for msg in allow_DeprecationWarning_message: - warnings.filterwarnings("default", category=DeprecationWarning, message=msg) - - class FullQiskitTestCase(QiskitTestCase): - """Terra-specific further additions for test cases, if ``testtools`` is available. + """further additions for Qiskit test cases, if ``testtools`` is available. - It is not normally safe to derive from this class by name; on import, Terra checks if the + It is not normally safe to derive from this class by name; on import, Qiskit checks if the necessary packages are available, and binds this class to the name :obj:`~QiskitTestCase` if so. If you derive directly from it, you may try and instantiate the class without satisfying its dependencies.""" From ee41b187b8c3033f44dc3a78fd0c44cc255bb741 Mon Sep 17 00:00:00 2001 From: Rafaella Vale Date: Sun, 7 Jul 2024 06:33:44 +0000 Subject: [PATCH 62/89] Improved MCXVChain with dirty auxiliary qubits (#9687) * improved mcxvchain with dirty aux qubits Co-authored-by: thiagom123 Co-authored-by: IsmaelCesar Co-authored-by: Israel F. Araujo Co-authored-by: Adenilton Silva <7927558+adjs@users.noreply.github.com> * tests for improved mcxvchain with dirty aux qubits Co-authored-by: thiagom123 Co-authored-by: IsmaelCesar Co-authored-by: Israel F. Araujo Co-Authored-By: Adenilton Silva <7927558+adjs@users.noreply.github.com> * black * lint * Update x.py * Update test_controlled_gate.py fix test_mcxvchain_dirty_ancilla_relative_phase reduce number of controls in test_mcxvchain_dirty_ancilla_action_only and test_mcxvchain_dirty_ancilla_relative_phase * Update test_controlled_gate.py * release notes * removing non existend module * removing U1 and U2 gates MCXVChain * black * using the quantum_circuit.operation(qubit_indices) notation * mcx * comments * lint * rearranging transpile import --------- Co-authored-by: thiagom123 Co-authored-by: IsmaelCesar Co-authored-by: Israel F. Araujo Co-authored-by: Adenilton Silva <7927558+adjs@users.noreply.github.com> --- qiskit/circuit/library/standard_gates/x.py | 151 +++++++++++------- ...hain_dirty_auxiliary-5ea4037557209f6e.yaml | 10 ++ test/python/circuit/test_controlled_gate.py | 61 +++++++ 3 files changed, 167 insertions(+), 55 deletions(-) create mode 100644 releasenotes/notes/mcxvchain_dirty_auxiliary-5ea4037557209f6e.yaml diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index cd4a61963823..a08e4a55a960 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -1409,6 +1409,8 @@ def __new__( duration=None, unit="dt", _base_label=None, + relative_phase: bool = False, # pylint: disable=unused-argument + action_only: bool = False, # pylint: disable=unused-argument ): """Create a new MCX instance. @@ -1434,7 +1436,24 @@ def __init__( duration=None, unit="dt", _base_label=None, + relative_phase: bool = False, + action_only: bool = False, ): + """ + Args: + dirty_ancillas: when set to ``True``, the method applies an optimized multicontrolled-X gate + up to a relative phase using dirty ancillary qubits with the properties of lemmas 7 and 8 + from arXiv:1501.06911, with at most 8*k - 6 CNOT gates. + For k within the range {1, ..., ceil(n/2)}. And for n representing the total number of + qubits. + relative_phase: when set to ``True``, the method applies the optimized multicontrolled-X gate + up to a relative phase, in a way that, by lemma 7 of arXiv:1501.06911, the relative + phases of the ``action part`` cancel out with the phases of the ``reset part``. + + action_only: when set to ``True``, the method applies only the action part of lemma 8 + from arXiv:1501.06911. + + """ super().__init__( num_ctrl_qubits, label=label, @@ -1445,6 +1464,9 @@ def __init__( unit=unit, ) self._dirty_ancillas = dirty_ancillas + self._relative_phase = relative_phase + self._action_only = action_only + super().__init__(num_ctrl_qubits, label=label, ctrl_state=ctrl_state, _name="mcx_vchain") def inverse(self, annotated: bool = False): """Invert this gate. The MCX is its own inverse. @@ -1462,6 +1484,8 @@ def inverse(self, annotated: bool = False): num_ctrl_qubits=self.num_ctrl_qubits, dirty_ancillas=self._dirty_ancillas, ctrl_state=self.ctrl_state, + relative_phase=self._relative_phase, + action_only=self._action_only, ) @staticmethod @@ -1473,8 +1497,6 @@ def _define(self): """Define the MCX gate using a V-chain of CX gates.""" # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit - from .u1 import U1Gate - from .u2 import U2Gate q = QuantumRegister(self.num_qubits, name="q") qc = QuantumCircuit(q, name=self.name) @@ -1482,64 +1504,83 @@ def _define(self): q_target = q[self.num_ctrl_qubits] q_ancillas = q[self.num_ctrl_qubits + 1 :] - definition = [] - if self._dirty_ancillas: - i = self.num_ctrl_qubits - 3 - ancilla_pre_rule = [ - (U2Gate(0, numpy.pi), [q_target], []), - (CXGate(), [q_target, q_ancillas[i]], []), - (U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), - (CXGate(), [q_controls[-1], q_ancillas[i]], []), - (U1Gate(numpy.pi / 4), [q_ancillas[i]], []), - (CXGate(), [q_target, q_ancillas[i]], []), - (U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), - (CXGate(), [q_controls[-1], q_ancillas[i]], []), - (U1Gate(numpy.pi / 4), [q_ancillas[i]], []), - ] - for inst in ancilla_pre_rule: - definition.append(inst) + if self.num_ctrl_qubits < 3: + qc.mcx(q_controls, q_target) + elif not self._relative_phase and self.num_ctrl_qubits == 3: + qc._append(C3XGate(), [*q_controls, q_target], []) + else: + num_ancillas = self.num_ctrl_qubits - 2 + targets = [q_target] + q_ancillas[:num_ancillas][::-1] + + for j in range(2): + for i in range(self.num_ctrl_qubits): # action part + if i < self.num_ctrl_qubits - 2: + if targets[i] != q_target or self._relative_phase: + # gate cancelling + + # cancel rightmost gates of action part + # with leftmost gates of reset part + if self._relative_phase and targets[i] == q_target and j == 1: + qc.cx(q_ancillas[num_ancillas - i - 1], targets[i]) + qc.t(targets[i]) + qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i]) + qc.tdg(targets[i]) + qc.h(targets[i]) + else: + qc.h(targets[i]) + qc.t(targets[i]) + qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i]) + qc.tdg(targets[i]) + qc.cx(q_ancillas[num_ancillas - i - 1], targets[i]) + else: + controls = [ + q_controls[self.num_ctrl_qubits - i - 1], + q_ancillas[num_ancillas - i - 1], + ] + + qc.ccx(controls[0], controls[1], targets[i]) + else: + # implements an optimized toffoli operation + # up to a diagonal gate, akin to lemma 6 of arXiv:1501.06911 + qc.h(targets[i]) + qc.t(targets[i]) + qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i]) + qc.tdg(targets[i]) + qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i]) + qc.t(targets[i]) + qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i]) + qc.tdg(targets[i]) + qc.h(targets[i]) + + break + + for i in range(num_ancillas - 1): # reset part + qc.cx(q_ancillas[i], q_ancillas[i + 1]) + qc.t(q_ancillas[i + 1]) + qc.cx(q_controls[2 + i], q_ancillas[i + 1]) + qc.tdg(q_ancillas[i + 1]) + qc.h(q_ancillas[i + 1]) + + if self._action_only: + qc.ccx(q_controls[-1], q_ancillas[-1], q_target) + + break + else: + qc.rccx(q_controls[0], q_controls[1], q_ancillas[0]) + i = 0 + for j in range(2, self.num_ctrl_qubits - 1): + qc.rccx(q_controls[j], q_ancillas[i], q_ancillas[i + 1]) - for j in reversed(range(2, self.num_ctrl_qubits - 1)): - definition.append( - (RCCXGate(), [q_controls[j], q_ancillas[i - 1], q_ancillas[i]], []) - ) - i -= 1 + i += 1 - definition.append((RCCXGate(), [q_controls[0], q_controls[1], q_ancillas[0]], [])) - i = 0 - for j in range(2, self.num_ctrl_qubits - 1): - definition.append((RCCXGate(), [q_controls[j], q_ancillas[i], q_ancillas[i + 1]], [])) - i += 1 + qc.ccx(q_controls[-1], q_ancillas[i], q_target) - if self._dirty_ancillas: - ancilla_post_rule = [ - (U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), - (CXGate(), [q_controls[-1], q_ancillas[i]], []), - (U1Gate(numpy.pi / 4), [q_ancillas[i]], []), - (CXGate(), [q_target, q_ancillas[i]], []), - (U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), - (CXGate(), [q_controls[-1], q_ancillas[i]], []), - (U1Gate(numpy.pi / 4), [q_ancillas[i]], []), - (CXGate(), [q_target, q_ancillas[i]], []), - (U2Gate(0, numpy.pi), [q_target], []), - ] - for inst in ancilla_post_rule: - definition.append(inst) - else: - definition.append((CCXGate(), [q_controls[-1], q_ancillas[i], q_target], [])) + for j in reversed(range(2, self.num_ctrl_qubits - 1)): + qc.rccx(q_controls[j], q_ancillas[i - 1], q_ancillas[i]) - for j in reversed(range(2, self.num_ctrl_qubits - 1)): - definition.append((RCCXGate(), [q_controls[j], q_ancillas[i - 1], q_ancillas[i]], [])) - i -= 1 - definition.append((RCCXGate(), [q_controls[0], q_controls[1], q_ancillas[i]], [])) + i -= 1 - if self._dirty_ancillas: - for i, j in enumerate(list(range(2, self.num_ctrl_qubits - 1))): - definition.append( - (RCCXGate(), [q_controls[j], q_ancillas[i], q_ancillas[i + 1]], []) - ) + qc.rccx(q_controls[0], q_controls[1], q_ancillas[i]) - for instr, qargs, cargs in definition: - qc._append(instr, qargs, cargs) self.definition = qc diff --git a/releasenotes/notes/mcxvchain_dirty_auxiliary-5ea4037557209f6e.yaml b/releasenotes/notes/mcxvchain_dirty_auxiliary-5ea4037557209f6e.yaml new file mode 100644 index 000000000000..7a4850370f7f --- /dev/null +++ b/releasenotes/notes/mcxvchain_dirty_auxiliary-5ea4037557209f6e.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + :class:`.MCXVChain` has two new Boolean parameters `relative_phase` and `action_only`. + If `action_only` the circuit does not clean the dirty qubits. If `relative_phase` + the gate is implemented up to a global phase. Both parameters are used to optimize the + decomposition of MCXVChain. +fixes: + - | + :class:`.MCXVChain` with k controls and k-2 dirty auxiliary qubits now requires 8k-6 cx gates. diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index f26ab987f4fd..c845077d32dc 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -798,6 +798,67 @@ def test_mcx_gates(self, num_ctrl_qubits): statevector = corrected np.testing.assert_array_almost_equal(statevector.real, reference) + @data(5, 10, 15) + def test_mcxvchain_dirty_ancilla_cx_count(self, num_ctrl_qubits): + """Test if cx count of the v-chain mcx with dirty ancilla + is less than upper bound.""" + from qiskit import transpile + + mcx_vchain = MCXVChain(num_ctrl_qubits, dirty_ancillas=True) + qc = QuantumCircuit(mcx_vchain.num_qubits) + + qc.append(mcx_vchain, list(range(mcx_vchain.num_qubits))) + + tr_mcx_vchain = transpile(qc, basis_gates=["u", "cx"]) + cx_count = tr_mcx_vchain.count_ops()["cx"] + + self.assertLessEqual(cx_count, 8 * num_ctrl_qubits - 6) + + def test_mcxvchain_dirty_ancilla_action_only(self): + """Test the v-chain mcx with dirty auxiliary qubits + with gate cancelling with mirrored circuit.""" + + num_ctrl_qubits = 5 + + gate = MCXVChain(num_ctrl_qubits, dirty_ancillas=True) + gate_with_cancelling = MCXVChain(num_ctrl_qubits, dirty_ancillas=True, action_only=True) + + num_qubits = gate.num_qubits + ref_circuit = QuantumCircuit(num_qubits) + circuit = QuantumCircuit(num_qubits) + + ref_circuit.append(gate, list(range(num_qubits)), []) + ref_circuit.h(num_ctrl_qubits) + ref_circuit.append(gate, list(range(num_qubits)), []) + + circuit.append(gate_with_cancelling, list(range(num_qubits)), []) + circuit.h(num_ctrl_qubits) + circuit.append(gate_with_cancelling.inverse(), list(range(num_qubits)), []) + + self.assertTrue(matrix_equal(Operator(circuit).data, Operator(ref_circuit).data)) + + def test_mcxvchain_dirty_ancilla_relative_phase(self): + """Test the v-chain mcx with dirty auxiliary qubits + with only relative phase Toffoli gates.""" + num_ctrl_qubits = 5 + + gate = MCXVChain(num_ctrl_qubits, dirty_ancillas=True) + gate_relative_phase = MCXVChain(num_ctrl_qubits, dirty_ancillas=True, relative_phase=True) + + num_qubits = gate.num_qubits + 1 + ref_circuit = QuantumCircuit(num_qubits) + circuit = QuantumCircuit(num_qubits) + + ref_circuit.append(gate, list(range(num_qubits - 1)), []) + ref_circuit.h(num_qubits - 1) + ref_circuit.append(gate, list(range(num_qubits - 1)), []) + + circuit.append(gate_relative_phase, list(range(num_qubits - 1)), []) + circuit.h(num_qubits - 1) + circuit.append(gate_relative_phase.inverse(), list(range(num_qubits - 1)), []) + + self.assertTrue(matrix_equal(Operator(circuit).data, Operator(ref_circuit).data)) + @data(1, 2, 3, 4) def test_inverse_x(self, num_ctrl_qubits): """Test inverting the controlled X gate.""" From e11c4c1bbbe905298ba10121a836fc637fa67da4 Mon Sep 17 00:00:00 2001 From: abbycross Date: Sun, 7 Jul 2024 03:56:01 -0400 Subject: [PATCH 63/89] Minor updates to the readme language (#10701) * capitalize unless in code font * so many "quantum"s in one sentence * tighten text * ambiguous "them" * code review --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b84e8107f762..babf5756f5de 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ **Qiskit** is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives. -This library is the core component of Qiskit, which contains the building blocks for creating and working with quantum circuits, quantum operators, and primitive functions (sampler and estimator). -It also contains a transpiler that supports optimizing quantum circuits and a quantum information toolbox for creating advanced quantum operators. +This library is the core component of Qiskit, which contains the building blocks for creating and working with quantum circuits, quantum operators, and primitive functions (Sampler and Estimator). +It also contains a transpiler that supports optimizing quantum circuits, and a quantum information toolbox for creating advanced operators. For more details on how to use Qiskit, refer to the documentation located here: @@ -91,12 +91,12 @@ print(f" > Expectation values: {result.values}") Running this will give the outcome `4`. For fun, try to assign a value of +/- 1 to each single-qubit operator X and Y and see if you can achieve this outcome. (Spoiler alert: this is not possible!) -Using the Qiskit-provided `qiskit.primitives.Sampler` and `qiskit.primitives.Estimator` will not take you very far. The power of quantum computing cannot be simulated -on classical computers and you need to use real quantum hardware to scale to larger quantum circuits. However, running a quantum -circuit on hardware requires rewriting them to the basis gates and connectivity of the quantum hardware. -The tool that does this is the [transpiler](https://docs.quantum.ibm.com/api/qiskit/transpiler) -and Qiskit includes transpiler passes for synthesis, optimization, mapping, and scheduling. However, it also includes a -default compiler which works very well in most examples. The following code will map the example circuit to the `basis_gates = ['cz', 'sx', 'rz']` and a linear chain of qubits $0 \rightarrow 1 \rightarrow 2$ with the `coupling_map =[[0, 1], [1, 2]]`. +Using the Qiskit-provided `qiskit.primitives.Sampler` and `qiskit.primitives.Estimator` will not take you very far. +The power of quantum computing cannot be simulated on classical computers and you need to use real quantum hardware to scale to larger quantum circuits. +However, running a quantum circuit on hardware requires rewriting to the basis gates and connectivity of the quantum hardware. +The tool that does this is the [transpiler](https://docs.quantum.ibm.com/api/qiskit/transpiler), and Qiskit includes transpiler passes for synthesis, optimization, mapping, and scheduling. +However, it also includes a default compiler, which works very well in most examples. +The following code will map the example circuit to the `basis_gates = ['cz', 'sx', 'rz']` and a linear chain of qubits $0 \rightarrow 1 \rightarrow 2$ with the `coupling_map =[[0, 1], [1, 2]]`. ```python from qiskit import transpile From 865081e4afbb08225f7f12a3b55a0322e4574f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:53:10 +0200 Subject: [PATCH 64/89] Remove DenseLayout from default pipeline (#12731) Co-authored-by: Jake Lishman --- qiskit/transpiler/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 12d9b9056b80..d4aad32cf7e4 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -650,8 +650,6 @@ .. image:: /source_images/mapping.png - - By default, qiskit will do this mapping for you. The choice of mapping depends on the properties of the circuit, the particular device you are targeting, and the optimization level that is chosen. The choice of initial layout is extremely important for minimizing the @@ -684,10 +682,12 @@ :class:`~.SabreLayout` is used to select a layout if a perfect layout isn't found for optimization levels 1, 2, and 3. - :class:`~.TrivialLayout`: Always used for the layout at optimization level 0. + +There are other passes than can be used for the heuristic stage, but are not included in the default +pipeline, such as: + - :class:`~.DenseLayout`: Finds the sub-graph of the device with greatest connectivity - that has the same number of qubits as the circuit. Used for - optimization level 1 if there are control flow operations (such as - :class:`~.IfElseOp`) present in the circuit. + that has the same number of qubits as the circuit. Let's see what layouts are automatically picked at various optimization levels. The circuits returned by :func:`qiskit.compiler.transpile` are annotated with this initial layout information, From d19224cb38cecd1b5467677a63086679cc21de49 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 8 Jul 2024 15:45:45 +0200 Subject: [PATCH 65/89] Add ``PySequenceIndex::convert_index`` to convert Python-like indices to unsigned (#12716) * Implement convert_index This allows to convert a Python-like signed index into an unsigned index suitable in Rust notation. * Jake's comments - refactor PySeqIndex.with_len to use convert_idx - cleanup the tests --- .../src/synthesis/permutation/utils.rs | 19 ++---- crates/circuit/src/slice.rs | 64 +++++++++++++++---- 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/crates/accelerate/src/synthesis/permutation/utils.rs b/crates/accelerate/src/synthesis/permutation/utils.rs index 47a9e1c3a7a9..975635fbf8da 100644 --- a/crates/accelerate/src/synthesis/permutation/utils.rs +++ b/crates/accelerate/src/synthesis/permutation/utils.rs @@ -15,7 +15,7 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use std::vec::Vec; -use qiskit_circuit::slice::{PySequenceIndex, PySequenceIndexError, SequenceIndex}; +use qiskit_circuit::slice::PySequenceIndex; pub fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { let n = pattern.len(); @@ -120,11 +120,8 @@ pub fn pattern_to_cycles(pattern: &ArrayView1) -> Vec> { /// Periodic (or Python-like) access to a vector. /// Util used below in ``decompose_cycles``. #[inline] -fn pget(vec: &[usize], index: isize) -> Result { - let SequenceIndex::Int(wrapped) = PySequenceIndex::Int(index).with_len(vec.len())? else { - unreachable!() - }; - Ok(vec[wrapped]) +fn pget(vec: &[usize], index: isize) -> usize { + vec[PySequenceIndex::convert_idx(index, vec.len()).unwrap()] } /// Given a disjoint cycle decomposition of a permutation pattern (see the function @@ -138,16 +135,10 @@ pub fn decompose_cycles(cycles: &Vec>) -> Vec<(usize, usize)> { let length = cycle.len() as isize; for idx in 0..(length - 1) / 2 { - swaps.push(( - pget(cycle, idx - 1).unwrap(), - pget(cycle, length - 3 - idx).unwrap(), - )); + swaps.push((pget(cycle, idx - 1), pget(cycle, length - 3 - idx))); } for idx in 0..length / 2 { - swaps.push(( - pget(cycle, idx - 1).unwrap(), - pget(cycle, length - 2 - idx).unwrap(), - )); + swaps.push((pget(cycle, idx - 1), pget(cycle, length - 2 - idx))); } } diff --git a/crates/circuit/src/slice.rs b/crates/circuit/src/slice.rs index 056adff0a282..d62e47f03ef3 100644 --- a/crates/circuit/src/slice.rs +++ b/crates/circuit/src/slice.rs @@ -46,17 +46,8 @@ impl<'py> PySequenceIndex<'py> { 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)) + let wrapped_index = PySequenceIndex::convert_idx(*index, len)?; + Ok(SequenceIndex::Int(wrapped_index)) } PySequenceIndex::Slice(slice) => { let indices = slice @@ -80,6 +71,22 @@ impl<'py> PySequenceIndex<'py> { } } } + + /// Given an integer (which may be negative) get a valid unsigned index for a sequence. + pub fn convert_idx(index: isize, length: usize) -> Result { + let wrapped_index = if index >= 0 { + let index = index as usize; + if index >= length { + return Err(PySequenceIndexError::OutOfRange); + } + index + } else { + length + .checked_sub(index.unsigned_abs()) + .ok_or(PySequenceIndexError::OutOfRange)? + }; + Ok(wrapped_index) + } } /// Error type for problems encountered when calling methods on `PySequenceIndex`. @@ -165,8 +172,8 @@ impl SequenceIndex { } } - // Get an iterator over the contained indices that is guaranteed to iterate from the highest - // index to the lowest. + /// 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()) } @@ -372,4 +379,35 @@ mod test { ); } } + + /// Test SequenceIndex::from_int correctly handles positive and negative indices + #[test] + fn convert_py_idx() { + let cases = [ + (2, 5, 2), // (index, sequence length, expected result) + (-2, 5, 3), + (0, 2, 0), + ]; + + for (py_index, length, expected) in cases { + let index = PySequenceIndex::convert_idx(py_index, length).unwrap(); + assert_eq!(index, expected, "Expected {} but got {}", expected, index); + } + } + + /// Test that out-of-range errors are returned as expected. + #[test] + fn bad_convert_py_idx() { + let cases = [ + (5, 5), // (index, sequence length) + (-6, 5), + ]; + + for (py_index, length) in cases { + assert!(matches!( + PySequenceIndex::convert_idx(py_index, length).unwrap_err(), + PySequenceIndexError::OutOfRange, + )); + } + } } From e5533fde0a83f78d889cfc95616688fac969fc70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:30:19 +0000 Subject: [PATCH 66/89] Bump pypa/cibuildwheel from 2.19.1 to 2.19.2 in the github_actions group (#12733) Bumps the github_actions group with 1 update: [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel). Updates `pypa/cibuildwheel` from 2.19.1 to 2.19.2 - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.19.1...v2.19.2) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github_actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/wheels.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 7c29f1376e46..a33d025affc7 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -30,7 +30,7 @@ jobs: with: components: llvm-tools-preview - name: Build wheels - uses: pypa/cibuildwheel@v2.19.1 + uses: pypa/cibuildwheel@v2.19.2 env: CIBW_BEFORE_BUILD: 'bash ./tools/build_pgo.sh /tmp/pgo-data/merged.profdata' CIBW_BEFORE_BUILD_WINDOWS: 'bash ./tools/build_pgo.sh /tmp/pgo-data/merged.profdata && cp /tmp/pgo-data/merged.profdata ~/.' @@ -58,7 +58,7 @@ jobs: with: components: llvm-tools-preview - name: Build wheels - uses: pypa/cibuildwheel@v2.19.1 + uses: pypa/cibuildwheel@v2.19.2 env: CIBW_BEFORE_ALL: rustup target add aarch64-apple-darwin CIBW_BUILD: cp38-macosx_universal2 cp38-macosx_arm64 @@ -87,7 +87,7 @@ jobs: with: components: llvm-tools-preview - name: Build wheels - uses: pypa/cibuildwheel@v2.19.1 + uses: pypa/cibuildwheel@v2.19.2 env: CIBW_SKIP: 'pp* cp36-* cp37-* *musllinux* *amd64 *x86_64' - uses: actions/upload-artifact@v4 @@ -133,7 +133,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.19.1 + uses: pypa/cibuildwheel@v2.19.2 env: CIBW_ARCHS_LINUX: s390x CIBW_TEST_SKIP: "cp*" @@ -167,7 +167,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.19.1 + uses: pypa/cibuildwheel@v2.19.2 env: CIBW_ARCHS_LINUX: ppc64le CIBW_TEST_SKIP: "cp*" @@ -201,7 +201,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.19.1 + uses: pypa/cibuildwheel@v2.19.2 env: CIBW_ARCHS_LINUX: aarch64 - uses: actions/upload-artifact@v4 From 008dde3d56a5e9fa560519be6b76748f6e3a4532 Mon Sep 17 00:00:00 2001 From: Max Rossmannek Date: Mon, 8 Jul 2024 18:25:34 +0200 Subject: [PATCH 67/89] Improve the performance of the `ProductFormula` synthesizers (#12724) * [WIP] adds the output argument to the internal atomic evolution * meta: modernize type hints * refactor: change callable structure of atomic evolution This changes the structure of the `atomic_evolution` callable in the `ProductFormula` synthesis class. This is motivated by the significant performance improvements that can be obtained by appending to the existing circuit directly rather than building out individual evolution circuits and iteratively composing them. * refactor: deprecate the legacy atomic_evolution signature * refactor: add the wrap argument to ProductFormula This can be used to recover the previous behavior in which the single individually evolved Pauli terms get wrapped into gate objects. * fix: insert the missing barriers between LieTrotter repetitions * refactor: align SuzukiTrotter and LieTrotter * Propoagate deprecation notice * fix: the labels of wrapped Pauli evolutions * fix: respect the insert_barriers setting * docs: add a release note * Apply suggestions from code review Co-authored-by: Julien Gacon * fix: missing `wrap` forward in SuzukiTrotter * docs: improve documentation of the `atomic_evolution` argument Co-authored-by: Julien Gacon * docs: also document the deprecated form of `atomic_evolution` * docs: call out ProductFormula docs in release note * refactor: change to PendingDeprecationWarning * refactor: explicitly convert to Gate when wrapping This is slightly faster than the `.compose`-based operation done previously as it performs fewer checks. Thanks to @jakelishman for the suggestion offline. * Update qiskit/synthesis/evolution/lie_trotter.py Co-authored-by: Julien Gacon * docs: update after pending deprecation --------- Co-authored-by: Julien Gacon --- qiskit/circuit/quantumcircuit.py | 7 +- .../evolution/evolution_synthesis.py | 6 +- qiskit/synthesis/evolution/lie_trotter.py | 65 +++++-- qiskit/synthesis/evolution/product_formula.py | 166 ++++++++++++------ qiskit/synthesis/evolution/qdrift.py | 50 ++++-- qiskit/synthesis/evolution/suzuki_trotter.py | 76 ++++---- ...formula-improvements-1bc40650151cf107.yaml | 25 +++ .../circuit/library/test_evolution_gate.py | 45 ++++- 8 files changed, 315 insertions(+), 125 deletions(-) create mode 100644 releasenotes/notes/product-formula-improvements-1bc40650151cf107.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 6d41e6fdcd25..e2acf9dc2026 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1596,11 +1596,12 @@ def inverse(self, annotated: bool = False) -> "QuantumCircuit": ) return inverse_circ - def repeat(self, reps: int) -> "QuantumCircuit": + def repeat(self, reps: int, *, insert_barriers: bool = False) -> "QuantumCircuit": """Repeat this circuit ``reps`` times. Args: reps (int): How often this circuit should be repeated. + insert_barriers (bool): Whether to include barriers between circuit repetitions. Returns: QuantumCircuit: A circuit containing ``reps`` repetitions of this circuit. @@ -1616,8 +1617,10 @@ def repeat(self, reps: int) -> "QuantumCircuit": inst: Instruction = self.to_gate() except QiskitError: inst = self.to_instruction() - for _ in range(reps): + for i in range(reps): repeated_circ._append(inst, self.qubits, self.clbits) + if insert_barriers and i != reps - 1: + repeated_circ.barrier() return repeated_circ diff --git a/qiskit/synthesis/evolution/evolution_synthesis.py b/qiskit/synthesis/evolution/evolution_synthesis.py index f904f457e49b..9bf7ff35f60d 100644 --- a/qiskit/synthesis/evolution/evolution_synthesis.py +++ b/qiskit/synthesis/evolution/evolution_synthesis.py @@ -12,8 +12,10 @@ """Evolution synthesis.""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Any, Dict +from typing import Any class EvolutionSynthesis(ABC): @@ -32,7 +34,7 @@ def synthesize(self, evolution): raise NotImplementedError @property - def settings(self) -> Dict[str, Any]: + def settings(self) -> dict[str, Any]: """Return the settings in a dictionary, which can be used to reconstruct the object. Returns: diff --git a/qiskit/synthesis/evolution/lie_trotter.py b/qiskit/synthesis/evolution/lie_trotter.py index 6d73e077bf90..1a01675a6782 100644 --- a/qiskit/synthesis/evolution/lie_trotter.py +++ b/qiskit/synthesis/evolution/lie_trotter.py @@ -12,10 +12,15 @@ """The Lie-Trotter product formula.""" -from typing import Callable, Optional, Union, Dict, Any +from __future__ import annotations + +import inspect +from collections.abc import Callable +from typing import Any import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.quantum_info.operators import SparsePauliOp, Pauli +from qiskit.utils.deprecation import deprecate_arg from .product_formula import ProductFormula @@ -47,14 +52,32 @@ class LieTrotter(ProductFormula): `arXiv:math-ph/0506007 `_ """ + @deprecate_arg( + name="atomic_evolution", + since="1.2", + predicate=lambda callable: callable is not None + and len(inspect.signature(callable).parameters) == 2, + deprecation_description=( + "The 'Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]' signature of the " + "'atomic_evolution' argument" + ), + additional_msg=( + "Instead you should update your 'atomic_evolution' function to be of the following " + "type: 'Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]'." + ), + pending=True, + ) def __init__( self, reps: int = 1, insert_barriers: bool = False, cx_structure: str = "chain", - atomic_evolution: Optional[ - Callable[[Union[Pauli, SparsePauliOp], float], QuantumCircuit] - ] = None, + atomic_evolution: ( + Callable[[Pauli | SparsePauliOp, float], QuantumCircuit] + | Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None] + | None + ) = None, + wrap: bool = False, ) -> None: """ Args: @@ -62,12 +85,20 @@ def __init__( insert_barriers: Whether to insert barriers between the atomic evolutions. cx_structure: How to arrange the CX gates for the Pauli evolutions, can be ``"chain"``, where next neighbor connections are used, or ``"fountain"``, - where all qubits are connected to one. - atomic_evolution: A function to construct the circuit for the evolution of single - Pauli string. Per default, a single Pauli evolution is decomposed in a CX chain - and a single qubit Z rotation. + where all qubits are connected to one. This only takes effect when + ``atomic_evolution is None``. + atomic_evolution: A function to apply the evolution of a single :class:`.Pauli`, or + :class:`.SparsePauliOp` of only commuting terms, to a circuit. The function takes in + three arguments: the circuit to append the evolution to, the Pauli operator to + evolve, and the evolution time. By default, a single Pauli evolution is decomposed + into a chain of ``CX`` gates and a single ``RZ`` gate. + Alternatively, the function can also take Pauli operator and evolution time as + inputs and returns the circuit that will be appended to the overall circuit being + built. + wrap: Whether to wrap the atomic evolutions into custom gate objects. This only takes + effect when ``atomic_evolution is None``. """ - super().__init__(1, reps, insert_barriers, cx_structure, atomic_evolution) + super().__init__(1, reps, insert_barriers, cx_structure, atomic_evolution, wrap) def synthesize(self, evolution): # get operators and time to evolve @@ -75,27 +106,22 @@ def synthesize(self, evolution): time = evolution.time # construct the evolution circuit - evolution_circuit = QuantumCircuit(operators[0].num_qubits) + single_rep = QuantumCircuit(operators[0].num_qubits) if not isinstance(operators, list): pauli_list = [(Pauli(op), np.real(coeff)) for op, coeff in operators.to_list()] else: pauli_list = [(op, 1) for op in operators] - # if we only evolve a single Pauli we don't need to additionally wrap it - wrap = not (len(pauli_list) == 1 and self.reps == 1) - for i, (op, coeff) in enumerate(pauli_list): - evolution_circuit.compose( - self.atomic_evolution(op, coeff * time / self.reps), wrap=wrap, inplace=True - ) + self.atomic_evolution(single_rep, op, coeff * time / self.reps) if self.insert_barriers and i != len(pauli_list) - 1: - evolution_circuit.barrier() + single_rep.barrier() - return evolution_circuit.repeat(self.reps).decompose() + return single_rep.repeat(self.reps, insert_barriers=self.insert_barriers).decompose() @property - def settings(self) -> Dict[str, Any]: + def settings(self) -> dict[str, Any]: """Return the settings in a dictionary, which can be used to reconstruct the object. Returns: @@ -113,4 +139,5 @@ def settings(self) -> Dict[str, Any]: "reps": self.reps, "insert_barriers": self.insert_barriers, "cx_structure": self._cx_structure, + "wrap": self._wrap, } diff --git a/qiskit/synthesis/evolution/product_formula.py b/qiskit/synthesis/evolution/product_formula.py index bf1282481afc..df38a2a541ab 100644 --- a/qiskit/synthesis/evolution/product_formula.py +++ b/qiskit/synthesis/evolution/product_formula.py @@ -12,12 +12,17 @@ """A product formula base for decomposing non-commuting operator exponentials.""" -from typing import Callable, Optional, Union, Any, Dict +from __future__ import annotations + +import inspect +from collections.abc import Callable +from typing import Any from functools import partial import numpy as np from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.quantum_info import SparsePauliOp, Pauli +from qiskit.utils.deprecation import deprecate_arg from .evolution_synthesis import EvolutionSynthesis @@ -28,15 +33,33 @@ class ProductFormula(EvolutionSynthesis): :obj:`.LieTrotter` and :obj:`.SuzukiTrotter` inherit from this class. """ + @deprecate_arg( + name="atomic_evolution", + since="1.2", + predicate=lambda callable: callable is not None + and len(inspect.signature(callable).parameters) == 2, + deprecation_description=( + "The 'Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]' signature of the " + "'atomic_evolution' argument" + ), + additional_msg=( + "Instead you should update your 'atomic_evolution' function to be of the following " + "type: 'Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]'." + ), + pending=True, + ) def __init__( self, order: int, reps: int = 1, insert_barriers: bool = False, cx_structure: str = "chain", - atomic_evolution: Optional[ - Callable[[Union[Pauli, SparsePauliOp], float], QuantumCircuit] - ] = None, + atomic_evolution: ( + Callable[[Pauli | SparsePauliOp, float], QuantumCircuit] + | Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None] + | None + ) = None, + wrap: bool = False, ) -> None: """ Args: @@ -45,10 +68,18 @@ def __init__( insert_barriers: Whether to insert barriers between the atomic evolutions. cx_structure: How to arrange the CX gates for the Pauli evolutions, can be ``"chain"``, where next neighbor connections are used, or ``"fountain"``, - where all qubits are connected to one. - atomic_evolution: A function to construct the circuit for the evolution of single - Pauli string. Per default, a single Pauli evolution is decomposed in a CX chain - and a single qubit Z rotation. + where all qubits are connected to one. This only takes effect when + ``atomic_evolution is None``. + atomic_evolution: A function to apply the evolution of a single :class:`.Pauli`, or + :class:`.SparsePauliOp` of only commuting terms, to a circuit. The function takes in + three arguments: the circuit to append the evolution to, the Pauli operator to + evolve, and the evolution time. By default, a single Pauli evolution is decomposed + into a chain of ``CX`` gates and a single ``RZ`` gate. + Alternatively, the function can also take Pauli operator and evolution time as + inputs and returns the circuit that will be appended to the overall circuit being + built. + wrap: Whether to wrap the atomic evolutions into custom gate objects. This only takes + effect when ``atomic_evolution is None``. """ super().__init__() self.order = order @@ -58,15 +89,27 @@ def __init__( # user-provided atomic evolution, stored for serialization self._atomic_evolution = atomic_evolution self._cx_structure = cx_structure + self._wrap = wrap # if atomic evolution is not provided, set a default if atomic_evolution is None: - atomic_evolution = partial(_default_atomic_evolution, cx_structure=cx_structure) + self.atomic_evolution = partial( + _default_atomic_evolution, cx_structure=cx_structure, wrap=wrap + ) + + elif len(inspect.signature(atomic_evolution).parameters) == 2: + + def wrap_atomic_evolution(output, operator, time): + definition = atomic_evolution(operator, time) + output.compose(definition, wrap=wrap, inplace=True) + + self.atomic_evolution = wrap_atomic_evolution - self.atomic_evolution = atomic_evolution + else: + self.atomic_evolution = atomic_evolution @property - def settings(self) -> Dict[str, Any]: + def settings(self) -> dict[str, Any]: """Return the settings in a dictionary, which can be used to reconstruct the object. Returns: @@ -85,15 +128,18 @@ def settings(self) -> Dict[str, Any]: "reps": self.reps, "insert_barriers": self.insert_barriers, "cx_structure": self._cx_structure, + "wrap": self._wrap, } def evolve_pauli( + output: QuantumCircuit, pauli: Pauli, - time: Union[float, ParameterExpression] = 1.0, + time: float | ParameterExpression = 1.0, cx_structure: str = "chain", - label: Optional[str] = None, -) -> QuantumCircuit: + wrap: bool = False, + label: str | None = None, +) -> None: r"""Construct a circuit implementing the time evolution of a single Pauli string. For a Pauli string :math:`P = \{I, X, Y, Z\}^{\otimes n}` on :math:`n` qubits and an @@ -106,79 +152,91 @@ def evolve_pauli( Since only a single Pauli string is evolved the circuit decomposition is exact. Args: + output: The circuit object to which to append the evolved Pauli. pauli: The Pauli to evolve. time: The evolution time. cx_structure: Determine the structure of CX gates, can be either ``"chain"`` for next-neighbor connections or ``"fountain"`` to connect directly to the top qubit. + wrap: Whether to wrap the single Pauli evolutions into custom gate objects. label: A label for the gate. - - Returns: - A quantum circuit implementing the time evolution of the Pauli. """ num_non_identity = len([label for label in pauli.to_label() if label != "I"]) # first check, if the Pauli is only the identity, in which case the evolution only # adds a global phase if num_non_identity == 0: - definition = QuantumCircuit(pauli.num_qubits, global_phase=-time) + output.global_phase -= time # if we evolve on a single qubit, if yes use the corresponding qubit rotation elif num_non_identity == 1: - definition = _single_qubit_evolution(pauli, time) + _single_qubit_evolution(output, pauli, time, wrap) # same for two qubits, use Qiskit's native rotations elif num_non_identity == 2: - definition = _two_qubit_evolution(pauli, time, cx_structure) + _two_qubit_evolution(output, pauli, time, cx_structure, wrap) # otherwise do basis transformation and CX chains else: - definition = _multi_qubit_evolution(pauli, time, cx_structure) - - definition.name = f"exp(it {pauli.to_label()})" + _multi_qubit_evolution(output, pauli, time, cx_structure, wrap) - return definition - -def _single_qubit_evolution(pauli, time): - definition = QuantumCircuit(pauli.num_qubits) +def _single_qubit_evolution(output, pauli, time, wrap): + dest = QuantumCircuit(1) if wrap else output # Note that all phases are removed from the pauli label and are only in the coefficients. # That's because the operators we evolved have all been translated to a SparsePauliOp. + qubits = [] + label = "" for i, pauli_i in enumerate(reversed(pauli.to_label())): + idx = 0 if wrap else i if pauli_i == "X": - definition.rx(2 * time, i) + dest.rx(2 * time, idx) + qubits.append(i) + label += "X" elif pauli_i == "Y": - definition.ry(2 * time, i) + dest.ry(2 * time, idx) + qubits.append(i) + label += "Y" elif pauli_i == "Z": - definition.rz(2 * time, i) + dest.rz(2 * time, idx) + qubits.append(i) + label += "Z" - return definition + if wrap: + gate = dest.to_gate(label=f"exp(it {label})") + qubits = [output.qubits[q] for q in qubits] + output.append(gate, qargs=qubits, copy=False) -def _two_qubit_evolution(pauli, time, cx_structure): +def _two_qubit_evolution(output, pauli, time, cx_structure, wrap): # Get the Paulis and the qubits they act on. # Note that all phases are removed from the pauli label and are only in the coefficients. # That's because the operators we evolved have all been translated to a SparsePauliOp. labels_as_array = np.array(list(reversed(pauli.to_label()))) qubits = np.where(labels_as_array != "I")[0] + indices = [0, 1] if wrap else qubits labels = np.array([labels_as_array[idx] for idx in qubits]) - definition = QuantumCircuit(pauli.num_qubits) + dest = QuantumCircuit(2) if wrap else output # go through all cases we have implemented in Qiskit if all(labels == "X"): # RXX - definition.rxx(2 * time, qubits[0], qubits[1]) + dest.rxx(2 * time, indices[0], indices[1]) elif all(labels == "Y"): # RYY - definition.ryy(2 * time, qubits[0], qubits[1]) + dest.ryy(2 * time, indices[0], indices[1]) elif all(labels == "Z"): # RZZ - definition.rzz(2 * time, qubits[0], qubits[1]) + dest.rzz(2 * time, indices[0], indices[1]) elif labels[0] == "Z" and labels[1] == "X": # RZX - definition.rzx(2 * time, qubits[0], qubits[1]) + dest.rzx(2 * time, indices[0], indices[1]) elif labels[0] == "X" and labels[1] == "Z": # RXZ - definition.rzx(2 * time, qubits[1], qubits[0]) + dest.rzx(2 * time, indices[1], indices[0]) else: # all the others are not native in Qiskit, so use default the decomposition - definition = _multi_qubit_evolution(pauli, time, cx_structure) + _multi_qubit_evolution(output, pauli, time, cx_structure, wrap) + return - return definition + if wrap: + gate = dest.to_gate(label=f"exp(it {''.join(labels)})") + qubits = [output.qubits[q] for q in qubits] + output.append(gate, qargs=qubits, copy=False) -def _multi_qubit_evolution(pauli, time, cx_structure): +def _multi_qubit_evolution(output, pauli, time, cx_structure, wrap): # get diagonalizing clifford cliff = diagonalizing_clifford(pauli) @@ -198,14 +256,16 @@ def _multi_qubit_evolution(pauli, time, cx_structure): break # build the evolution as: diagonalization, reduction, 1q evolution, followed by inverses - definition = QuantumCircuit(pauli.num_qubits) - definition.compose(cliff, inplace=True) - definition.compose(chain, inplace=True) - definition.rz(2 * time, target) - definition.compose(chain.inverse(), inplace=True) - definition.compose(cliff.inverse(), inplace=True) + dest = QuantumCircuit(pauli.num_qubits) if wrap else output + dest.compose(cliff, inplace=True) + dest.compose(chain, inplace=True) + dest.rz(2 * time, target) + dest.compose(chain.inverse(), inplace=True) + dest.compose(cliff.inverse(), inplace=True) - return definition + if wrap: + gate = dest.to_gate(label=f"exp(it {pauli.to_label()})") + output.append(gate, qargs=output.qubits, copy=False) def diagonalizing_clifford(pauli: Pauli) -> QuantumCircuit: @@ -313,16 +373,12 @@ def cnot_fountain(pauli: Pauli) -> QuantumCircuit: return chain -def _default_atomic_evolution(operator, time, cx_structure): +def _default_atomic_evolution(output, operator, time, cx_structure, wrap): if isinstance(operator, Pauli): # single Pauli operator: just exponentiate it - evolution_circuit = evolve_pauli(operator, time, cx_structure) + evolve_pauli(output, operator, time, cx_structure, wrap) else: # sum of Pauli operators: exponentiate each term (this assumes they commute) pauli_list = [(Pauli(op), np.real(coeff)) for op, coeff in operator.to_list()] - name = f"exp(it {[pauli.to_label() for pauli, _ in pauli_list]})" - evolution_circuit = QuantumCircuit(operator.num_qubits, name=name) for pauli, coeff in pauli_list: - evolution_circuit.compose(evolve_pauli(pauli, coeff * time, cx_structure), inplace=True) - - return evolution_circuit + evolve_pauli(output, pauli, coeff * time, cx_structure, wrap) diff --git a/qiskit/synthesis/evolution/qdrift.py b/qiskit/synthesis/evolution/qdrift.py index d4326903b4ba..7c68f66dc8ea 100644 --- a/qiskit/synthesis/evolution/qdrift.py +++ b/qiskit/synthesis/evolution/qdrift.py @@ -12,11 +12,15 @@ """QDrift Class""" +from __future__ import annotations + +import inspect import math -from typing import Union, Optional, Callable +from collections.abc import Callable import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.quantum_info.operators import SparsePauliOp, Pauli +from qiskit.utils.deprecation import deprecate_arg from .product_formula import ProductFormula from .lie_trotter import LieTrotter @@ -32,15 +36,33 @@ class QDrift(ProductFormula): `arXiv:quant-ph/1811.08017 `_ """ + @deprecate_arg( + name="atomic_evolution", + since="1.2", + predicate=lambda callable: callable is not None + and len(inspect.signature(callable).parameters) == 2, + deprecation_description=( + "The 'Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]' signature of the " + "'atomic_evolution' argument" + ), + additional_msg=( + "Instead you should update your 'atomic_evolution' function to be of the following " + "type: 'Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]'." + ), + pending=True, + ) def __init__( self, reps: int = 1, insert_barriers: bool = False, cx_structure: str = "chain", - atomic_evolution: Optional[ - Callable[[Union[Pauli, SparsePauliOp], float], QuantumCircuit] - ] = None, - seed: Optional[int] = None, + atomic_evolution: ( + Callable[[Pauli | SparsePauliOp, float], QuantumCircuit] + | Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None] + | None + ) = None, + seed: int | None = None, + wrap: bool = False, ) -> None: r""" Args: @@ -48,13 +70,21 @@ def __init__( insert_barriers: Whether to insert barriers between the atomic evolutions. cx_structure: How to arrange the CX gates for the Pauli evolutions, can be ``"chain"``, where next neighbor connections are used, or ``"fountain"``, where all - qubits are connected to one. - atomic_evolution: A function to construct the circuit for the evolution of single - Pauli string. Per default, a single Pauli evolution is decomposed in a CX chain - and a single qubit Z rotation. + qubits are connected to one. This only takes effect when + ``atomic_evolution is None``. + atomic_evolution: A function to apply the evolution of a single :class:`.Pauli`, or + :class:`.SparsePauliOp` of only commuting terms, to a circuit. The function takes in + three arguments: the circuit to append the evolution to, the Pauli operator to + evolve, and the evolution time. By default, a single Pauli evolution is decomposed + into a chain of ``CX`` gates and a single ``RZ`` gate. + Alternatively, the function can also take Pauli operator and evolution time as + inputs and returns the circuit that will be appended to the overall circuit being + built. seed: An optional seed for reproducibility of the random sampling process. + wrap: Whether to wrap the atomic evolutions into custom gate objects. This only takes + effect when ``atomic_evolution is None``. """ - super().__init__(1, reps, insert_barriers, cx_structure, atomic_evolution) + super().__init__(1, reps, insert_barriers, cx_structure, atomic_evolution, wrap) self.sampled_ops = None self.rng = np.random.default_rng(seed) diff --git a/qiskit/synthesis/evolution/suzuki_trotter.py b/qiskit/synthesis/evolution/suzuki_trotter.py index a18b30720364..e03fd27e26d4 100644 --- a/qiskit/synthesis/evolution/suzuki_trotter.py +++ b/qiskit/synthesis/evolution/suzuki_trotter.py @@ -12,12 +12,16 @@ """The Suzuki-Trotter product formula.""" -from typing import Callable, Optional, Union +from __future__ import annotations + +import inspect +from collections.abc import Callable import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.quantum_info.operators import SparsePauliOp, Pauli +from qiskit.utils.deprecation import deprecate_arg from .product_formula import ProductFormula @@ -51,15 +55,33 @@ class SuzukiTrotter(ProductFormula): `arXiv:math-ph/0506007 `_ """ + @deprecate_arg( + name="atomic_evolution", + since="1.2", + predicate=lambda callable: callable is not None + and len(inspect.signature(callable).parameters) == 2, + deprecation_description=( + "The 'Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]' signature of the " + "'atomic_evolution' argument" + ), + additional_msg=( + "Instead you should update your 'atomic_evolution' function to be of the following " + "type: 'Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]'." + ), + pending=True, + ) def __init__( self, order: int = 2, reps: int = 1, insert_barriers: bool = False, cx_structure: str = "chain", - atomic_evolution: Optional[ - Callable[[Union[Pauli, SparsePauliOp], float], QuantumCircuit] - ] = None, + atomic_evolution: ( + Callable[[Pauli | SparsePauliOp, float], QuantumCircuit] + | Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None] + | None + ) = None, + wrap: bool = False, ) -> None: """ Args: @@ -68,10 +90,17 @@ def __init__( insert_barriers: Whether to insert barriers between the atomic evolutions. cx_structure: How to arrange the CX gates for the Pauli evolutions, can be ``"chain"``, where next neighbor connections are used, or ``"fountain"``, where all qubits are - connected to one. - atomic_evolution: A function to construct the circuit for the evolution of single - Pauli string. Per default, a single Pauli evolution is decomposed in a CX chain - and a single qubit Z rotation. + connected to one. This only takes effect when ``atomic_evolution is None``. + atomic_evolution: A function to apply the evolution of a single :class:`.Pauli`, or + :class:`.SparsePauliOp` of only commuting terms, to a circuit. The function takes in + three arguments: the circuit to append the evolution to, the Pauli operator to + evolve, and the evolution time. By default, a single Pauli evolution is decomposed + into a chain of ``CX`` gates and a single ``RZ`` gate. + Alternatively, the function can also take Pauli operator and evolution time as + inputs and returns the circuit that will be appended to the overall circuit being + built. + wrap: Whether to wrap the atomic evolutions into custom gate objects. This only takes + effect when ``atomic_evolution is None``. Raises: ValueError: If order is not even """ @@ -81,7 +110,7 @@ def __init__( "Suzuki product formulae are symmetric and therefore only defined " "for even orders." ) - super().__init__(order, reps, insert_barriers, cx_structure, atomic_evolution) + super().__init__(order, reps, insert_barriers, cx_structure, atomic_evolution, wrap) def synthesize(self, evolution): # get operators and time to evolve @@ -97,32 +126,13 @@ def synthesize(self, evolution): # construct the evolution circuit single_rep = QuantumCircuit(operators[0].num_qubits) - first_barrier = False - - for op, coeff in ops_to_evolve: - # add barriers - if first_barrier: - if self.insert_barriers: - single_rep.barrier() - else: - first_barrier = True - - single_rep.compose(self.atomic_evolution(op, coeff), wrap=True, inplace=True) - - evolution_circuit = QuantumCircuit(operators[0].num_qubits) - first_barrier = False - - for _ in range(self.reps): - # add barriers - if first_barrier: - if self.insert_barriers: - single_rep.barrier() - else: - first_barrier = True - evolution_circuit.compose(single_rep, inplace=True) + for i, (op, coeff) in enumerate(ops_to_evolve): + self.atomic_evolution(single_rep, op, coeff) + if self.insert_barriers and i != len(ops_to_evolve) - 1: + single_rep.barrier() - return evolution_circuit + return single_rep.repeat(self.reps, insert_barriers=self.insert_barriers).decompose() @staticmethod def _recurse(order, time, pauli_list): diff --git a/releasenotes/notes/product-formula-improvements-1bc40650151cf107.yaml b/releasenotes/notes/product-formula-improvements-1bc40650151cf107.yaml new file mode 100644 index 000000000000..fc9713e7a436 --- /dev/null +++ b/releasenotes/notes/product-formula-improvements-1bc40650151cf107.yaml @@ -0,0 +1,25 @@ +--- +features_circuits: + - | + Added the ``insert_barriers`` keyword argument to the + :meth:`~.QuantumCircuit.repeat` method. Setting it to ``True`` will insert + barriers between circuit repetitions. +features_synthesis: + - | + Added the ``wrap`` keyword argument to the :class:`.ProductFormula` classes + which (when enabled) wraps individual Pauli evolution terms. This can be + useful when visualizing circuits. +upgrade_synthesis: + - | + The ``atomic_evolution`` argument to :class:`.ProductFormula` (and its + subclasses) has a new function signature. Rather than taking some Pauli + operator and time coefficient and returning the evolution circuit, the new + function takes in an existing circuit and should append the evolution of the + provided Pauli and given time to this circuit. This new implementation + benefits from significantly better performance. + - | + :class:`.LieTrotter` and :class:`.SuzukiTrotter` no longer wrap the + individually evolved Pauli terms into gate definitions. If you rely on a + certain decomposition level of your circuit, you have to remove one level of + :meth:`~.QuantumCircuit.decompose` or add the ``wrap=True`` keyword argument + to your synthesis object. diff --git a/test/python/circuit/library/test_evolution_gate.py b/test/python/circuit/library/test_evolution_gate.py index 88b0529ca7c2..a7c9a73abb5f 100644 --- a/test/python/circuit/library/test_evolution_gate.py +++ b/test/python/circuit/library/test_evolution_gate.py @@ -20,6 +20,7 @@ from qiskit.circuit import QuantumCircuit, Parameter from qiskit.circuit.library import PauliEvolutionGate from qiskit.synthesis import LieTrotter, SuzukiTrotter, MatrixExponential, QDrift +from qiskit.synthesis.evolution.product_formula import cnot_chain, diagonalizing_clifford from qiskit.converters import circuit_to_dag from qiskit.quantum_info import Operator, SparsePauliOp, Pauli, Statevector from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -130,7 +131,7 @@ def test_suzuki_trotter_manual(self): expected.ry(2 * p_4 * time, 0) expected.rx(p_4 * time, 0) - self.assertEqual(evo_gate.definition.decompose(), expected) + self.assertEqual(evo_gate.definition, expected) @data( (X + Y, 0.5, 1, [(Pauli("X"), 0.5), (Pauli("X"), 0.5)]), @@ -175,11 +176,15 @@ def energy(evo): self.assertAlmostEqual(energy(exact), np.average(qdrift_energy), places=2) - def test_passing_grouped_paulis(self): + @data(True, False) + def test_passing_grouped_paulis(self, wrap): """Test passing a list of already grouped Paulis.""" grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)] - evo_gate = PauliEvolutionGate(grouped_ops, time=0.12, synthesis=LieTrotter()) - decomposed = evo_gate.definition.decompose() + evo_gate = PauliEvolutionGate(grouped_ops, time=0.12, synthesis=LieTrotter(wrap=wrap)) + if wrap: + decomposed = evo_gate.definition.decompose() + else: + decomposed = evo_gate.definition self.assertEqual(decomposed.count_ops()["rz"], 4) self.assertEqual(decomposed.count_ops()["rzz"], 1) self.assertEqual(decomposed.count_ops()["rxx"], 1) @@ -327,6 +332,38 @@ def test_labels_and_name(self): self.assertEqual(evo.name, "PauliEvolution") self.assertEqual(evo.label, f"exp(-it {label})") + def test_atomic_evolution(self): + """Test a custom atomic_evolution.""" + + def atomic_evolution(pauli, time): + cliff = diagonalizing_clifford(pauli) + chain = cnot_chain(pauli) + + target = None + for i, pauli_i in enumerate(reversed(pauli.to_label())): + if pauli_i != "I": + target = i + break + + definition = QuantumCircuit(pauli.num_qubits) + definition.compose(cliff, inplace=True) + definition.compose(chain, inplace=True) + definition.rz(2 * time, target) + definition.compose(chain.inverse(), inplace=True) + definition.compose(cliff.inverse(), inplace=True) + + return definition + + op = (X ^ X ^ X) + (Y ^ Y ^ Y) + (Z ^ Z ^ Z) + time = 0.123 + reps = 4 + with self.assertWarns(PendingDeprecationWarning): + evo_gate = PauliEvolutionGate( + op, time, synthesis=LieTrotter(reps=reps, atomic_evolution=atomic_evolution) + ) + decomposed = evo_gate.definition.decompose() + self.assertEqual(decomposed.count_ops()["cx"], reps * 3 * 4) + if __name__ == "__main__": unittest.main() From 4867e8aae354cb52b50c4cabaa5869b98405ddfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:54:00 +0200 Subject: [PATCH 68/89] Add Rust representation for most controlled gates (#12659) * Add C3X (MCX), extend rust tests to multi-controlled gates. * Add macro to generate multi-controlled gates. Add CU, CU1, CU3, C3SX, C4X, CCZ. * Kill C4XGate * Finish adding gates, add circuit construction methods when possible. * Add import paths, fix drawer test. * Establish CGates with non-default control states as non-standard in circuit_instruction.rs. Add unit test. * Fix merge conflicts * Apply macro on missing gates * Add RCCX gate and RC3X (RCCCX) gate. * Make equivalence tests more explicit * Fix lint * Modify circuit methods for consistency * Fix default ctrl state for 3q+ gates, add test for CCZ * Apply comments from Matt's code review * Fix ctrl_state logic * Rename c3x to mcx? * Brackets didn't match explanation * Make sure controlled test doesn't use custom ControlledGate instances. * Rename c4x to mcx in Rust space. * Return PyResult rather than panic on error * Add suggestion from Matt's code review Co-authored-by: Matthew Treinish --------- Co-authored-by: John Lapeyre Co-authored-by: Matthew Treinish --- crates/circuit/src/circuit_instruction.rs | 37 +- crates/circuit/src/gate_matrix.rs | 455 +++++++++------- crates/circuit/src/imports.rs | 15 +- crates/circuit/src/operations.rs | 522 ++++++++++++++++++- crates/circuit/src/util.rs | 1 + qiskit/circuit/library/standard_gates/u.py | 2 + qiskit/circuit/library/standard_gates/u1.py | 2 + qiskit/circuit/library/standard_gates/u3.py | 2 + qiskit/circuit/library/standard_gates/x.py | 8 + qiskit/circuit/library/standard_gates/z.py | 2 + qiskit/circuit/quantumcircuit.py | 61 ++- test/python/circuit/test_rust_equivalence.py | 81 ++- 12 files changed, 928 insertions(+), 260 deletions(-) diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index ed1c358cbc5b..501105f9f17e 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -22,7 +22,7 @@ use pyo3::{intern, IntoPy, PyObject, PyResult}; use smallvec::{smallvec, SmallVec}; use crate::imports::{ - get_std_gate_class, populate_std_gate_map, GATE, INSTRUCTION, OPERATION, + get_std_gate_class, populate_std_gate_map, CONTROLLED_GATE, GATE, INSTRUCTION, OPERATION, SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, WARNINGS_WARN, }; use crate::interner::Index; @@ -884,24 +884,49 @@ pub fn convert_py_to_operation_type( Ok(stdgate) => stdgate.extract().ok().unwrap_or_default(), Err(_) => None, }; - // If the input instruction is a standard gate and a singleton instance + // If the input instruction is a standard gate and a singleton instance, // we should check for mutable state. A mutable instance should be treated // as a custom gate not a standard gate because it has custom properties. - // - // In the futuer we can revisit this when we've dropped `duration`, `unit`, + // Controlled gates with non-default control states are also considered + // custom gates even if a standard representation exists for the default + // control state. + + // In the future we can revisit this when we've dropped `duration`, `unit`, // and `condition` from the api as we should own the label in the // `CircuitInstruction`. The other piece here is for controlled gates there // is the control state, so for `SingletonControlledGates` we'll still need // this check. if standard.is_some() { let mutable: bool = py_op.getattr(py, intern!(py, "mutable"))?.extract(py)?; - if mutable + // The default ctrl_states are the all 1 state and None. + // These are the only cases where controlled gates can be standard. + let is_default_ctrl_state = || -> PyResult { + match py_op.getattr(py, intern!(py, "ctrl_state")) { + Ok(c_state) => match c_state.extract::>(py) { + Ok(c_state_int) => match c_state_int { + Some(c_int) => { + let qubits: u32 = + py_op.getattr(py, intern!(py, "num_qubits"))?.extract(py)?; + Ok(c_int == (2_i32.pow(qubits - 1) - 1)) + } + None => Ok(true), + }, + Err(_) => Ok(false), + }, + Err(_) => Ok(false), + } + }; + + if (mutable && (py_op_bound.is_instance(SINGLETON_GATE.get_bound(py))? - || py_op_bound.is_instance(SINGLETON_CONTROLLED_GATE.get_bound(py))?) + || py_op_bound.is_instance(SINGLETON_CONTROLLED_GATE.get_bound(py))?)) + || (py_op_bound.is_instance(CONTROLLED_GATE.get_bound(py))? + && !is_default_ctrl_state()?) { standard = None; } } + if let Some(op) = standard { let base_class = op_type.to_object(py); populate_std_gate_map(py, op, base_class); diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 46585ff6da6e..6f527af2e30f 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -10,59 +10,62 @@ // 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; use crate::util::{ - c64, GateArray0Q, GateArray1Q, GateArray2Q, GateArray3Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM, + c64, GateArray0Q, GateArray1Q, GateArray2Q, GateArray3Q, GateArray4Q, C_M_ONE, C_ONE, C_ZERO, + IM, M_IM, }; pub static ONE_QUBIT_IDENTITY: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_ONE]]; -#[inline] -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(); - let cosphi = phi.cos(); - let sinphi = phi.sin(); - [ - [cost, c64(-sint * sinphi, -sint * cosphi)], - [c64(sint * sinphi, -sint * cosphi), cost], - ] +// Utility for generating static matrices for controlled gates with "n" control qubits. +// Assumptions: +// 1. the reference "gate-matrix" is a single-qubit gate matrix (2x2) +// 2. the first "n" qubits are controls and the last qubit is the target +macro_rules! make_n_controlled_gate { + ($gate_matrix:expr, $n_control_qubits:expr) => {{ + const DIM: usize = 2_usize.pow($n_control_qubits as u32 + 1_u32); + // DIM x DIM matrix of all zeros + let mut matrix: [[Complex64; DIM]; DIM] = [[C_ZERO; DIM]; DIM]; + // DIM x DIM diagonal matrix + { + let mut i = 0; + while i < DIM { + matrix[i][i] = C_ONE; + i += 1; + } + } + // Insert elements of gate_matrix in columns DIM/2-1 and DIM-1 + matrix[DIM / 2 - 1][DIM / 2 - 1] = $gate_matrix[0][0]; + matrix[DIM - 1][DIM - 1] = $gate_matrix[1][1]; + matrix[DIM / 2 - 1][DIM - 1] = $gate_matrix[0][1]; + matrix[DIM - 1][DIM / 2 - 1] = $gate_matrix[1][0]; + matrix + }}; } -#[inline] -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()); - [[cos, isin], [isin, cos]] -} +pub static X_GATE: GateArray1Q = [[C_ZERO, C_ONE], [C_ONE, C_ZERO]]; -#[inline] -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.); - [[cos, -sin], [sin, cos]] -} +pub static Z_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_M_ONE]]; -#[inline] -pub fn rz_gate(theta: f64) -> GateArray1Q { - let ilam2 = c64(0., 0.5 * theta); - [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] -} +pub static Y_GATE: GateArray1Q = [[C_ZERO, M_IM], [IM, C_ZERO]]; 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: 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 S_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, IM]]; + +pub static SDG_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, M_IM]]; + +pub static T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, 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 SX_GATE: GateArray1Q = [ @@ -75,52 +78,27 @@ pub static SXDG_GATE: GateArray1Q = [ [c64(0.5, 0.5), c64(0.5, -0.5)], ]; -pub static X_GATE: GateArray1Q = [[C_ZERO, C_ONE], [C_ONE, C_ZERO]]; +pub static CX_GATE: GateArray2Q = make_n_controlled_gate!(X_GATE, 1); -pub static Z_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_M_ONE]]; +pub static CZ_GATE: GateArray2Q = make_n_controlled_gate!(Z_GATE, 1); -pub static Y_GATE: GateArray1Q = [[C_ZERO, M_IM], [IM, C_ZERO]]; +pub static CY_GATE: GateArray2Q = make_n_controlled_gate!(Y_GATE, 1); -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 CCX_GATE: GateArray3Q = make_n_controlled_gate!(X_GATE, 2); -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 CCZ_GATE: GateArray3Q = make_n_controlled_gate!(Z_GATE, 2); -pub static CCX_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_ZERO, C_ZERO, C_ONE, - ], - [ - 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_ONE, C_ZERO, - ], - [ - C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, - ], -]; +pub static C3X_GATE: GateArray4Q = make_n_controlled_gate!(X_GATE, 3); + +pub static C3SX_GATE: GateArray4Q = make_n_controlled_gate!(SX_GATE, 3); + +pub static CH_GATE: GateArray2Q = make_n_controlled_gate!(H_GATE, 1); + +pub static CS_GATE: GateArray2Q = make_n_controlled_gate!(S_GATE, 1); + +pub static CSDG_GATE: GateArray2Q = make_n_controlled_gate!(SDG_GATE, 1); + +pub static CSX_GATE: GateArray2Q = make_n_controlled_gate!(SX_GATE, 1); pub static ECR_GATE: GateArray2Q = [ [ @@ -162,55 +140,6 @@ pub static ISWAP_GATE: GateArray2Q = [ [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; -pub static S_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, IM]]; - -pub static SDG_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, M_IM]]; - -pub static T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, 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 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, @@ -245,42 +174,95 @@ 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()); +pub static RCCX_GATE: GateArray3Q = [ [ - [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, C_ZERO, C_ZERO, C_ZERO, + ], [ - [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], - ] -} + 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_ZERO, C_ZERO, M_IM], + [ + 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_M_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, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO], +]; -#[inline] -pub fn crz_gate(theta: f64) -> GateArray2Q { - let i_half_theta = c64(0., theta / 2.); +pub static RC3X_GATE: GateArray4Q = [ [ - [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()], - ] -} + C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, 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_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_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, 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_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_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_ZERO, C_ZERO, C_ZERO, C_ZERO, 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, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, 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_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_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + M_IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, 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_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_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_M_ONE, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], +]; #[inline] pub fn global_phase_gate(theta: f64) -> GateArray0Q { @@ -302,28 +284,6 @@ pub fn u_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { ] } -#[inline] -pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { - let cos = (theta / 2.).cos(); - let sin = (theta / 2.).sin(); - [ - [ - c64(cos, 0.), - C_ZERO, - C_ZERO, - c64(0., -sin) * c64(0., -beta).exp(), - ], - [C_ZERO, C_ONE, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [ - c64(0., -sin) * c64(0., beta).exp(), - C_ZERO, - C_ZERO, - c64(cos, 0.), - ], - ] -} - #[inline] pub fn u1_gate(lam: f64) -> GateArray1Q { [[C_ONE, C_ZERO], [C_ZERO, c64(0., lam).exp()]] @@ -354,37 +314,102 @@ pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { } #[inline] -pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { - let cos = (theta / 2.).cos(); - let sin = (theta / 2.).sin(); +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 cu_gate(theta: f64, phi: f64, lam: f64, gamma: f64) -> GateArray2Q { + let cos_theta = (theta / 2.).cos(); + let sin_theta = (theta / 2.).sin(); [ [C_ONE, C_ZERO, C_ZERO, C_ZERO], [ C_ZERO, - c64(cos, 0.), - c64(0., -sin) * c64(0., -beta).exp(), + c64(0., gamma).exp() * cos_theta, C_ZERO, + c64(0., gamma + phi).exp() * (-1.) * sin_theta, ], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], [ C_ZERO, - c64(0., -sin) * c64(0., beta).exp(), - c64(cos, 0.), + c64(0., gamma + lam).exp() * sin_theta, C_ZERO, + c64(0., gamma + phi + lam).exp() * cos_theta, ], - [C_ZERO, C_ZERO, C_ZERO, C_ONE], ] } #[inline] -pub fn cp_gate(lam: f64) -> GateArray2Q { +pub fn cu1_gate(lam: f64) -> GateArray2Q { + let gate_matrix = u1_gate(lam); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn cu3_gate(theta: f64, phi: f64, lam: f64) -> GateArray2Q { + let gate_matrix = u3_gate(theta, phi, lam); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +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(); + let cosphi = phi.cos(); + let sinphi = phi.sin(); [ - [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()], + [cost, c64(-sint * sinphi, -sint * cosphi)], + [c64(sint * sinphi, -sint * cosphi), cost], ] } +#[inline] +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()); + [[cos, isin], [isin, cos]] +} + +#[inline] +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.); + [[cos, -sin], [sin, cos]] +} + +#[inline] +pub fn rz_gate(theta: f64) -> GateArray1Q { + let ilam2 = c64(0., 0.5 * theta); + [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] +} + +#[inline] +pub fn crx_gate(theta: f64) -> GateArray2Q { + let gate_matrix = rx_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn cry_gate(theta: f64) -> GateArray2Q { + let gate_matrix = ry_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn crz_gate(theta: f64) -> GateArray2Q { + let gate_matrix = rz_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + #[inline] pub fn rxx_gate(theta: f64) -> GateArray2Q { let (sint, cost) = (theta / 2.0).sin_cos(); @@ -440,3 +465,47 @@ pub fn rzx_gate(theta: f64) -> GateArray2Q { [C_ZERO, csin, C_ZERO, ccos], ] } + +#[inline] +pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [ + c64(cos, 0.), + C_ZERO, + C_ZERO, + c64(0., -sin) * c64(0., -beta).exp(), + ], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [ + c64(0., -sin) * c64(0., beta).exp(), + C_ZERO, + C_ZERO, + c64(cos, 0.), + ], + ] +} + +#[inline] +pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [ + C_ZERO, + c64(cos, 0.), + c64(0., -sin) * c64(0., -beta).exp(), + C_ZERO, + ], + [ + C_ZERO, + c64(0., -sin) * c64(0., beta).exp(), + c64(cos, 0.), + C_ZERO, + ], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], + ] +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 554f553d9427..dfefb0a348f8 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -71,8 +71,9 @@ pub static SINGLETON_GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.singleton", "SingletonGate"); pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.singleton", "SingletonControlledGate"); +pub static CONTROLLED_GATE: ImportOnceCell = + ImportOnceCell::new("qiskit.circuit", "ControlledGate"); pub static DEEPCOPY: ImportOnceCell = ImportOnceCell::new("copy", "deepcopy"); - pub static WARNINGS_WARN: ImportOnceCell = ImportOnceCell::new("warnings", "warn"); /// A mapping from the enum variant in crate::operations::StandardGate to the python @@ -178,19 +179,19 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // CU3Gate = 41 ["qiskit.circuit.library.standard_gates.u3", "CU3Gate"], // C3XGate = 42 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "C3XGate"], // C3SXGate = 43 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "C3SXGate"], // C4XGate = 44 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "C4XGate"], // DCXGate = 45 ["qiskit.circuit.library.standard_gates.dcx", "DCXGate"], // CCZGate = 46 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.z", "CCZGate"], // RCCXGate = 47 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "RCCXGate"], // RC3XGate = 48 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "RC3XGate"], // RXXGate = 49 ["qiskit.circuit.library.standard_gates.rxx", "RXXGate"], // RYYGate = 50 diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 3bfef81d29ce..fcbfddb72181 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -248,23 +248,21 @@ impl ToPyObject for StandardGate { } } -// TODO: replace all 34s (placeholders) with actual number 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, 2, // 20-29 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39 - 2, 2, 34, 34, 34, 2, 34, 34, 34, 2, // 40-49 + 2, 2, 4, 4, 5, 2, 3, 3, 4, 2, // 40-49 2, 2, 2, // 50-52 ]; -// TODO: replace all 34s (placeholders) with actual number 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, 1, // 20-29 - 1, 1, 2, 0, 1, 0, 0, 0, 0, 3, // 30-39 - 1, 3, 34, 34, 34, 0, 34, 34, 34, 1, // 40-49 + 1, 1, 2, 0, 1, 0, 0, 0, 0, 4, // 30-39 + 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, // 40-49 1, 1, 1, // 50-52 ]; @@ -311,13 +309,13 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "cu", // 39 "cu1", // 40 "cu3", // 41 - "c3x", // 42 + "mcx", // 42 ("c3x") "c3sx", // 43 - "c4x", // 44 + "mcx", // 44 ("c4x") "dcx", // 45 "ccz", // 46 "rccx", // 47 - "rc3x", // 48 + "rcccx", // 48 ("rc3x") "rxx", // 49 "ryy", // 50 "rzz", // 51 @@ -539,6 +537,34 @@ impl Operation for StandardGate { } _ => None, }, + Self::CUGate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam), Param::Float(gamma)] => { + Some(aview2(&gate_matrix::cu_gate(*theta, *phi, *lam, *gamma)).to_owned()) + } + _ => None, + }, + Self::CU1Gate => match params[0] { + Param::Float(lam) => Some(aview2(&gate_matrix::cu1_gate(lam)).to_owned()), + _ => None, + }, + Self::CU3Gate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::cu3_gate(*theta, *phi, *lam)).to_owned()) + } + _ => None, + }, + Self::C3XGate => match params { + [] => Some(aview2(&gate_matrix::C3X_GATE).to_owned()), + _ => None, + }, + Self::C3SXGate => match params { + [] => Some(aview2(&gate_matrix::C3SX_GATE).to_owned()), + _ => None, + }, + Self::CCZGate => match params { + [] => Some(aview2(&gate_matrix::CCZ_GATE).to_owned()), + _ => None, + }, Self::CHGate => match params { [] => Some(aview2(&gate_matrix::CH_GATE).to_owned()), _ => None, @@ -563,8 +589,6 @@ impl Operation for StandardGate { [] => 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()) @@ -575,8 +599,7 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::DCX_GATE).to_owned()), _ => None, }, - Self::CCZGate => todo!(), - Self::RCCXGate | Self::RC3XGate => todo!(), + Self::C4XGate => todo!(), Self::RXXGate => match params[0] { Param::Float(theta) => Some(aview2(&gate_matrix::rxx_gate(theta)).to_owned()), _ => None, @@ -593,6 +616,14 @@ impl Operation for StandardGate { Param::Float(theta) => Some(aview2(&gate_matrix::rzx_gate(theta)).to_owned()), _ => None, }, + Self::RCCXGate => match params { + [] => Some(aview2(&gate_matrix::RCCX_GATE).to_owned()), + _ => None, + }, + Self::RC3XGate => match params { + [] => Some(aview2(&gate_matrix::RC3X_GATE).to_owned()), + _ => None, + }, } } @@ -1140,6 +1171,68 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CUGate => Python::with_gil(|py| -> Option { + let param_second_p = radd_param( + multiply_param(¶ms[2], 0.5, py), + multiply_param(¶ms[1], 0.5, py), + py, + ); + let param_third_p = radd_param( + multiply_param(¶ms[2], 0.5, py), + multiply_param(¶ms[1], -0.5, py), + py, + ); + let param_first_u = radd_param( + multiply_param(¶ms[1], -0.5, py), + multiply_param(¶ms[2], -0.5, py), + py, + ); + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::PhaseGate, + smallvec![params[3].clone()], + smallvec![Qubit(0)], + ), + ( + Self::PhaseGate, + smallvec![param_second_p], + smallvec![Qubit(0)], + ), + ( + Self::PhaseGate, + smallvec![param_third_p], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::UGate, + smallvec![ + multiply_param(¶ms[0], -0.5, py), + Param::Float(0.), + param_first_u + ], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::UGate, + smallvec![ + multiply_param(¶ms[0], 0.5, py), + params[1].clone(), + Param::Float(0.) + ], + smallvec![Qubit(1)], + ), + ], + FLOAT_ZERO, + ) + .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)]; @@ -1161,6 +1254,35 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CU1Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::U1Gate, + smallvec![multiply_param(¶ms[0], 0.5, py)], + smallvec![Qubit(0)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::U1Gate, + smallvec![multiply_param(¶ms[0], -0.5, py)], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::U1Gate, + smallvec![multiply_param(¶ms[0], 0.5, py)], + smallvec![Qubit(1)], + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CPhaseGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; @@ -1193,6 +1315,59 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CU3Gate => Python::with_gil(|py| -> Option { + let param_first_u1 = radd_param( + multiply_param(¶ms[2], 0.5, py), + multiply_param(¶ms[1], 0.5, py), + py, + ); + let param_second_u1 = radd_param( + multiply_param(¶ms[2], 0.5, py), + multiply_param(¶ms[1], -0.5, py), + py, + ); + let param_first_u3 = radd_param( + multiply_param(¶ms[1], -0.5, py), + multiply_param(¶ms[2], -0.5, py), + py, + ); + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::U1Gate, smallvec![param_first_u1], smallvec![Qubit(0)]), + ( + Self::U1Gate, + smallvec![param_second_u1], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::U3Gate, + smallvec![ + multiply_param(¶ms[0], -0.5, py), + Param::Float(0.), + param_first_u3 + ], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::U3Gate, + smallvec![ + multiply_param(¶ms[0], 0.5, py), + params[1].clone(), + Param::Float(0.) + ], + smallvec![Qubit(1)], + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CSGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; @@ -1217,6 +1392,109 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::C3XGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 4, + [ + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(0)], + ), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(1)], + ), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(2)], + ), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(2)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(2)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(2)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CSdgGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; @@ -1241,6 +1519,73 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::C3SXGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 4, + [ + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(0), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(1), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(1), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(2), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(2), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(2), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(2), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ], + 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)]; @@ -1258,6 +1603,25 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CCZGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + (Self::HGate, smallvec![], smallvec![Qubit(2)]), + ( + Self::CCXGate, + smallvec![], + smallvec![Qubit(0), Qubit(1), Qubit(2)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(2)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CSwapGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1292,10 +1656,7 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CUGate => todo!(), - Self::CU1Gate => todo!(), - Self::CU3Gate => todo!(), - Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), + Self::C4XGate => todo!(), Self::DCXGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1310,8 +1671,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 { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; @@ -1396,6 +1755,116 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::RCCXGate => Python::with_gil(|py| -> Option { + let q2 = smallvec![Qubit(2)]; + let q0_2 = smallvec![Qubit(0), Qubit(2)]; + let q1_2 = smallvec![Qubit(1), Qubit(2)]; + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + q2.clone(), + ), + (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q1_2.clone()), + (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q0_2), + (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q1_2), + (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + q2, + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::RC3XGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 4, + [ + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![Qubit(3)], + ), + ( + Self::U1Gate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(3)], + ), + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(3)], + ), + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![Qubit(3)], + ), + ( + Self::U1Gate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(3)], + ), + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![Qubit(3)], + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), } } @@ -1418,7 +1887,7 @@ fn clone_param(param: &Param, py: Python) -> Param { fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { match param { - Param::Float(theta) => Param::Float(*theta * mult), + Param::Float(theta) => Param::Float(theta * mult), Param::ParameterExpression(theta) => Param::ParameterExpression( theta .clone_ref(py) @@ -1442,6 +1911,21 @@ fn add_param(param: &Param, summand: f64, py: Python) -> Param { } } +fn radd_param(param1: Param, param2: Param, py: Python) -> Param { + match [param1, param2] { + [Param::Float(theta), Param::Float(lambda)] => Param::Float(theta + lambda), + [Param::ParameterExpression(theta), Param::ParameterExpression(lambda)] => { + Param::ParameterExpression( + theta + .clone_ref(py) + .call_method1(py, intern!(py, "__radd__"), (lambda,)) + .expect("Parameter expression addition failed"), + ) + } + _ => unreachable!(), + } +} + /// This class is used to wrap a Python side Instruction that is not in the standard library #[derive(Clone, Debug)] #[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")] diff --git a/crates/circuit/src/util.rs b/crates/circuit/src/util.rs index 11562b0a48cd..1b6402720c46 100644 --- a/crates/circuit/src/util.rs +++ b/crates/circuit/src/util.rs @@ -39,6 +39,7 @@ pub type GateArray0Q = [[Complex64; 1]; 1]; pub type GateArray1Q = [[Complex64; 2]; 2]; pub type GateArray2Q = [[Complex64; 4]; 4]; pub type GateArray3Q = [[Complex64; 8]; 8]; +pub type GateArray4Q = [[Complex64; 16]; 16]; // Use prefix `C_` to distinguish from real, for example pub const C_ZERO: Complex64 = c64(0., 0.); diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py index 07684097f8cc..7f1d32eb914c 100644 --- a/qiskit/circuit/library/standard_gates/u.py +++ b/qiskit/circuit/library/standard_gates/u.py @@ -262,6 +262,8 @@ class CUGate(ControlledGate): \end{pmatrix} """ + _standard_gate = StandardGate.CUGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index f141146b72dc..e62a132670ff 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -208,6 +208,8 @@ class CU1Gate(ControlledGate): phase difference. """ + _standard_gate = StandardGate.CU1Gate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index f191609ea8f1..80581bf55a5d 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -230,6 +230,8 @@ class CU3Gate(ControlledGate): \end{pmatrix} """ + _standard_gate = StandardGate.CU3Gate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index a08e4a55a960..5605038680d1 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -522,6 +522,8 @@ class RCCXGate(SingletonGate): with the :meth:`~qiskit.circuit.QuantumCircuit.rccx` method. """ + _standard_gate = StandardGate.RCCXGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create a new simplified CCX gate.""" super().__init__("rccx", 3, [], label=label, duration=duration, unit=unit) @@ -578,6 +580,8 @@ class C3SXGate(SingletonControlledGate): [1] Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf """ + _standard_gate = StandardGate.C3SXGate + def __init__( self, label: Optional[str] = None, @@ -682,6 +686,8 @@ class C3XGate(SingletonControlledGate): This implementation uses :math:`\sqrt{T}` and 14 CNOT gates. """ + _standard_gate = StandardGate.C3XGate + def __init__( self, label: Optional[str] = None, @@ -869,6 +875,8 @@ class RC3XGate(SingletonGate): with the :meth:`~qiskit.circuit.QuantumCircuit.rcccx` method. """ + _standard_gate = StandardGate.RC3XGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create a new RC3X gate.""" super().__init__("rcccx", 4, [], label=label, duration=duration, unit=unit) diff --git a/qiskit/circuit/library/standard_gates/z.py b/qiskit/circuit/library/standard_gates/z.py index 19e4382cd846..4b2364178a94 100644 --- a/qiskit/circuit/library/standard_gates/z.py +++ b/qiskit/circuit/library/standard_gates/z.py @@ -286,6 +286,8 @@ class CCZGate(SingletonControlledGate): the target qubit if the control qubits are in the :math:`|11\rangle` state. """ + _standard_gate = StandardGate.CCZGate + def __init__( self, label: Optional[str] = None, diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index e2acf9dc2026..9d7cdfa2fb50 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -4545,7 +4545,7 @@ def id(self, qubit: QubitSpecifier) -> InstructionSet: # pylint: disable=invali Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.IGate, None, qargs=[qubit]) + return self._append_standard_gate(StandardGate.IGate, [], qargs=[qubit]) def ms(self, theta: ParameterValueType, qubits: Sequence[QubitSpecifier]) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.MSGate`. @@ -4711,10 +4711,8 @@ def rccx( Returns: A handle to the instructions created. """ - from .library.standard_gates.x import RCCXGate - - return self.append( - RCCXGate(), [control_qubit1, control_qubit2, target_qubit], [], copy=False + return self._append_standard_gate( + StandardGate.RCCXGate, [], qargs=[control_qubit1, control_qubit2, target_qubit] ) def rcccx( @@ -4737,13 +4735,10 @@ def rcccx( Returns: A handle to the instructions created. """ - from .library.standard_gates.x import RC3XGate - - return self.append( - RC3XGate(), - [control_qubit1, control_qubit2, control_qubit3, target_qubit], + return self._append_standard_gate( + StandardGate.RC3XGate, [], - copy=False, + qargs=[control_qubit1, control_qubit2, control_qubit3, target_qubit], ) def rx( @@ -4761,7 +4756,7 @@ def rx( Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.RXGate, [theta], [qubit], None, label=label) + return self._append_standard_gate(StandardGate.RXGate, [theta], [qubit], label=label) def crx( self, @@ -4790,7 +4785,7 @@ def crx( # 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 + StandardGate.CRXGate, [theta], [control_qubit, target_qubit], label=label ) from .library.standard_gates.rx import CRXGate @@ -4834,7 +4829,7 @@ def ry( Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.RYGate, [theta], [qubit], None, label=label) + return self._append_standard_gate(StandardGate.RYGate, [theta], [qubit], label=label) def cry( self, @@ -4863,7 +4858,7 @@ def cry( # 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 + StandardGate.CRYGate, [theta], [control_qubit, target_qubit], label=label ) from .library.standard_gates.ry import CRYGate @@ -4904,7 +4899,7 @@ def rz(self, phi: ParameterValueType, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.RZGate, [phi], [qubit], None) + return self._append_standard_gate(StandardGate.RZGate, [phi], [qubit]) def crz( self, @@ -4933,7 +4928,7 @@ def crz( # 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 + StandardGate.CRZGate, [theta], [control_qubit, target_qubit], label=label ) from .library.standard_gates.rz import CRZGate @@ -5305,6 +5300,15 @@ def cu( 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.CUGate, + [theta, phi, lam, gamma], + qargs=[control_qubit, target_qubit], + label=label, + ) + from .library.standard_gates.u import CUGate return self.append( @@ -5405,13 +5409,10 @@ def ccx( 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]: + # if the control state is |11> use the fast Rust version of the gate + if ctrl_state is None or ctrl_state in ["11", 3]: return self._append_standard_gate( - StandardGate.CCXGate, - [], - qargs=[control_qubit1, control_qubit2, target_qubit], - cargs=None, + StandardGate.CCXGate, [], qargs=[control_qubit1, control_qubit2, target_qubit] ) from .library.standard_gates.x import CCXGate @@ -5518,7 +5519,7 @@ def y(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.YGate, None, qargs=[qubit]) + return self._append_standard_gate(StandardGate.YGate, [], qargs=[qubit]) def cy( self, @@ -5548,7 +5549,6 @@ def cy( StandardGate.CYGate, [], qargs=[control_qubit, target_qubit], - cargs=None, label=label, ) @@ -5572,7 +5572,7 @@ def z(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.ZGate, None, qargs=[qubit]) + return self._append_standard_gate(StandardGate.ZGate, [], qargs=[qubit]) def cz( self, @@ -5635,6 +5635,15 @@ def ccz( Returns: A handle to the instructions created. """ + # if the control state is |11> use the fast Rust version of the gate + if ctrl_state is None or ctrl_state in ["11", 3]: + return self._append_standard_gate( + StandardGate.CCZGate, + [], + qargs=[control_qubit1, control_qubit2, target_qubit], + label=label, + ) + from .library.standard_gates.z import CCZGate return self.append( diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index 6c0cc977e58a..c344ff309964 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -12,6 +12,7 @@ """Rust gate definition tests""" +from functools import partial from math import pi from test import QiskitTestCase @@ -19,10 +20,13 @@ import numpy as np from qiskit.circuit import QuantumCircuit, CircuitInstruction +from qiskit.circuit.library.standard_gates import C3XGate, CU1Gate, CZGate, CCZGate from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping +from qiskit.quantum_info import Operator SKIP_LIST = {"rx", "ry", "ecr"} -CUSTOM_MAPPING = {"x", "rz"} +CUSTOM_NAME_MAPPING = {"mcx": C3XGate()} +MATRIX_SKIP_LIST = {"c3sx"} class TestRustGateEquivalence(QiskitTestCase): @@ -31,8 +35,9 @@ class TestRustGateEquivalence(QiskitTestCase): def setUp(self): super().setUp() self.standard_gates = get_standard_gate_name_mapping() + self.standard_gates.update(CUSTOM_NAME_MAPPING) # Pre-warm gate mapping cache, this is needed so rust -> py conversion is done - qc = QuantumCircuit(3) + qc = QuantumCircuit(5) for gate in self.standard_gates.values(): if getattr(gate, "_standard_gate", None): if gate.params: @@ -73,10 +78,12 @@ 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": + # In the following cases, Rust uses U but python still uses U3 and U2 + if ( + name in {"x", "y", "h", "r", "p", "u2", "u3", "cu", "crx"} + and rs_inst.operation.name == "u" + ): if py_inst.operation.name == "u3": self.assertEqual(rs_inst.operation.params, py_inst.operation.params) elif py_inst.operation.name == "u2": @@ -93,8 +100,11 @@ 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/u3 in some cases - elif rs_inst.operation.name == "p" and not name in ["cp", "cs", "csdg"]: + # In the following cases, Rust uses P but python still uses U1 and U3 + elif ( + name in {"z", "s", "sdg", "t", "tdg", "rz", "u1", "crx"} + and rs_inst.operation.name == "p" + ): if py_inst.operation.name == "u1": self.assertEqual(py_inst.operation.name, "u1") self.assertEqual(rs_inst.operation.params, py_inst.operation.params) @@ -111,8 +121,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 cp but python still uses cu1 in some cases - elif rs_inst.operation.name == "cp": + # In the following cases, Rust uses CP but python still uses CU1 + elif name in {"csx"} and rs_inst.operation.name == "cp": self.assertEqual(py_inst.operation.name, "cu1") self.assertEqual(rs_inst.operation.params, py_inst.operation.params) self.assertEqual( @@ -131,6 +141,9 @@ def test_matrix(self): """Test matrices are the same in rust space.""" for name, gate_class in self.standard_gates.items(): standard_gate = getattr(gate_class, "_standard_gate", None) + if name in MATRIX_SKIP_LIST: + # to_matrix not defined for type + continue if standard_gate is None: # gate is not in rust yet continue @@ -148,6 +161,9 @@ def test_name(self): if standard_gate is None: # gate is not in rust yet continue + if gate_class.name == "mcx": + # ambiguous gate name + continue with self.subTest(name=name): self.assertEqual(gate_class.name, standard_gate.name) @@ -175,3 +191,50 @@ def test_num_params(self): self.assertEqual( len(gate_class.params), standard_gate.num_params, msg=f"{name} not equal" ) + + def test_non_default_controls(self): + """Test that controlled gates with a non-default ctrl_state + are not using the standard rust representation.""" + # CZ and CU1 are diagonal matrices with one non-1 term + # in the diagonal (see op_terms) + gate_classes = [CZGate, partial(CU1Gate, 0.1)] + op_terms = [-1, 0.99500417 + 0.09983342j] + + for gate_cls, term in zip(gate_classes, op_terms): + with self.subTest(name="2q gates"): + default_op = np.diag([1, 1, 1, term]) + non_default_op = np.diag([1, 1, term, 1]) + state_out_map = { + 1: default_op, + "1": default_op, + None: default_op, + 0: non_default_op, + "0": non_default_op, + } + for state, op in state_out_map.items(): + circuit = QuantumCircuit(2) + gate = gate_cls(ctrl_state=state) + circuit.append(gate, [0, 1]) + self.assertIsNotNone(getattr(gate, "_standard_gate", None)) + np.testing.assert_almost_equal(circuit.data[0].operation.to_matrix(), op) + + with self.subTest(name="3q gate"): + default_op = np.diag([1, 1, 1, 1, 1, 1, 1, -1]) + non_default_op_0 = np.diag([1, 1, 1, 1, -1, 1, 1, 1]) + non_default_op_1 = np.diag([1, 1, 1, 1, 1, -1, 1, 1]) + non_default_op_2 = np.diag([1, 1, 1, 1, 1, 1, -1, 1]) + state_out_map = { + 3: default_op, + "11": default_op, + None: default_op, + 0: non_default_op_0, + 1: non_default_op_1, + "01": non_default_op_1, + "10": non_default_op_2, + } + for state, op in state_out_map.items(): + circuit = QuantumCircuit(3) + gate = CCZGate(ctrl_state=state) + circuit.append(gate, [0, 1, 2]) + self.assertIsNotNone(getattr(gate, "_standard_gate", None)) + np.testing.assert_almost_equal(Operator(circuit.data[0].operation).to_matrix(), op) From 4fe9dbc6a80dfb113eea6bc18da8bc5e80c4d4f2 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 8 Jul 2024 15:47:23 -0400 Subject: [PATCH 69/89] Use rust gates for ConsolidateBlocks (#12704) * Use rust gates for ConsolidateBlocks This commit moves to use rust gates for the ConsolidateBlocks transpiler pass. Instead of generating the unitary matrices for the gates in a 2q block Python side and passing that list to a rust function this commit switches to passing a list of DAGOpNodes to the rust and then generating the matrices inside the rust function directly. This is similar to what was done in #12650 for Optimize1qGatesDecomposition. Besides being faster to get the matrix for standard gates, it also reduces the eager construction of Python gate objects which was a significant source of overhead after #12459. To that end this builds on the thread of work in the two PRs #12692 and #12701 which changed the access patterns for other passes to minimize eager gate object construction. * Add rust filter function for DAGCircuit.collect_2q_runs() * Update crates/accelerate/src/convert_2q_block_matrix.rs --------- Co-authored-by: John Lapeyre --- .../accelerate/src/convert_2q_block_matrix.rs | 104 ++++++++++++++++-- crates/circuit/src/bit_data.rs | 8 +- crates/circuit/src/imports.rs | 1 + crates/circuit/src/lib.rs | 2 +- qiskit/dagcircuit/dagcircuit.py | 24 +--- .../passes/optimization/consolidate_blocks.py | 19 ++-- qiskit/transpiler/passes/utils/__init__.py | 1 - .../passes/utils/block_to_matrix.py | 47 -------- 8 files changed, 120 insertions(+), 86 deletions(-) delete mode 100644 qiskit/transpiler/passes/utils/block_to_matrix.py diff --git a/crates/accelerate/src/convert_2q_block_matrix.rs b/crates/accelerate/src/convert_2q_block_matrix.rs index 9c179397d641..7a9165777dc9 100644 --- a/crates/accelerate/src/convert_2q_block_matrix.rs +++ b/crates/accelerate/src/convert_2q_block_matrix.rs @@ -10,7 +10,9 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use pyo3::intern; use pyo3::prelude::*; +use pyo3::types::PyDict; use pyo3::wrap_pyfunction; use pyo3::Python; @@ -20,32 +22,84 @@ use numpy::ndarray::{aview2, Array2, ArrayView2}; use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2}; use smallvec::SmallVec; +use qiskit_circuit::bit_data::BitData; +use qiskit_circuit::circuit_instruction::{operation_type_to_py, CircuitInstruction}; +use qiskit_circuit::dag_node::DAGOpNode; use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY; +use qiskit_circuit::imports::QI_OPERATOR; +use qiskit_circuit::operations::{Operation, OperationType}; + +use crate::QiskitError; + +fn get_matrix_from_inst<'py>( + py: Python<'py>, + inst: &'py CircuitInstruction, +) -> PyResult> { + match inst.operation.matrix(&inst.params) { + Some(mat) => Ok(mat), + None => match inst.operation { + OperationType::Standard(_) => Err(QiskitError::new_err( + "Parameterized gates can't be consolidated", + )), + OperationType::Gate(_) => Ok(QI_OPERATOR + .get_bound(py) + .call1((operation_type_to_py(py, inst)?,))? + .getattr(intern!(py, "data"))? + .extract::>()? + .as_array() + .to_owned()), + _ => unreachable!("Only called for unitary ops"), + }, + } +} /// Return the matrix Operator resulting from a block of Instructions. #[pyfunction] #[pyo3(text_signature = "(op_list, /")] pub fn blocks_to_matrix( py: Python, - op_list: Vec<(PyReadonlyArray2, SmallVec<[u8; 2]>)>, + op_list: Vec>, + block_index_map_dict: &Bound, ) -> PyResult>> { + // Build a BitData in block_index_map_dict order. block_index_map_dict is a dict of bits to + // indices mapping the order of the qargs in the block. There should only be 2 entries since + // there are only 2 qargs here (e.g. `{Qubit(): 0, Qubit(): 1}`) so we need to ensure that + // we added the qubits to bit data in the correct index order. + let mut index_map: Vec = (0..block_index_map_dict.len()).map(|_| py.None()).collect(); + for bit_tuple in block_index_map_dict.items() { + let (bit, index): (PyObject, usize) = bit_tuple.extract()?; + index_map[index] = bit; + } + let mut bit_map: BitData = BitData::new(py, "qargs".to_string()); + for bit in index_map { + bit_map.add(py, bit.bind(py), true)?; + } let identity = aview2(&ONE_QUBIT_IDENTITY); - let input_matrix = op_list[0].0.as_array(); - let mut matrix: Array2 = match op_list[0].1.as_slice() { + let first_node = &op_list[0]; + let input_matrix = get_matrix_from_inst(py, &first_node.instruction)?; + let mut matrix: Array2 = match bit_map + .map_bits(first_node.instruction.qubits.bind(py).iter())? + .collect::>() + .as_slice() + { [0] => kron(&identity, &input_matrix), [1] => kron(&input_matrix, &identity), - [0, 1] => input_matrix.to_owned(), - [1, 0] => change_basis(input_matrix), + [0, 1] => input_matrix, + [1, 0] => change_basis(input_matrix.view()), [] => Array2::eye(4), _ => unreachable!(), }; - for (op_matrix, q_list) in op_list.into_iter().skip(1) { - let op_matrix = op_matrix.as_array(); + for node in op_list.into_iter().skip(1) { + let op_matrix = get_matrix_from_inst(py, &node.instruction)?; + let q_list = bit_map + .map_bits(node.instruction.qubits.bind(py).iter())? + .map(|x| x as u8) + .collect::>(); let result = match q_list.as_slice() { [0] => Some(kron(&identity, &op_matrix)), [1] => Some(kron(&op_matrix, &identity)), - [1, 0] => Some(change_basis(op_matrix)), + [1, 0] => Some(change_basis(op_matrix.view())), [] => Some(Array2::eye(4)), _ => None, }; @@ -71,8 +125,42 @@ pub fn change_basis(matrix: ArrayView2) -> Array2 { trans_matrix } +#[pyfunction] +pub fn collect_2q_blocks_filter(node: &Bound) -> Option { + match node.downcast::() { + Ok(bound_node) => { + let node = bound_node.borrow(); + match &node.instruction.operation { + OperationType::Standard(gate) => Some( + gate.num_qubits() <= 2 + && node + .instruction + .extra_attrs + .as_ref() + .and_then(|attrs| attrs.condition.as_ref()) + .is_none() + && !node.is_parameterized(), + ), + OperationType::Gate(gate) => Some( + gate.num_qubits() <= 2 + && node + .instruction + .extra_attrs + .as_ref() + .and_then(|attrs| attrs.condition.as_ref()) + .is_none() + && !node.is_parameterized(), + ), + _ => Some(false), + } + } + Err(_) => None, + } +} + #[pymodule] pub fn convert_2q_block_matrix(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(blocks_to_matrix))?; + m.add_wrapped(wrap_pyfunction!(collect_2q_blocks_filter))?; Ok(()) } diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index 40540f9df5a4..977d1b34e496 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -70,7 +70,7 @@ impl PartialEq for BitAsKey { impl Eq for BitAsKey {} #[derive(Clone, Debug)] -pub(crate) struct BitData { +pub struct BitData { /// The public field name (i.e. `qubits` or `clbits`). description: String, /// Registered Python bits. @@ -81,7 +81,7 @@ pub(crate) struct BitData { cached: Py, } -pub(crate) struct BitNotFoundError<'py>(pub(crate) Bound<'py, PyAny>); +pub struct BitNotFoundError<'py>(pub(crate) Bound<'py, PyAny>); impl<'py> From> for PyErr { fn from(error: BitNotFoundError) -> Self { @@ -111,6 +111,10 @@ where self.bits.len() } + pub fn is_empty(&self) -> bool { + self.bits.is_empty() + } + /// Gets a reference to the underlying vector of Python bits. #[inline] pub fn bits(&self) -> &Vec { diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index dfefb0a348f8..d277dea9f89c 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -74,6 +74,7 @@ pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = pub static CONTROLLED_GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit", "ControlledGate"); pub static DEEPCOPY: ImportOnceCell = ImportOnceCell::new("copy", "deepcopy"); +pub static QI_OPERATOR: ImportOnceCell = ImportOnceCell::new("qiskit.quantum_info", "Operator"); pub static WARNINGS_WARN: ImportOnceCell = ImportOnceCell::new("warnings", "warn"); /// A mapping from the enum variant in crate::operations::StandardGate to the python diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 9f0a8017bf21..c7469434c668 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -10,6 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +pub mod bit_data; pub mod circuit_data; pub mod circuit_instruction; pub mod dag_node; @@ -20,7 +21,6 @@ pub mod parameter_table; pub mod slice; pub mod util; -mod bit_data; mod interner; use pyo3::prelude::*; diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index b93a90e47f7b..5d6739d72198 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -55,6 +55,7 @@ from qiskit.circuit.bit import Bit from qiskit.pulse import Schedule from qiskit._accelerate.euler_one_qubit_decomposer import collect_1q_runs_filter +from qiskit._accelerate.convert_2q_block_matrix import collect_2q_blocks_filter BitLocations = namedtuple("BitLocations", ("index", "registers")) # The allowable arguments to :meth:`DAGCircuit.copy_empty_like`'s ``vars_mode``. @@ -1348,9 +1349,9 @@ def replace_block_with_op( for nd in node_block: block_qargs |= set(nd.qargs) block_cargs |= set(nd.cargs) - if (condition := getattr(nd.op, "condition", None)) is not None: + if (condition := getattr(nd, "condition", None)) is not None: block_cargs.update(condition_resources(condition).clbits) - elif isinstance(nd.op, SwitchCaseOp): + elif nd.name in CONTROL_FLOW_OP_NAMES and isinstance(nd.op, SwitchCaseOp): if isinstance(nd.op.target, Clbit): block_cargs.add(nd.op.target) elif isinstance(nd.op.target, ClassicalRegister): @@ -2158,28 +2159,13 @@ def collect_1q_runs(self) -> list[list[DAGOpNode]]: def collect_2q_runs(self): """Return a set of non-conditional runs of 2q "op" nodes.""" - to_qid = {} - for i, qubit in enumerate(self.qubits): - to_qid[qubit] = i - - def filter_fn(node): - if isinstance(node, DAGOpNode): - return ( - isinstance(node.op, Gate) - and len(node.qargs) <= 2 - and not getattr(node.op, "condition", None) - and not node.op.is_parameterized() - ) - else: - return None - def color_fn(edge): if isinstance(edge, Qubit): - return to_qid[edge] + return self.find_bit(edge).index else: return None - return rx.collect_bicolor_runs(self._multi_graph, filter_fn, color_fn) + return rx.collect_bicolor_runs(self._multi_graph, collect_2q_blocks_filter, color_fn) def nodes_on_wire(self, wire, only_ops=False): """ diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 8dca049aa9ac..72a08efe0f7d 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -24,10 +24,11 @@ from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate from qiskit.circuit.library.standard_gates import CXGate from qiskit.transpiler.basepasses import TransformationPass -from qiskit.circuit.controlflow import ControlFlowOp from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passes.synthesis import unitary_synthesis -from qiskit.transpiler.passes.utils import _block_to_matrix +from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES +from qiskit._accelerate.convert_2q_block_matrix import blocks_to_matrix + from .collect_1q_runs import Collect1qRuns from .collect_2q_blocks import Collect2qBlocks @@ -105,14 +106,14 @@ def run(self, dag): block_cargs = set() for nd in block: block_qargs |= set(nd.qargs) - if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None): - block_cargs |= set(getattr(nd.op, "condition", None)[0]) + if isinstance(nd, DAGOpNode) and getattr(nd, "condition", None): + block_cargs |= set(getattr(nd, "condition", None)[0]) all_block_gates.add(nd) block_index_map = self._block_qargs_to_indices(dag, block_qargs) for nd in block: - if nd.op.name == basis_gate_name: + if nd.name == basis_gate_name: basis_count += 1 - if self._check_not_in_basis(dag, nd.op.name, nd.qargs): + if self._check_not_in_basis(dag, nd.name, nd.qargs): outside_basis = True if len(block_qargs) > 2: q = QuantumRegister(len(block_qargs)) @@ -124,7 +125,7 @@ def run(self, dag): qc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) unitary = UnitaryGate(Operator(qc), check_input=False) else: - matrix = _block_to_matrix(block, block_index_map) + matrix = blocks_to_matrix(block, block_index_map) unitary = UnitaryGate(matrix, check_input=False) max_2q_depth = 20 # If depth > 20, there will be 1q gates to consolidate. @@ -192,7 +193,9 @@ def _handle_control_flow_ops(self, dag): pass_manager.append(Collect2qBlocks()) pass_manager.append(self) - for node in dag.op_nodes(ControlFlowOp): + for node in dag.op_nodes(): + if node.name not in CONTROL_FLOW_OP_NAMES: + continue node.op = node.op.replace_blocks(pass_manager.run(block) for block in node.op.blocks) return dag diff --git a/qiskit/transpiler/passes/utils/__init__.py b/qiskit/transpiler/passes/utils/__init__.py index 7227409a213c..9cba3b1a50cd 100644 --- a/qiskit/transpiler/passes/utils/__init__.py +++ b/qiskit/transpiler/passes/utils/__init__.py @@ -31,4 +31,3 @@ # Utility functions from . import control_flow -from .block_to_matrix import _block_to_matrix diff --git a/qiskit/transpiler/passes/utils/block_to_matrix.py b/qiskit/transpiler/passes/utils/block_to_matrix.py deleted file mode 100644 index 4b8a09cd30f7..000000000000 --- a/qiskit/transpiler/passes/utils/block_to_matrix.py +++ /dev/null @@ -1,47 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# 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. - -"""Converts any block of 2 qubit gates into a matrix.""" - -from qiskit.quantum_info import Operator -from qiskit.exceptions import QiskitError -from qiskit._accelerate.convert_2q_block_matrix import blocks_to_matrix - - -def _block_to_matrix(block, block_index_map): - """ - The function converts any sequence of operations between two qubits into a matrix - that can be utilized to create a gate or a unitary. - - Args: - block (List(DAGOpNode)): A block of operations on two qubits. - block_index_map (dict(Qubit, int)): The mapping of the qubit indices in the main circuit. - - Returns: - NDArray: Matrix representation of the block of operations. - """ - op_list = [] - block_index_length = len(block_index_map) - if block_index_length != 2: - raise QiskitError( - "This function can only operate with blocks of 2 qubits." - + f"This block had {block_index_length}" - ) - for node in block: - try: - current = node.op.to_matrix() - except QiskitError: - current = Operator(node.op).data - q_list = [block_index_map[qubit] for qubit in node.qargs] - op_list.append((current, q_list)) - matrix = blocks_to_matrix(op_list) - return matrix From d86f9958516ee7f48359ddc7364050bb791602d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:25:54 +0200 Subject: [PATCH 70/89] Fix `C3SXGate` `to_matrix` method (#12742) * Fix c3sx matrix * Apply Jake's suggestion --- qiskit/circuit/library/standard_gates/x.py | 2 ++ .../notes/fix-c3sx-gate-matrix-050cf9f9ac3b2b82.yaml | 5 +++++ test/python/circuit/test_controlled_gate.py | 6 ++++++ test/python/circuit/test_rust_equivalence.py | 4 ---- 4 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/fix-c3sx-gate-matrix-050cf9f9ac3b2b82.yaml diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 5605038680d1..614b4f4d2dd6 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -22,6 +22,7 @@ from qiskit._accelerate.circuit import StandardGate _X_ARRAY = [[0, 1], [1, 0]] +_SX_ARRAY = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]] @with_gate_array(_X_ARRAY) @@ -571,6 +572,7 @@ def __eq__(self, other): return isinstance(other, RCCXGate) +@with_controlled_gate_array(_SX_ARRAY, num_ctrl_qubits=3, cached_states=(7,)) class C3SXGate(SingletonControlledGate): """The 3-qubit controlled sqrt-X gate. diff --git a/releasenotes/notes/fix-c3sx-gate-matrix-050cf9f9ac3b2b82.yaml b/releasenotes/notes/fix-c3sx-gate-matrix-050cf9f9ac3b2b82.yaml new file mode 100644 index 000000000000..accd20de4ae5 --- /dev/null +++ b/releasenotes/notes/fix-c3sx-gate-matrix-050cf9f9ac3b2b82.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixed a missing decorator in :class:`.C3SXGate` that made it fail if :meth:`.Gate.to_matrix` was called. + The gate matrix is now return as expected. diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index c845077d32dc..e36ee04e2162 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -230,6 +230,12 @@ def test_special_cases_equivalent_to_controlled_base_gate(self): # Ensure that both the array form (if the gate overrides `__array__`) and the # circuit-definition form are tested. self.assertTrue(Operator(special_case_gate).equiv(naive_operator)) + if not isinstance(special_case_gate, (MCXGate, MCPhaseGate, MCU1Gate)): + # Ensure that the to_matrix method yields the same result + np.testing.assert_allclose( + special_case_gate.to_matrix(), naive_operator.to_matrix(), atol=1e-8 + ) + if not isinstance(special_case_gate, CXGate): # CX is treated like a primitive within Terra, and doesn't have a definition. self.assertTrue(Operator(special_case_gate.definition).equiv(naive_operator)) diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index c344ff309964..29cc0723bb60 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -26,7 +26,6 @@ SKIP_LIST = {"rx", "ry", "ecr"} CUSTOM_NAME_MAPPING = {"mcx": C3XGate()} -MATRIX_SKIP_LIST = {"c3sx"} class TestRustGateEquivalence(QiskitTestCase): @@ -141,9 +140,6 @@ def test_matrix(self): """Test matrices are the same in rust space.""" for name, gate_class in self.standard_gates.items(): standard_gate = getattr(gate_class, "_standard_gate", None) - if name in MATRIX_SKIP_LIST: - # to_matrix not defined for type - continue if standard_gate is None: # gate is not in rust yet continue From fa3d6df04f57d6a5bcc658a7f1a6e5a99984b949 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 10 Jul 2024 11:27:55 +0300 Subject: [PATCH 71/89] Adding QFT gate to natively reason about Quantum Fourier Transforms (#11463) * initial commit * release notes * fixing synthesis plugin options * finalize merge conflicts * fixing default option values for qft plugins' * additional tests for qft plugins * renaming QftGate to QFTGate * Also renaming Qft to QFT in synthesis method names * appplying Jake's suggestion from code review * inlining _basic_definition into _define * docstring improvements * more docstring improvements * renaming do_swaps to reverse_qubits in the new code * typos * adding synth_qft_full to __init__ * round of suggestions from code review * another round of code review suggestions * fixes * also adding QFTGate plugins to the docs --- pyproject.toml | 3 + qiskit/circuit/library/__init__.py | 3 +- .../circuit/library/basis_change/__init__.py | 2 +- qiskit/circuit/library/basis_change/qft.py | 46 +++++- qiskit/qpy/binary_io/circuits.py | 2 + qiskit/synthesis/__init__.py | 3 +- qiskit/synthesis/qft/__init__.py | 1 + qiskit/synthesis/qft/qft_decompose_full.py | 79 +++++++++++ qiskit/synthesis/qft/qft_decompose_lnn.py | 26 ++-- .../passes/synthesis/high_level_synthesis.py | 133 +++++++++++++++++- .../notes/add-qft-gate-fd4e08f6721a9da4.yaml | 22 +++ test/python/circuit/library/test_qft.py | 119 +++++++++++++++- test/python/circuit/test_gate_definitions.py | 1 + test/python/synthesis/test_qft_synthesis.py | 51 ++++++- .../transpiler/test_high_level_synthesis.py | 47 ++++++- 15 files changed, 510 insertions(+), 28 deletions(-) create mode 100644 qiskit/synthesis/qft/qft_decompose_full.py create mode 100644 releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml diff --git a/pyproject.toml b/pyproject.toml index 2f62557aa15b..b1f7b039e407 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,6 +88,9 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS "permutation.kms" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisPermutation" "permutation.basic" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:BasicSynthesisPermutation" "permutation.acg" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:ACGSynthesisPermutation" +"qft.full" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:QFTSynthesisFull" +"qft.line" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:QFTSynthesisLine" +"qft.default" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:QFTSynthesisFull" "permutation.token_swapper" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:TokenSwapperSynthesisPermutation" [project.entry-points."qiskit.transpiler.init"] diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index a9ae005d982d..39266cf4ca17 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -226,6 +226,7 @@ :template: autosummary/class_no_inherited_members.rst QFT + QFTGate Arithmetic Circuits =================== @@ -523,7 +524,7 @@ XOR, InnerProduct, ) -from .basis_change import QFT +from .basis_change import QFT, QFTGate from .arithmetic import ( FunctionalPauliRotations, LinearPauliRotations, diff --git a/qiskit/circuit/library/basis_change/__init__.py b/qiskit/circuit/library/basis_change/__init__.py index c2b8896608d4..16b7e53cc2d3 100644 --- a/qiskit/circuit/library/basis_change/__init__.py +++ b/qiskit/circuit/library/basis_change/__init__.py @@ -12,4 +12,4 @@ """The basis change circuits.""" -from .qft import QFT +from .qft import QFT, QFTGate diff --git a/qiskit/circuit/library/basis_change/qft.py b/qiskit/circuit/library/basis_change/qft.py index a8816ad470a2..2ec6dd69cb79 100644 --- a/qiskit/circuit/library/basis_change/qft.py +++ b/qiskit/circuit/library/basis_change/qft.py @@ -10,14 +10,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Quantum Fourier Transform Circuit.""" +"""Define a Quantum Fourier Transform circuit (QFT) and a native gate (QFTGate).""" -from typing import Optional +from __future__ import annotations import warnings import numpy as np -from qiskit.circuit import QuantumCircuit, QuantumRegister, CircuitInstruction - +from qiskit.circuit.quantumcircuit import QuantumCircuit, QuantumRegister, CircuitInstruction, Gate from ..blueprintcircuit import BlueprintCircuit @@ -75,12 +74,12 @@ class QFT(BlueprintCircuit): def __init__( self, - num_qubits: Optional[int] = None, + num_qubits: int | None = None, approximation_degree: int = 0, do_swaps: bool = True, inverse: bool = False, insert_barriers: bool = False, - name: Optional[str] = None, + name: str | None = None, ) -> None: """Construct a new QFT circuit. @@ -293,3 +292,38 @@ def _build(self) -> None: wrapped = circuit.to_instruction() if self.insert_barriers else circuit.to_gate() self.compose(wrapped, qubits=self.qubits, inplace=True) + + +class QFTGate(Gate): + r"""Quantum Fourier Transform Gate. + + The Quantum Fourier Transform (QFT) on :math:`n` qubits is the operation + + .. math:: + + |j\rangle \mapsto \frac{1}{2^{n/2}} \sum_{k=0}^{2^n - 1} e^{2\pi ijk / 2^n} |k\rangle + + """ + + def __init__( + self, + num_qubits: int, + ): + """ + Args: + num_qubits: The number of qubits on which the QFT acts. + """ + super().__init__(name="qft", num_qubits=num_qubits, params=[]) + + def __array__(self, dtype=complex): + """Return a numpy array for the QFTGate.""" + n = self.num_qubits + nums = np.arange(2**n) + outer = np.outer(nums, nums) + return np.exp(2j * np.pi * outer * (0.5**n), dtype=dtype) * (0.5 ** (n / 2)) + + def _define(self): + """Provide a specific decomposition of the QFTGate into a quantum circuit.""" + from qiskit.synthesis.qft import synth_qft_full + + self.definition = synth_qft_full(num_qubits=self.num_qubits) diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 0e2045d5be5d..142639a4e164 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -397,6 +397,8 @@ def _read_instruction( "DiagonalGate", }: gate = gate_class(params) + elif gate_name == "QFTGate": + gate = gate_class(len(qargs), *params) else: if gate_name == "Barrier": params = [len(qargs)] diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index b46c8eac5459..cfe5f0b304cb 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -91,6 +91,7 @@ ====================== .. autofunction:: synth_qft_line +.. autofunction:: synth_qft_full Unitary Synthesis ================= @@ -162,7 +163,7 @@ synth_circuit_from_stabilizers, ) from .discrete_basis import SolovayKitaevDecomposition, generate_basic_approximations -from .qft import synth_qft_line +from .qft import synth_qft_line, synth_qft_full from .unitary.qsd import qs_decomposition from .unitary import aqc from .one_qubit import OneQubitEulerDecomposer diff --git a/qiskit/synthesis/qft/__init__.py b/qiskit/synthesis/qft/__init__.py index 99bd2f7da9b2..8140bf4a74e2 100644 --- a/qiskit/synthesis/qft/__init__.py +++ b/qiskit/synthesis/qft/__init__.py @@ -13,3 +13,4 @@ """Module containing stabilizer QFT circuit synthesis.""" from .qft_decompose_lnn import synth_qft_line +from .qft_decompose_full import synth_qft_full diff --git a/qiskit/synthesis/qft/qft_decompose_full.py b/qiskit/synthesis/qft/qft_decompose_full.py new file mode 100644 index 000000000000..9038ea6589c3 --- /dev/null +++ b/qiskit/synthesis/qft/qft_decompose_full.py @@ -0,0 +1,79 @@ +# 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. +""" +Circuit synthesis for a QFT circuit. +""" + +from __future__ import annotations +import numpy as np +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def synth_qft_full( + num_qubits: int, + do_swaps: bool = True, + approximation_degree: int = 0, + insert_barriers: bool = False, + inverse: bool = False, + name: str | None = None, +) -> QuantumCircuit: + """Construct a circuit for the Quantum Fourier Transform using all-to-all connectivity. + + .. note:: + + With the default value of ``do_swaps = True``, this synthesis algorithm creates a + circuit that faithfully implements the QFT operation. This circuit contains a sequence + of swap gates at the end, corresponding to reversing the order of its output qubits. + In some applications this reversal permutation can be avoided. Setting ``do_swaps = False`` + creates a circuit without this reversal permutation, at the expense that this circuit + implements the "QFT-with-reversal" instead of QFT. Alternatively, the + :class:`~.ElidePermutations` transpiler pass is able to remove these swap gates. + + Args: + num_qubits: The number of qubits on which the Quantum Fourier Transform acts. + do_swaps: Whether to synthesize the "QFT" or the "QFT-with-reversal" operation. + approximation_degree: The degree of approximation (0 for no approximation). + It is possible to implement the QFT approximately by ignoring + controlled-phase rotations with the angle beneath a threshold. This is discussed + in more detail in https://arxiv.org/abs/quant-ph/9601018 or + https://arxiv.org/abs/quant-ph/0403071. + insert_barriers: If ``True``, barriers are inserted for improved visualization. + inverse: If ``True``, the inverse Quantum Fourier Transform is constructed. + name: The name of the circuit. + + Returns: + A circuit implementing the QFT operation. + + """ + + circuit = QuantumCircuit(num_qubits, name=name) + + for j in reversed(range(num_qubits)): + circuit.h(j) + num_entanglements = max(0, j - max(0, approximation_degree - (num_qubits - j - 1))) + for k in reversed(range(j - num_entanglements, j)): + # Use negative exponents so that the angle safely underflows to zero, rather than + # using a temporary variable that overflows to infinity in the worst case. + lam = np.pi * (2.0 ** (k - j)) + circuit.cp(lam, j, k) + + if insert_barriers: + circuit.barrier() + + if do_swaps: + for i in range(num_qubits // 2): + circuit.swap(i, num_qubits - i - 1) + + if inverse: + circuit = circuit.inverse() + + return circuit diff --git a/qiskit/synthesis/qft/qft_decompose_lnn.py b/qiskit/synthesis/qft/qft_decompose_lnn.py index a54be481f51b..f1a0876a0c3f 100644 --- a/qiskit/synthesis/qft/qft_decompose_lnn.py +++ b/qiskit/synthesis/qft/qft_decompose_lnn.py @@ -21,21 +21,29 @@ def synth_qft_line( num_qubits: int, do_swaps: bool = True, approximation_degree: int = 0 ) -> QuantumCircuit: - """Synthesis of a QFT circuit for a linear nearest neighbor connectivity. - Based on Fig 2.b in Fowler et al. [1]. + """Construct a circuit for the Quantum Fourier Transform using linear + neighbor connectivity. - Note that this method *reverts* the order of qubits in the circuit, - compared to the original :class:`.QFT` code. - Hence, the default value of the ``do_swaps`` parameter is ``True`` - since it produces a circuit with fewer CX gates. + The construction is based on Fig 2.b in Fowler et al. [1]. + + .. note:: + + With the default value of ``do_swaps = True``, this synthesis algorithm creates a + circuit that faithfully implements the QFT operation. When ``do_swaps = False``, + this synthesis algorithm creates a circuit that corresponds to "QFT-with-reversal": + applying the QFT and reversing the order of its output qubits. Args: - num_qubits: The number of qubits on which the QFT acts. + num_qubits: The number of qubits on which the Quantum Fourier Transform acts. approximation_degree: The degree of approximation (0 for no approximation). - do_swaps: Whether to include the final swaps in the QFT. + It is possible to implement the QFT approximately by ignoring + controlled-phase rotations with the angle beneath a threshold. This is discussed + in more detail in https://arxiv.org/abs/quant-ph/9601018 or + https://arxiv.org/abs/quant-ph/0403071. + do_swaps: Whether to synthesize the "QFT" or the "QFT-with-reversal" operation. Returns: - A circuit implementation of the QFT circuit. + A circuit implementing the QFT operation. References: 1. A. G. Fowler, S. J. Devitt, and L. C. L. Hollenberg, diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index bbc986621050..f1de2b8f2136 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -131,6 +131,32 @@ ACGSynthesisPermutation KMSSynthesisPermutation TokenSwapperSynthesisPermutation + + +QFT Synthesis +''''''''''''' + +.. list-table:: Plugins for :class:`.QFTGate` (key = ``"qft"``) + :header-rows: 1 + + * - Plugin name + - Plugin class + - Targeted connectivity + * - ``"full"`` + - :class:`~.QFTSynthesisFull` + - all-to-all + * - ``"line"`` + - :class:`~.QFTSynthesisLine` + - linear + * - ``"default"`` + - :class:`~.QFTSynthesisFull` + - all-to-all + +.. autosummary:: + :toctree: ../stubs/ + + QFTSynthesisFull + QFTSynthesisLine """ from typing import Optional, Union, List, Tuple, Callable @@ -157,6 +183,7 @@ ControlModifier, PowerModifier, ) +from qiskit.circuit.library import QFTGate from qiskit.synthesis.clifford import ( synth_clifford_full, synth_clifford_layers, @@ -176,6 +203,10 @@ synth_permutation_acg, synth_permutation_depth_lnn_kms, ) +from qiskit.synthesis.qft import ( + synth_qft_full, + synth_qft_line, +) from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin @@ -887,6 +918,107 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** return decomposition +class QFTSynthesisFull(HighLevelSynthesisPlugin): + """Synthesis plugin for QFT gates using all-to-all connectivity. + + This plugin name is :``qft.full`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + The plugin supports the following additional options: + + * reverse_qubits (bool): Whether to synthesize the "QFT" operation (if ``False``, + which is the default) or the "QFT-with-reversal" operation (if ``True``). + Some implementation of the ``QFTGate`` include a layer of swap gates at the end + of the synthesized circuit, which can in principle be dropped if the ``QFTGate`` + itself is the last gate in the circuit. + * approximation_degree (int): The degree of approximation (0 for no approximation). + It is possible to implement the QFT approximately by ignoring + controlled-phase rotations with the angle beneath a threshold. This is discussed + in more detail in [1] or [2]. + * insert_barriers (bool): If True, barriers are inserted as visualization improvement. + * inverse (bool): If True, the inverse Fourier transform is constructed. + * name (str): The name of the circuit. + + References: + 1. Adriano Barenco, Artur Ekert, Kalle-Antti Suominen, and Päivi Törmä, + *Approximate Quantum Fourier Transform and Decoherence*, + Physical Review A (1996). + `arXiv:quant-ph/9601018 [quant-ph] `_ + 2. Donny Cheung, + *Improved Bounds for the Approximate QFT* (2004), + `arXiv:quant-ph/0403071 [quant-ph] `_ + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + """Run synthesis for the given QFTGate.""" + if not isinstance(high_level_object, QFTGate): + raise TranspilerError( + "The synthesis plugin 'qft.full` only applies to objects of type QFTGate." + ) + + reverse_qubits = options.get("reverse_qubits", False) + approximation_degree = options.get("approximation_degree", 0) + insert_barriers = options.get("insert_barriers", False) + inverse = options.get("inverse", False) + name = options.get("name", None) + + decomposition = synth_qft_full( + num_qubits=high_level_object.num_qubits, + do_swaps=not reverse_qubits, + approximation_degree=approximation_degree, + insert_barriers=insert_barriers, + inverse=inverse, + name=name, + ) + return decomposition + + +class QFTSynthesisLine(HighLevelSynthesisPlugin): + """Synthesis plugin for QFT gates using linear connectivity. + + This plugin name is :``qft.line`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + The plugin supports the following additional options: + + * reverse_qubits (bool): Whether to synthesize the "QFT" operation (if ``False``, + which is the default) or the "QFT-with-reversal" operation (if ``True``). + Some implementation of the ``QFTGate`` include a layer of swap gates at the end + of the synthesized circuit, which can in principle be dropped if the ``QFTGate`` + itself is the last gate in the circuit. + * approximation_degree (int): the degree of approximation (0 for no approximation). + It is possible to implement the QFT approximately by ignoring + controlled-phase rotations with the angle beneath a threshold. This is discussed + in more detail in [1] or [2]. + + References: + 1. Adriano Barenco, Artur Ekert, Kalle-Antti Suominen, and Päivi Törmä, + *Approximate Quantum Fourier Transform and Decoherence*, + Physical Review A (1996). + `arXiv:quant-ph/9601018 [quant-ph] `_ + 2. Donny Cheung, + *Improved Bounds for the Approximate QFT* (2004), + `arXiv:quant-ph/0403071 [quant-ph] `_ + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + """Run synthesis for the given QFTGate.""" + if not isinstance(high_level_object, QFTGate): + raise TranspilerError( + "The synthesis plugin 'qft.line` only applies to objects of type QFTGate." + ) + + reverse_qubits = options.get("reverse_qubits", False) + approximation_degree = options.get("approximation_degree", 0) + + decomposition = synth_qft_line( + num_qubits=high_level_object.num_qubits, + do_swaps=not reverse_qubits, + approximation_degree=approximation_degree, + ) + return decomposition + + class TokenSwapperSynthesisPermutation(HighLevelSynthesisPlugin): """The permutation synthesis plugin based on the token swapper algorithm. @@ -917,7 +1049,6 @@ class TokenSwapperSynthesisPermutation(HighLevelSynthesisPlugin): For more details on the token swapper algorithm, see to the paper: `arXiv:1902.09102 `__. - """ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): diff --git a/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml b/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml new file mode 100644 index 000000000000..46b60c8b7025 --- /dev/null +++ b/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml @@ -0,0 +1,22 @@ +--- +features: + - | + Added a new class :class:`~qiskit.circuit.library.QFTGate` for + natively representing Quantum Fourier Transforms (QFTs). The older way + of representing QFTs via quantum circuits, see + :class:`~qiskit.circuit.library.QFT`, remains for backward compatibility. + The new way of representing a QFT via a gate avoids synthesizing its + definition circuit when the gate is declared, delaying the actual synthesis to + the transpiler. It also allows to easily choose between several different + algorithms for synthesizing QFTs, which are available as high-level-synthesis + plugins. + - | + Added a synthesis method :func:`.synth_qft_full` for constructing a QFT circuit + assuming a fully-connected architecture. + - | + Added two high-level-synthesis plugins for synthesizing a + :class:`~qiskit.circuit.library.QFTGate`. + The class :class:`.QFTSynthesisFull` is based on :func:`.synth_qft_full` and synthesizes + a QFT gate assuming all-to-all connectivity. + The class :class:`.QFTSynthesisLine` is based on :func:`.synth_qft_line` and synthesizes + a QFT gate assuming linear nearest neighbor connectivity. diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index 078b5af04ea9..1f5c9715dd7a 100644 --- a/test/python/circuit/library/test_qft.py +++ b/test/python/circuit/library/test_qft.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2020. +# (C) Copyright IBM 2017, 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 @@ -12,15 +12,19 @@ """Test library of QFT circuits.""" +import io + import unittest import warnings import numpy as np from ddt import ddt, data, unpack from qiskit import transpile -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library import QFT +from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.circuit.library import QFT, QFTGate from qiskit.quantum_info import Operator +from qiskit.qpy import dump, load +from qiskit.qasm2 import dumps from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -206,5 +210,114 @@ def __init__(self, *_args, **_kwargs): qft._build() +@ddt +class TestQFTGate(QiskitTestCase): + """Test the QFT Gate.""" + + @data(2, 3, 4, 5, 6) + def test_array_equivalent_to_decomposition(self, num_qubits): + """Test that the provided __array__ method and that the provided basic + definition are equivalent. + """ + qft_gate = QFTGate(num_qubits=num_qubits) + qft_gate_decomposition = qft_gate.definition + self.assertEqual(Operator(qft_gate), Operator(qft_gate_decomposition)) + + @data(2, 3, 4, 5, 6) + def test_gate_equivalent_to_original(self, num_qubits): + """Test that the Operator can be constructed out of a QFT gate, and is + equivalent to the Operator constructed out of a QFT circuit. + """ + qft_gate = QFTGate(num_qubits=num_qubits) + qft_circuit = QFT(num_qubits=num_qubits) + self.assertEqual(Operator(qft_gate), Operator(qft_circuit)) + + def test_append_to_circuit(self): + """Test adding a QFTGate to a quantum circuit.""" + qc = QuantumCircuit(5) + qc.append(QFTGate(4), [1, 2, 0, 4]) + self.assertIsInstance(qc.data[0].operation, QFTGate) + + @data(2, 3, 4, 5, 6) + def test_circuit_with_gate_equivalent_to_original(self, num_qubits): + """Test that the Operator can be constructed out of a circuit containing a QFT gate, and is + equivalent to the Operator constructed out of a QFT circuit. + """ + qft_gate = QFTGate(num_qubits=num_qubits) + circuit_with_qft_gate = QuantumCircuit(num_qubits) + circuit_with_qft_gate.append(qft_gate, range(num_qubits)) + qft_circuit = QFT(num_qubits=num_qubits) + self.assertEqual(Operator(circuit_with_qft_gate), Operator(qft_circuit)) + + def test_inverse(self): + """Test that inverse can be constructed for a circuit with a QFTGate.""" + qc = QuantumCircuit(5) + qc.append(QFTGate(4), [1, 2, 0, 4]) + qci = qc.inverse() + self.assertEqual(Operator(qci), Operator(qc).adjoint()) + + def test_reverse_ops(self): + """Test reverse_ops works for a circuit with a QFTGate.""" + qc = QuantumCircuit(5) + qc.cx(1, 3) + qc.append(QFTGate(4), [1, 2, 0, 4]) + qc.h(0) + qcr = qc.reverse_ops() + expected = QuantumCircuit(5) + expected.h(0) + expected.append(QFTGate(4), [1, 2, 0, 4]) + expected.cx(1, 3) + self.assertEqual(qcr, expected) + + def test_conditional(self): + """Test adding conditional to a QFTGate.""" + qc = QuantumCircuit(5, 1) + qc.append(QFTGate(4), [1, 2, 0, 4]).c_if(0, 1) + self.assertIsNotNone(qc.data[0].operation.condition) + + def test_qasm(self): + """Test qasm for circuits with QFTGates.""" + qr = QuantumRegister(5, "q0") + qc = QuantumCircuit(qr) + qc.append(QFTGate(num_qubits=4), [1, 2, 0, 4]) + qc.append(QFTGate(num_qubits=3), [0, 1, 2]) + qc.h(qr[0]) + qc_qasm = dumps(qc) + reconstructed = QuantumCircuit.from_qasm_str(qc_qasm) + self.assertEqual(Operator(qc), Operator(reconstructed)) + + def test_qpy(self): + """Test qpy for circuits with QFTGates.""" + qc = QuantumCircuit(6, 1) + qc.append(QFTGate(num_qubits=4), [1, 2, 0, 4]) + qc.append(QFTGate(num_qubits=3), [0, 1, 2]) + qc.h(0) + + qpy_file = io.BytesIO() + dump(qc, qpy_file) + qpy_file.seek(0) + new_circuit = load(qpy_file)[0] + self.assertEqual(qc, new_circuit) + + def test_gate_equality(self): + """Test checking equality of QFTGates.""" + self.assertEqual(QFTGate(num_qubits=3), QFTGate(num_qubits=3)) + self.assertNotEqual(QFTGate(num_qubits=3), QFTGate(num_qubits=4)) + + def test_circuit_with_gate_equality(self): + """Test checking equality of circuits with QFTGates.""" + qc1 = QuantumCircuit(5) + qc1.append(QFTGate(num_qubits=3), [1, 2, 0]) + + qc2 = QuantumCircuit(5) + qc2.append(QFTGate(num_qubits=3), [1, 2, 0]) + + qc3 = QuantumCircuit(5) + qc3.append(QFTGate(num_qubits=4), [1, 2, 0, 4]) + + self.assertEqual(qc1, qc2) + self.assertNotEqual(qc1, qc3) + + if __name__ == "__main__": unittest.main() diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index c5df22a0e8a1..950b0c478ed8 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -294,6 +294,7 @@ class TestGateEquivalenceEqual(QiskitTestCase): "_DefinedGate", "_SingletonGateOverrides", "_SingletonControlledGateOverrides", + "QFTGate", } # Amazingly, Python's scoping rules for class bodies means that this is the closest we can get diff --git a/test/python/synthesis/test_qft_synthesis.py b/test/python/synthesis/test_qft_synthesis.py index d208d8c9fcd2..6cfe111ae07f 100644 --- a/test/python/synthesis/test_qft_synthesis.py +++ b/test/python/synthesis/test_qft_synthesis.py @@ -15,10 +15,10 @@ import unittest from test import combine -from ddt import ddt +from ddt import ddt, data from qiskit.circuit.library import QFT -from qiskit.synthesis.qft import synth_qft_line +from qiskit.synthesis.qft import synth_qft_line, synth_qft_full from qiskit.quantum_info import Operator from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -57,5 +57,52 @@ def test_qft_lnn_approximated(self, num_qubits, do_swaps, approximation_degree): self.assertTrue(check_lnn_connectivity(qft_lnn)) +@ddt +class TestQFTFull(QiskitTestCase): + """Tests for QFT synthesis using all-to-all connectivity.""" + + @data(2, 3, 4, 5, 6) + def test_synthesis_default(self, num_qubits): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits) + synthesized = synth_qft_full(num_qubits) + self.assertEqual(Operator(original), Operator(synthesized)) + + @combine(num_qubits=[5, 6, 7, 8], do_swaps=[False, True]) + def test_synthesis_do_swaps(self, num_qubits, do_swaps): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, do_swaps=do_swaps) + synthesized = synth_qft_full(num_qubits, do_swaps=do_swaps) + self.assertEqual(Operator(original), Operator(synthesized)) + + @combine(num_qubits=[5, 6, 7, 8], approximation_degree=[0, 1, 2, 3]) + def test_synthesis_arpproximate(self, num_qubits, approximation_degree): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, approximation_degree=approximation_degree) + synthesized = synth_qft_full(num_qubits, approximation_degree=approximation_degree) + self.assertEqual(Operator(original), Operator(synthesized)) + + @combine(num_qubits=[5, 6, 7, 8], inverse=[False, True]) + def test_synthesis_inverse(self, num_qubits, inverse): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, inverse=inverse) + synthesized = synth_qft_full(num_qubits, inverse=inverse) + self.assertEqual(Operator(original), Operator(synthesized)) + + @combine(num_qubits=[5, 6, 7, 8], insert_barriers=[False, True]) + def test_synthesis_insert_barriers(self, num_qubits, insert_barriers): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, insert_barriers=insert_barriers) + synthesized = synth_qft_full(num_qubits, insert_barriers=insert_barriers) + self.assertEqual(Operator(original), Operator(synthesized)) + + @data(5, 6, 7, 8) + def test_synthesis_name(self, num_qubits): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, name="SomeRandomName") + synthesized = synth_qft_full(num_qubits, name="SomeRandomName") + self.assertEqual(original.name, synthesized.name) + + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index a76ab08d90e1..fd6ae6a01cda 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -16,6 +16,7 @@ import itertools import unittest.mock import numpy as np +from ddt import ddt, data from qiskit.circuit import ( QuantumCircuit, @@ -40,19 +41,21 @@ UGate, CU3Gate, CU1Gate, + QFTGate, ) from qiskit.circuit.library.generalized_gates import LinearFunction from qiskit.quantum_info import Clifford from qiskit.synthesis.linear import random_invertible_binary_matrix -from qiskit.transpiler.passes.synthesis.plugin import ( - HighLevelSynthesisPlugin, - HighLevelSynthesisPluginManager, -) from qiskit.compiler import transpile from qiskit.exceptions import QiskitError from qiskit.converters import dag_to_circuit, circuit_to_dag, circuit_to_instruction from qiskit.transpiler import PassManager, TranspilerError, CouplingMap, Target from qiskit.transpiler.passes.basis import BasisTranslator +from qiskit.transpiler.passes.synthesis.plugin import ( + HighLevelSynthesisPlugin, + HighLevelSynthesisPluginManager, + high_level_synthesis_plugin_names, +) from qiskit.transpiler.passes.synthesis.high_level_synthesis import HighLevelSynthesis, HLSConfig from qiskit.circuit.annotated_operation import ( AnnotatedOperation, @@ -2098,5 +2101,41 @@ def test_leave_store_alone_with_target(self): self.assertEqual(pass_(qc), expected) +@ddt +class TestQFTSynthesisPlugins(QiskitTestCase): + """Tests related to plugins for QFTGate.""" + + def test_supported_names(self): + """Test that there is a default synthesis plugin for QFTGates.""" + supported_plugin_names = high_level_synthesis_plugin_names("qft") + self.assertIn("default", supported_plugin_names) + + @data("line", "full") + def test_qft_plugins_qft(self, qft_plugin_name): + """Test QFTSynthesisLine plugin for circuits with QFTGates.""" + qc = QuantumCircuit(4) + qc.append(QFTGate(3), [0, 1, 2]) + qc.cx(1, 3) + qc.append(QFTGate(3).inverse(), [0, 1, 2]) + hls_config = HLSConfig(qft=[qft_plugin_name]) + basis_gates = ["cx", "u"] + qct = transpile(qc, hls_config=hls_config, basis_gates=basis_gates) + self.assertEqual(Operator(qc), Operator(qct)) + ops = set(qct.count_ops().keys()) + self.assertEqual(ops, {"u", "cx"}) + + @data("line", "full") + def test_qft_line_plugin_annotated_qft(self, qft_plugin_name): + """Test QFTSynthesisLine plugin for circuits with annotated QFTGates.""" + qc = QuantumCircuit(4) + qc.append(QFTGate(3).inverse(annotated=True).control(annotated=True), [0, 1, 2, 3]) + hls_config = HLSConfig(qft=[qft_plugin_name]) + basis_gates = ["cx", "u"] + qct = transpile(qc, hls_config=hls_config, basis_gates=basis_gates) + self.assertEqual(Operator(qc), Operator(qct)) + ops = set(qct.count_ops().keys()) + self.assertEqual(ops, {"u", "cx"}) + + if __name__ == "__main__": unittest.main() From 1e8205e43d72cf2a186a8013072aa6b2c5cd1196 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 10 Jul 2024 09:48:08 -0400 Subject: [PATCH 72/89] Avoid Python op creation in BasisTranslator (#12705) This commit updates the BasisTranslator transpiler pass. It builds off of #12692 and #12701 to adjust access patterns in the python transpiler path to avoid eagerly creating a Python space operation object. The goal of this PR is to mitigate the performance regression introduced by the extra conversion cost of #12459 on the BasisTranslator. --- crates/circuit/src/dag_node.rs | 5 ++ .../passes/basis/basis_translator.py | 61 +++++++++++-------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index 55a40c83dc39..f347ec72c811 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -270,6 +270,11 @@ impl DAGOpNode { self.instruction.params.to_object(py) } + #[setter] + fn set_params(&mut self, val: smallvec::SmallVec<[crate::operations::Param; 3]>) { + self.instruction.params = val; + } + pub fn is_parameterized(&self) -> bool { self.instruction.is_parameterized() } diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index f2e752dd94f5..e69887a3b940 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -30,11 +30,13 @@ QuantumCircuit, ParameterExpression, ) -from qiskit.dagcircuit import DAGCircuit +from qiskit.dagcircuit import DAGCircuit, DAGOpNode from qiskit.converters import circuit_to_dag, dag_to_circuit from qiskit.circuit.equivalence import Key, NodeData from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError +from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES +from qiskit._accelerate.circuit import StandardGate logger = logging.getLogger(__name__) @@ -253,7 +255,7 @@ def apply_translation(dag, wire_map): node_qargs = tuple(wire_map[bit] for bit in node.qargs) qubit_set = frozenset(node_qargs) if node.name in target_basis or len(node.qargs) < self._min_qubits: - if isinstance(node.op, ControlFlowOp): + if node.name in CONTROL_FLOW_OP_NAMES: flow_blocks = [] for block in node.op.blocks: dag_block = circuit_to_dag(block) @@ -281,7 +283,7 @@ def apply_translation(dag, wire_map): continue if qubit_set in extra_instr_map: self._replace_node(dag, node, extra_instr_map[qubit_set]) - elif (node.op.name, node.op.num_qubits) in instr_map: + elif (node.name, node.num_qubits) in instr_map: self._replace_node(dag, node, instr_map) else: raise TranspilerError(f"BasisTranslator did not map {node.name}.") @@ -298,20 +300,29 @@ def apply_translation(dag, wire_map): return dag def _replace_node(self, dag, node, instr_map): - target_params, target_dag = instr_map[node.op.name, node.op.num_qubits] - if len(node.op.params) != len(target_params): + target_params, target_dag = instr_map[node.name, node.num_qubits] + if len(node.params) != len(target_params): raise TranspilerError( "Translation num_params not equal to op num_params." - f"Op: {node.op.params} {node.op.name} Translation: {target_params}\n{target_dag}" + f"Op: {node.params} {node.name} Translation: {target_params}\n{target_dag}" ) - if node.op.params: - parameter_map = dict(zip(target_params, node.op.params)) + if node.params: + parameter_map = dict(zip(target_params, node.params)) bound_target_dag = target_dag.copy_empty_like() for inner_node in target_dag.topological_op_nodes(): - if any(isinstance(x, ParameterExpression) for x in inner_node.op.params): + new_op = inner_node._raw_op + if not isinstance(inner_node._raw_op, StandardGate): new_op = inner_node.op.copy() + new_node = DAGOpNode( + new_op, + qargs=inner_node.qargs, + cargs=inner_node.cargs, + params=inner_node.params, + dag=bound_target_dag, + ) + if any(isinstance(x, ParameterExpression) for x in inner_node.params): new_params = [] - for param in new_op.params: + for param in new_node.params: if not isinstance(param, ParameterExpression): new_params.append(param) else: @@ -325,10 +336,10 @@ def _replace_node(self, dag, node, instr_map): if not new_value.parameters: new_value = new_value.numeric() new_params.append(new_value) - new_op.params = new_params - else: - new_op = inner_node.op - bound_target_dag.apply_operation_back(new_op, inner_node.qargs, inner_node.cargs) + new_node.params = new_params + if not isinstance(new_op, StandardGate): + new_op.params = new_params + bound_target_dag._apply_op_node_back(new_node) if isinstance(target_dag.global_phase, ParameterExpression): old_phase = target_dag.global_phase bind_dict = {x: parameter_map[x] for x in old_phase.parameters} @@ -353,7 +364,7 @@ def _replace_node(self, dag, node, instr_map): dag_op = bound_target_dag.op_nodes()[0].op # dag_op may be the same instance as other ops in the dag, # so if there is a condition, need to copy - if getattr(node.op, "condition", None): + if getattr(node, "condition", None): dag_op = dag_op.copy() dag.substitute_node(node, dag_op, inplace=True) @@ -370,8 +381,8 @@ def _extract_basis(self, circuit): def _(self, dag: DAGCircuit): for node in dag.op_nodes(): if not dag.has_calibration_for(node) and len(node.qargs) >= self._min_qubits: - yield (node.name, node.op.num_qubits) - if isinstance(node.op, ControlFlowOp): + yield (node.name, node.num_qubits) + if node.name in CONTROL_FLOW_OP_NAMES: for block in node.op.blocks: yield from self._extract_basis(block) @@ -412,10 +423,10 @@ def _extract_basis_target( frozenset(qargs).issuperset(incomplete_qargs) for incomplete_qargs in self._qargs_with_non_global_operation ): - qargs_local_source_basis[frozenset(qargs)].add((node.name, node.op.num_qubits)) + qargs_local_source_basis[frozenset(qargs)].add((node.name, node.num_qubits)) else: - source_basis.add((node.name, node.op.num_qubits)) - if isinstance(node.op, ControlFlowOp): + source_basis.add((node.name, node.num_qubits)) + if node.name in CONTROL_FLOW_OP_NAMES: for block in node.op.blocks: block_dag = circuit_to_dag(block) source_basis, qargs_local_source_basis = self._extract_basis_target( @@ -628,7 +639,7 @@ def _compose_transforms(basis_transforms, source_basis, source_dag): doomed_nodes = [ node for node in dag.op_nodes() - if (node.op.name, node.op.num_qubits) == (gate_name, gate_num_qubits) + if (node.name, node.num_qubits) == (gate_name, gate_num_qubits) ] if doomed_nodes and logger.isEnabledFor(logging.DEBUG): @@ -642,9 +653,7 @@ def _compose_transforms(basis_transforms, source_basis, source_dag): for node in doomed_nodes: - replacement = equiv.assign_parameters( - dict(zip_longest(equiv_params, node.op.params)) - ) + replacement = equiv.assign_parameters(dict(zip_longest(equiv_params, node.params))) replacement_dag = circuit_to_dag(replacement) @@ -666,8 +675,8 @@ def _get_example_gates(source_dag): def recurse(dag, example_gates=None): example_gates = example_gates or {} for node in dag.op_nodes(): - example_gates[(node.op.name, node.op.num_qubits)] = node.op - if isinstance(node.op, ControlFlowOp): + example_gates[(node.name, node.num_qubits)] = node + if node.name in CONTROL_FLOW_OP_NAMES: for block in node.op.blocks: example_gates = recurse(circuit_to_dag(block), example_gates) return example_gates From 61805c8e166800687aa64ef3bf8d7da7540f5f2f Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 11 Jul 2024 12:36:14 +0200 Subject: [PATCH 73/89] Some formatting issues in Pauli docs (#12753) * some formatting issues in Pauli docs * Update qiskit/quantum_info/operators/symplectic/pauli.py Co-authored-by: Jake Lishman --------- Co-authored-by: Jake Lishman --- .../operators/symplectic/pauli.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py index 867867eeb98a..44ca095f8ed6 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli.py +++ b/qiskit/quantum_info/operators/symplectic/pauli.py @@ -77,7 +77,7 @@ class Pauli(BasePauli): An :math:`n`-qubit Pauli may be represented by a string consisting of :math:`n` characters from ``['I', 'X', 'Y', 'Z']``, and optionally phase - coefficient in :math:`['', '-i', '-', 'i']`. For example: ``XYZ`` or + coefficient in ``['', '-i', '-', 'i']``. For example: ``'XYZ'`` or ``'-iZIZ'``. In the string representation qubit-0 corresponds to the right-most @@ -160,21 +160,23 @@ class initialization (``Pauli('-iXYZ')``). A ``Pauli`` object can be _CANONICAL_PHASE_LABEL = {"": 0, "-i": 1, "-": 2, "i": 3} def __init__(self, data: str | tuple | Pauli | ScalarOp | QuantumCircuit | None = None): - """Initialize the Pauli. + r"""Initialize the Pauli. When using the symplectic array input data both z and x arguments must be provided, however the first (z) argument can be used alone for string - label, Pauli operator, or ScalarOp input data. + label, Pauli operator, or :class:`.ScalarOp` input data. Args: data (str or tuple or Pauli or ScalarOp): input data for Pauli. If input is - a tuple it must be of the form ``(z, x)`` or (z, x, phase)`` where - ``z`` and ``x`` are boolean Numpy arrays, and phase is an integer from Z_4. + a tuple it must be of the form ``(z, x)`` or ``(z, x, phase)`` where + ``z`` and ``x`` are boolean Numpy arrays, and phase is an integer from + :math:`\mathbb{Z}_4`. If input is a string, it must be a concatenation of a phase and a Pauli string - (e.g. 'XYZ', '-iZIZ') where a phase string is a combination of at most three - characters from ['+', '-', ''], ['1', ''], and ['i', 'j', ''] in this order, - e.g. '', '-1j' while a Pauli string is 1 or more characters of 'I', 'X', 'Y' or 'Z', - e.g. 'Z', 'XIYY'. + (e.g. ``'XYZ', '-iZIZ'``) where a phase string is a combination of at most three + characters from ``['+', '-', '']``, ``['1', '']``, and ``['i', 'j', '']`` in this order, + e.g. ``''``, ``'-1j'`` while a Pauli string is 1 or more + characters of ``'I'``, ``'X'``, ``'Y'``, or ``'Z'``, + e.g. ``'Z'``, ``'XIYY'``. Raises: QiskitError: if input array is invalid shape. From 4c9ca6eddaec717b85a9b49798c2fe3f09a44fbc Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 11 Jul 2024 13:27:56 +0200 Subject: [PATCH 74/89] Randomized errors are failing because Aer uses deprecated functionality (#12722) --- test/randomized/test_transpiler_equivalence.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/randomized/test_transpiler_equivalence.py b/test/randomized/test_transpiler_equivalence.py index 3bd09d89348b..76bbf9ef08e3 100644 --- a/test/randomized/test_transpiler_equivalence.py +++ b/test/randomized/test_transpiler_equivalence.py @@ -48,6 +48,8 @@ """ import os +import warnings + from test.utils.base import dicts_almost_equal from math import pi @@ -292,7 +294,16 @@ def equivalent_transpile(self, kwargs): # Note that there's no transpilation here, which is why the gates are limited to only ones # that Aer supports natively. - aer_counts = self.backend.run(self.qc, shots=shots).result().get_counts() + with warnings.catch_warnings(): + # Safe to remove once https://github.com/Qiskit/qiskit-aer/pull/2179 is in a release version + # of Aer. + warnings.filterwarnings( + "default", + category=DeprecationWarning, + module="qiskit_aer", + message="Treating CircuitInstruction as an iterable", + ) + aer_counts = self.backend.run(self.qc, shots=shots).result().get_counts() try: xpiled_qc = transpile(self.qc, **kwargs) From 99ae318d89337fdc04673b47435680141ecfc06a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 11 Jul 2024 07:58:47 -0400 Subject: [PATCH 75/89] Use rustworkx 0.15.0 features in DAGCircuit.remove_op_node (#12756) This commit updates the minimum rustworkx version to 0.15.0 to pull in the new PyDiGraph.remove_node_retain_edges_by_id() method introduced in that release. This new function is used for the DAGCircuit.remove_op_node() method instead of the PyDiGraph.remove_node_retain_edges() function. This new method has much better scaling characteristics and should improve the performance characteristics of removing very wide operations from a DAGCircuit. Fixes #11677 Part of #12156 --- qiskit/dagcircuit/dagcircuit.py | 4 +--- .../update-rustworkx-min-version-4f07aacfebccae80.yaml | 7 +++++++ requirements.txt | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/update-rustworkx-min-version-4f07aacfebccae80.yaml diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 5d6739d72198..796e0bc2b700 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1986,9 +1986,7 @@ def remove_op_node(self, node): "node type was wrongly provided." ) - self._multi_graph.remove_node_retain_edges( - node._node_id, use_outgoing=False, condition=lambda edge1, edge2: edge1 == edge2 - ) + self._multi_graph.remove_node_retain_edges_by_id(node._node_id) self._decrement_op(node.name) def remove_ancestors_of(self, node): diff --git a/releasenotes/notes/update-rustworkx-min-version-4f07aacfebccae80.yaml b/releasenotes/notes/update-rustworkx-min-version-4f07aacfebccae80.yaml new file mode 100644 index 000000000000..7661f82aee8b --- /dev/null +++ b/releasenotes/notes/update-rustworkx-min-version-4f07aacfebccae80.yaml @@ -0,0 +1,7 @@ +--- +upgrade_misc: + - | + The minimum version of rustworkx required to run this release has been + increased from 0.14.0 to 0.15.0. This is required because Qiskit is now + using new functionality added in the rustworkx 0.15.0 release which + improves performance. diff --git a/requirements.txt b/requirements.txt index 539f9587994d..2dd5e49e2b3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -rustworkx>=0.14.0 +rustworkx>=0.15.0 numpy>=1.17,<3 scipy>=1.5 sympy>=1.3 From a306cb67c8fbaab5e3329fa40613d22980aaea29 Mon Sep 17 00:00:00 2001 From: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:40:01 +0200 Subject: [PATCH 76/89] Barebone generic backend options (#12747) * Update generic_backend_v2.py * reno * Update barebone-backend-option-675c86df4382a443.yaml * ... * suggestions from code review * Update barebone-backend-option-675c86df4382a443.yaml * tests, typo --- .../fake_provider/generic_backend_v2.py | 63 +++++++++++++------ ...ebone-backend-option-675c86df4382a443.yaml | 8 +++ .../fake_provider/test_generic_backend_v2.py | 20 ++++++ 3 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/barebone-backend-option-675c86df4382a443.yaml diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index 214754080e57..0da1df7eab65 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -112,6 +112,8 @@ def __init__( calibrate_instructions: bool | InstructionScheduleMap | None = None, dtm: float | None = None, seed: int | None = None, + pulse_channels: bool = True, + noise_info: bool = True, ): """ Args: @@ -159,6 +161,10 @@ def __init__( None by default. seed: Optional seed for generation of default values. + + pulse_channels: If true, sets default pulse channel information on the backend. + + noise_info: If true, associates gates and qubits with default noise information. """ super().__init__( @@ -175,6 +181,10 @@ def __init__( self._control_flow = control_flow self._calibrate_instructions = calibrate_instructions self._supported_gates = get_standard_gate_name_mapping() + self._noise_info = noise_info + + if calibrate_instructions and not noise_info: + raise QiskitError("Must set parameter noise_info when calibrating instructions.") if coupling_map is None: self._coupling_map = CouplingMap().from_full(num_qubits) @@ -198,7 +208,10 @@ def __init__( self._basis_gates.append(name) self._build_generic_target() - self._build_default_channels() + if pulse_channels: + self._build_default_channels() + else: + self.channels_map = {} @property def target(self): @@ -340,22 +353,31 @@ def _build_generic_target(self): """ # the qubit properties are sampled from default ranges properties = _QUBIT_PROPERTIES - self._target = Target( - description=f"Generic Target with {self._num_qubits} qubits", - num_qubits=self._num_qubits, - dt=properties["dt"], - qubit_properties=[ - QubitProperties( - t1=self._rng.uniform(properties["t1"][0], properties["t1"][1]), - t2=self._rng.uniform(properties["t2"][0], properties["t2"][1]), - frequency=self._rng.uniform( - properties["frequency"][0], properties["frequency"][1] - ), - ) - for _ in range(self._num_qubits) - ], - concurrent_measurements=[list(range(self._num_qubits))], - ) + if not self._noise_info: + self._target = Target( + description=f"Generic Target with {self._num_qubits} qubits", + num_qubits=self._num_qubits, + dt=properties["dt"], + qubit_properties=None, + concurrent_measurements=[list(range(self._num_qubits))], + ) + else: + self._target = Target( + description=f"Generic Target with {self._num_qubits} qubits", + num_qubits=self._num_qubits, + dt=properties["dt"], + qubit_properties=[ + QubitProperties( + t1=self._rng.uniform(properties["t1"][0], properties["t1"][1]), + t2=self._rng.uniform(properties["t2"][0], properties["t2"][1]), + frequency=self._rng.uniform( + properties["frequency"][0], properties["frequency"][1] + ), + ) + for _ in range(self._num_qubits) + ], + concurrent_measurements=[list(range(self._num_qubits))], + ) # Generate instruction schedule map with calibrations to add to target. calibration_inst_map = None @@ -380,8 +402,11 @@ def _build_generic_target(self): f"Provided basis gate {name} needs more qubits than {self.num_qubits}, " f"which is the size of the backend." ) - noise_params = self._get_noise_defaults(name, gate.num_qubits) - self._add_noisy_instruction_to_target(gate, noise_params, calibration_inst_map) + if self._noise_info: + noise_params = self._get_noise_defaults(name, gate.num_qubits) + self._add_noisy_instruction_to_target(gate, noise_params, calibration_inst_map) + else: + self._target.add_instruction(gate, properties=None, name=name) if self._control_flow: self._target.add_instruction(IfElseOp, name="if_else") diff --git a/releasenotes/notes/barebone-backend-option-675c86df4382a443.yaml b/releasenotes/notes/barebone-backend-option-675c86df4382a443.yaml new file mode 100644 index 000000000000..912970845042 --- /dev/null +++ b/releasenotes/notes/barebone-backend-option-675c86df4382a443.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added two parameters to :class:`.GenericBackendV2` to exclude error (`noise_info`) and + pulse channel information (`pulse_channels`) from the construction of the backend. These parameters + are true by default, replicating the initial default behavior of the constructor. A memory-sensitive + user may set these options to `False` to reduce the memory overhead by 40x when transpiling on large- + scale :class:`.GenericBackendV2`. diff --git a/test/python/providers/fake_provider/test_generic_backend_v2.py b/test/python/providers/fake_provider/test_generic_backend_v2.py index cd7c611b2212..33bf57cf3903 100644 --- a/test/python/providers/fake_provider/test_generic_backend_v2.py +++ b/test/python/providers/fake_provider/test_generic_backend_v2.py @@ -45,6 +45,26 @@ def test_ccx_2Q(self): with self.assertRaises(QiskitError): GenericBackendV2(num_qubits=2, basis_gates=["ccx", "id"]) + def test_calibration_no_noise_info(self): + """Test failing with a backend with calibration and no noise info""" + with self.assertRaises(QiskitError): + GenericBackendV2( + num_qubits=2, + basis_gates=["ccx", "id"], + calibrate_instructions=True, + noise_info=False, + ) + + def test_no_noise(self): + """Test no noise info when parameter is false""" + backend = GenericBackendV2(num_qubits=2, noise_info=False) + self.assertEqual(backend.target.qubit_properties, None) + + def test_no_pulse_channels(self): + """Test no/empty pulse channels when parameter is false""" + backend = GenericBackendV2(num_qubits=2, pulse_channels=False) + self.assertTrue(len(backend.channels_map) == 0) + def test_operation_names(self): """Test that target basis gates include "delay", "measure" and "reset" even if not provided by user.""" From 41267ecf5d20bf022a8d4fcb1580a88f56d1843c Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:10:27 +0300 Subject: [PATCH 77/89] Add clifford gates to collect_cliffords (#12750) * add clifford gates to collect_cliffords * replace hard coded clifford names by clifford_circuit names * move import * replace hard coded clifford names in random_clifford_circuit * add release notes * add permutation to collect_clifford gate list --- qiskit/circuit/random/utils.py | 6 ++++-- .../passes/optimization/collect_cliffords.py | 21 ++++++------------- ...fix-collect-clifford-83af26d98b8c69e8.yaml | 6 ++++++ 3 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/fix-collect-clifford-83af26d98b8c69e8.yaml diff --git a/qiskit/circuit/random/utils.py b/qiskit/circuit/random/utils.py index 3bcdbeaef4ac..5caba8ca2ae0 100644 --- a/qiskit/circuit/random/utils.py +++ b/qiskit/circuit/random/utils.py @@ -18,6 +18,7 @@ from qiskit.circuit import Reset from qiskit.circuit.library import standard_gates from qiskit.circuit.exceptions import CircuitError +from qiskit.quantum_info.operators.symplectic.clifford_circuits import _BASIS_1Q, _BASIS_2Q def random_circuit( @@ -312,8 +313,9 @@ def random_clifford_circuit(num_qubits, num_gates, gates="all", seed=None): QuantumCircuit: constructed circuit """ - gates_1q = ["i", "x", "y", "z", "h", "s", "sdg", "sx", "sxdg"] - gates_2q = ["cx", "cz", "cy", "swap", "iswap", "ecr", "dcx"] + gates_1q = list(set(_BASIS_1Q.keys()) - {"v", "w", "id", "iden", "sinv"}) + gates_2q = list(_BASIS_2Q.keys()) + if gates == "all": if num_qubits == 1: gates = gates_1q diff --git a/qiskit/transpiler/passes/optimization/collect_cliffords.py b/qiskit/transpiler/passes/optimization/collect_cliffords.py index c0e9641923cd..40acd21c6855 100644 --- a/qiskit/transpiler/passes/optimization/collect_cliffords.py +++ b/qiskit/transpiler/passes/optimization/collect_cliffords.py @@ -22,6 +22,7 @@ ) from qiskit.quantum_info.operators import Clifford +from qiskit.quantum_info.operators.symplectic.clifford_circuits import _BASIS_1Q, _BASIS_2Q class CollectCliffords(CollectAndCollapse): @@ -69,21 +70,11 @@ def __init__( ) -clifford_gate_names = [ - "x", - "y", - "z", - "h", - "s", - "sdg", - "cx", - "cy", - "cz", - "swap", - "clifford", - "linear_function", - "pauli", -] +clifford_gate_names = ( + list(_BASIS_1Q.keys()) + + list(_BASIS_2Q.keys()) + + ["clifford", "linear_function", "pauli", "permutation"] +) def _is_clifford_gate(node): diff --git a/releasenotes/notes/fix-collect-clifford-83af26d98b8c69e8.yaml b/releasenotes/notes/fix-collect-clifford-83af26d98b8c69e8.yaml new file mode 100644 index 000000000000..48eac19acc9d --- /dev/null +++ b/releasenotes/notes/fix-collect-clifford-83af26d98b8c69e8.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Add more Clifford gates to the :class:`.CollectCliffords()` transpiler pass. + In particular, we have added the gates :class:`ECRGate()`, :class:`DCXGate()`, + :class:`iSWAPGate()`, :class:`SXGate()` and :class:`SXdgGate()` to this transpiler pass. From 99032fc042b5f4f57956f451578c3476a672ddb9 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 11 Jul 2024 17:40:08 +0200 Subject: [PATCH 78/89] Remove some of the entries in allow_DeprecationWarning (#12721) * remove some of the entries * fixing obscure expcetion handleing for comparison * remove all the modules * ignore "Treating CircuitInstruction as an iterable is deprecated" in Aer * remove allow_DeprecationWarning_module and revert ignore/default * revert --- qiskit/circuit/instruction.py | 7 ++++--- test/utils/base.py | 22 ---------------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 1b5fca3f738b..4bebf812e185 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -188,11 +188,12 @@ def __eq__(self, other): return False for self_param, other_param in zip_longest(self.params, other.params): - try: + if isinstance(self_param, numpy.ndarray): + if numpy.array_equal(self_param, other_param): + continue + else: if self_param == other_param: continue - except ValueError: - pass try: self_asarray = numpy.asarray(self_param) diff --git a/test/utils/base.py b/test/utils/base.py index e4dbd7f67072..0b02f3e58488 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -129,30 +129,8 @@ def setUpClass(cls): module=r"qiskit_aer(\.[a-zA-Z0-9_]+)*", ) - allow_DeprecationWarning_modules = [ - "test.python.pulse.test_builder", - "test.python.pulse.test_block", - "test.python.quantum_info.operators.symplectic.test_legacy_pauli", - "qiskit.quantum_info.operators.pauli", - "pybobyqa", - "numba", - "qiskit.utils.measurement_error_mitigation", - "qiskit.circuit.library.standard_gates.x", - "qiskit.pulse.schedule", - "qiskit.pulse.instructions.instruction", - "qiskit.pulse.instructions.play", - "qiskit.pulse.library.parametric_pulses", - "qiskit.quantum_info.operators.symplectic.pauli", - ] - for mod in allow_DeprecationWarning_modules: - warnings.filterwarnings("default", category=DeprecationWarning, module=mod) allow_DeprecationWarning_message = [ - r"elementwise comparison failed.*", - r"The jsonschema validation included in qiskit-terra.*", - r"The DerivativeBase.parameter_expression_grad method.*", r"The property ``qiskit\.circuit\.bit\.Bit\.(register|index)`` is deprecated.*", - # Caused by internal scikit-learn scipy usage - r"The 'sym_pos' keyword is deprecated and should be replaced by using", ] for msg in allow_DeprecationWarning_message: warnings.filterwarnings("default", category=DeprecationWarning, message=msg) From cb653baf14a74ce314d7ea5e93a18d0c10145f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Fri, 12 Jul 2024 11:25:37 +0200 Subject: [PATCH 79/89] Implement removal of ascestors, descendants, non-ancestors and non-descendants. --- crates/circuit/src/dag_circuit.rs | 76 +++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index a9d6637f774e..971ac0126add 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -2919,43 +2919,69 @@ def _format(operand): /// Remove all of the ancestor operation nodes of node. fn remove_ancestors_of(&mut self, node: &DAGNode) -> PyResult<()> { - // anc = rx.ancestors(self._multi_graph, node) - // # TODO: probably better to do all at once using - // # multi_graph.remove_nodes_from; same for related functions ... - // - // for anc_node in anc: - // if isinstance(anc_node, DAGOpNode): - // self.remove_op_node(anc_node) - todo!() + let dag_binding = self.dag.clone(); + for ancestor in core_ancestors(&dag_binding, node.node.unwrap()) + .filter(|next| next != &node.node.unwrap()) + .filter(|next| match dag_binding.node_weight(*next) { + Some(NodeType::Operation(_)) => true, + _ => false, + }) + { + self.dag.remove_node(ancestor); + } + Ok(()) } /// Remove all of the descendant operation nodes of node. fn remove_descendants_of(&mut self, node: &DAGNode) -> PyResult<()> { - // desc = rx.descendants(self._multi_graph, node) - // for desc_node in desc: - // if isinstance(desc_node, DAGOpNode): - // self.remove_op_node(desc_node) - todo!() + let dag_binding = self.dag.clone(); + for descendant in core_descendants(&dag_binding, node.node.unwrap()) + .filter(|next| next != &node.node.unwrap()) + .filter(|next| match dag_binding.node_weight(*next) { + Some(NodeType::Operation(_)) => true, + _ => false, + }) + { + self.dag.remove_node(descendant); + } + Ok(()) } /// Remove all of the non-ancestors operation nodes of node. fn remove_nonancestors_of(&mut self, node: &DAGNode) -> PyResult<()> { - // anc = rx.ancestors(self._multi_graph, node) - // comp = list(set(self._multi_graph.nodes()) - set(anc)) - // for n in comp: - // if isinstance(n, DAGOpNode): - // self.remove_op_node(n) - todo!() + let dag_binding = self.dag.clone(); + let mut ancestors = core_ancestors(&dag_binding, node.node.unwrap()) + .filter(|next| next != &node.node.unwrap()) + .filter(|next| match dag_binding.node_weight(*next) { + Some(NodeType::Operation(_)) => true, + _ => false, + }); + for node_id in dag_binding.node_indices() { + if ancestors.find(|anc| *anc == node_id).is_some() { + continue; + } + self.dag.remove_node(node_id); + } + Ok(()) } /// Remove all of the non-descendants operation nodes of node. fn remove_nondescendants_of(&mut self, node: &DAGNode) -> PyResult<()> { - // dec = rx.descendants(self._multi_graph, node) - // comp = list(set(self._multi_graph.nodes()) - set(dec)) - // for n in comp: - // if isinstance(n, DAGOpNode): - // self.remove_op_node(n) - todo!() + let dag_binding = self.dag.clone(); + let mut descendants = core_descendants(&dag_binding, node.node.unwrap()) + .filter(|next| next != &node.node.unwrap()) + .filter(|next| match dag_binding.node_weight(*next) { + Some(NodeType::Operation(_)) => true, + _ => false, + }); + + for node_id in dag_binding.node_indices() { + if descendants.find(|desc| *desc == node_id).is_some() { + continue; + } + self.dag.remove_node(node_id); + } + Ok(()) } /// Return a list of op nodes in the first layer of this dag. From 59a62d9e2e91fcc3642d392c2e28efceeb642cc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:38:41 +0000 Subject: [PATCH 80/89] Bump thiserror from 1.0.61 to 1.0.62 (#12768) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.61 to 1.0.62. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.61...1.0.62) --- updated-dependencies: - dependency-name: thiserror 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 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 125ce6abe876..cc146f07a058 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1527,18 +1527,18 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" dependencies = [ "proc-macro2", "quote", From 2a2b806c1ba800498f8f9659455325fcef40856c Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:54:23 -0400 Subject: [PATCH 81/89] Make `CircuitData` inner data accessible in Rust (#12766) * Add: `iter()` method to `CircuitData` - Make `PackedInstruction` public. * Docs: Remove "rust-native method" from docstring. --- crates/circuit/src/circuit_data.rs | 5 +++++ crates/circuit/src/circuit_instruction.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 501645415874..0b4d60b6c91a 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -1167,4 +1167,9 @@ impl CircuitData { py_op: RefCell::new(inst.py_op.clone()), }) } + + /// Returns an iterator over all the instructions present in the circuit. + pub fn iter(&self) -> impl Iterator { + self.data.iter() + } } diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 501105f9f17e..ecb7a1623a20 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -44,7 +44,7 @@ pub struct ExtraInstructionAttributes { /// Private type used to store instructions with interned arg lists. #[derive(Clone, Debug)] -pub(crate) struct PackedInstruction { +pub struct PackedInstruction { /// The Python-side operation instance. pub op: OperationType, /// The index under which the interner has stored `qubits`. From 3e2a6e8dcfdc894f40bf8a4bd90fd2cc0af8febb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:07:41 +0200 Subject: [PATCH 82/89] Finalize Rust representation for standard gates (#12709) * Add missing gate definitions * Reorder gates, following number of qubits and a sort of alphabetical order. Make definitions and matrices consistent with new gate order. Remove C4XGate (second mcx) from list. --- crates/circuit/src/gate_matrix.rs | 175 +- crates/circuit/src/imports.rs | 176 +- crates/circuit/src/operations.rs | 1501 +++++++++--------- test/python/circuit/test_rust_equivalence.py | 7 - 4 files changed, 935 insertions(+), 924 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 6f527af2e30f..a38783ef7e7f 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -46,28 +46,21 @@ macro_rules! make_n_controlled_gate { }}; } +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 X_GATE: GateArray1Q = [[C_ZERO, C_ONE], [C_ONE, C_ZERO]]; pub static Z_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_M_ONE]]; pub static Y_GATE: GateArray1Q = [[C_ZERO, M_IM], [IM, C_ZERO]]; -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 S_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, IM]]; pub static SDG_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, M_IM]]; -pub static T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, 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 SX_GATE: GateArray1Q = [ [c64(0.5, 0.5), c64(0.5, -0.5)], [c64(0.5, -0.5), c64(0.5, 0.5)], @@ -78,27 +71,27 @@ pub static SXDG_GATE: GateArray1Q = [ [c64(0.5, 0.5), c64(0.5, -0.5)], ]; -pub static CX_GATE: GateArray2Q = make_n_controlled_gate!(X_GATE, 1); - -pub static CZ_GATE: GateArray2Q = make_n_controlled_gate!(Z_GATE, 1); - -pub static CY_GATE: GateArray2Q = make_n_controlled_gate!(Y_GATE, 1); - -pub static CCX_GATE: GateArray3Q = make_n_controlled_gate!(X_GATE, 2); - -pub static CCZ_GATE: GateArray3Q = make_n_controlled_gate!(Z_GATE, 2); - -pub static C3X_GATE: GateArray4Q = make_n_controlled_gate!(X_GATE, 3); +pub static T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)]]; -pub static C3SX_GATE: GateArray4Q = make_n_controlled_gate!(SX_GATE, 3); +pub static TDG_GATE: GateArray1Q = [ + [C_ONE, C_ZERO], + [C_ZERO, c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], +]; pub static CH_GATE: GateArray2Q = make_n_controlled_gate!(H_GATE, 1); -pub static CS_GATE: GateArray2Q = make_n_controlled_gate!(S_GATE, 1); +pub static CX_GATE: GateArray2Q = make_n_controlled_gate!(X_GATE, 1); -pub static CSDG_GATE: GateArray2Q = make_n_controlled_gate!(SDG_GATE, 1); +pub static CY_GATE: GateArray2Q = make_n_controlled_gate!(Y_GATE, 1); -pub static CSX_GATE: GateArray2Q = make_n_controlled_gate!(SX_GATE, 1); +pub static CZ_GATE: GateArray2Q = make_n_controlled_gate!(Z_GATE, 1); + +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], +]; pub static ECR_GATE: GateArray2Q = [ [ @@ -133,6 +126,7 @@ pub static SWAP_GATE: GateArray2Q = [ [C_ZERO, C_ONE, C_ZERO, C_ZERO], [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; + pub static ISWAP_GATE: GateArray2Q = [ [C_ONE, C_ZERO, C_ZERO, C_ZERO], [C_ZERO, C_ZERO, IM, C_ZERO], @@ -140,6 +134,16 @@ pub static ISWAP_GATE: GateArray2Q = [ [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; +pub static CS_GATE: GateArray2Q = make_n_controlled_gate!(S_GATE, 1); + +pub static CSDG_GATE: GateArray2Q = make_n_controlled_gate!(SDG_GATE, 1); + +pub static CSX_GATE: GateArray2Q = make_n_controlled_gate!(SX_GATE, 1); + +pub static CCX_GATE: GateArray3Q = make_n_controlled_gate!(X_GATE, 2); + +pub static CCZ_GATE: GateArray3Q = make_n_controlled_gate!(Z_GATE, 2); + pub static CSWAP_GATE: GateArray3Q = [ [ C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, @@ -167,13 +171,6 @@ pub static CSWAP_GATE: GateArray3Q = [ ], ]; -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], -]; - pub static RCCX_GATE: GateArray3Q = [ [ C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, @@ -197,6 +194,10 @@ pub static RCCX_GATE: GateArray3Q = [ [C_ZERO, C_ZERO, C_ZERO, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO], ]; +pub static C3X_GATE: GateArray4Q = make_n_controlled_gate!(X_GATE, 3); + +pub static C3SX_GATE: GateArray4Q = make_n_controlled_gate!(SX_GATE, 3); + pub static RC3X_GATE: GateArray4Q = [ [ C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, @@ -274,6 +275,41 @@ pub fn phase_gate(lam: f64) -> GateArray1Q { [[C_ONE, C_ZERO], [C_ZERO, c64(0., lam).exp()]] } +#[inline] +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(); + 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) -> GateArray1Q { + let half_theta = theta / 2.; + let cos = c64(half_theta.cos(), 0.); + let isin = c64(0., -half_theta.sin()); + [[cos, isin], [isin, cos]] +} + +#[inline] +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.); + [[cos, -sin], [sin, cos]] +} + +#[inline] +pub fn rz_gate(theta: f64) -> GateArray1Q { + let ilam2 = c64(0., 0.5 * theta); + [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] +} + #[inline] pub fn u_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { let cos = (theta / 2.).cos(); @@ -323,6 +359,24 @@ pub fn cp_gate(lam: f64) -> GateArray2Q { ] } +#[inline] +pub fn crx_gate(theta: f64) -> GateArray2Q { + let gate_matrix = rx_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn cry_gate(theta: f64) -> GateArray2Q { + let gate_matrix = ry_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn crz_gate(theta: f64) -> GateArray2Q { + let gate_matrix = rz_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + #[inline] pub fn cu_gate(theta: f64, phi: f64, lam: f64, gamma: f64) -> GateArray2Q { let cos_theta = (theta / 2.).cos(); @@ -357,59 +411,6 @@ pub fn cu3_gate(theta: f64, phi: f64, lam: f64) -> GateArray2Q { make_n_controlled_gate!(gate_matrix, 1) } -#[inline] -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(); - 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) -> GateArray1Q { - let half_theta = theta / 2.; - let cos = c64(half_theta.cos(), 0.); - let isin = c64(0., -half_theta.sin()); - [[cos, isin], [isin, cos]] -} - -#[inline] -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.); - [[cos, -sin], [sin, cos]] -} - -#[inline] -pub fn rz_gate(theta: f64) -> GateArray1Q { - let ilam2 = c64(0., 0.5 * theta); - [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] -} - -#[inline] -pub fn crx_gate(theta: f64) -> GateArray2Q { - let gate_matrix = rx_gate(theta); - make_n_controlled_gate!(gate_matrix, 1) -} - -#[inline] -pub fn cry_gate(theta: f64) -> GateArray2Q { - let gate_matrix = ry_gate(theta); - make_n_controlled_gate!(gate_matrix, 1) -} - -#[inline] -pub fn crz_gate(theta: f64) -> GateArray2Q { - let gate_matrix = rz_gate(theta); - make_n_controlled_gate!(gate_matrix, 1) -} - #[inline] pub fn rxx_gate(theta: f64) -> GateArray2Q { let (sint, cost) = (theta / 2.0).sin_cos(); diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index d277dea9f89c..153b66392083 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -86,121 +86,119 @@ pub static WARNINGS_WARN: ImportOnceCell = ImportOnceCell::new("warnings", "warn /// index of it's entry in this table. This is all done statically for performance // TODO: replace placeholders with actual implementation static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ - // ZGate = 0 - ["qiskit.circuit.library.standard_gates.z", "ZGate"], - // YGate = 1 - ["qiskit.circuit.library.standard_gates.y", "YGate"], - // XGate = 2 - ["qiskit.circuit.library.standard_gates.x", "XGate"], - // CZGate = 3 - ["qiskit.circuit.library.standard_gates.z", "CZGate"], - // CYGate = 4 - ["qiskit.circuit.library.standard_gates.y", "CYGate"], - // CXGate = 5 - ["qiskit.circuit.library.standard_gates.x", "CXGate"], - // CCXGate = 6 - ["qiskit.circuit.library.standard_gates.x", "CCXGate"], - // RXGate = 7 - ["qiskit.circuit.library.standard_gates.rx", "RXGate"], - // RYGate = 8 - ["qiskit.circuit.library.standard_gates.ry", "RYGate"], - // RZGate = 9 - ["qiskit.circuit.library.standard_gates.rz", "RZGate"], - // ECRGate = 10 - ["qiskit.circuit.library.standard_gates.ecr", "ECRGate"], - // SwapGate = 11 - ["qiskit.circuit.library.standard_gates.swap", "SwapGate"], - // SXGate = 12 - ["qiskit.circuit.library.standard_gates.sx", "SXGate"], - // GlobalPhaseGate = 13 + // GlobalPhaseGate = 0 [ "qiskit.circuit.library.standard_gates.global_phase", "GlobalPhaseGate", ], - // IGate = 14 - ["qiskit.circuit.library.standard_gates.i", "IGate"], - // HGate = 15 + // HGate = 1 ["qiskit.circuit.library.standard_gates.h", "HGate"], - // PhaseGate = 16 + // IGate = 2 + ["qiskit.circuit.library.standard_gates.i", "IGate"], + // XGate = 3 + ["qiskit.circuit.library.standard_gates.x", "XGate"], + // YGate = 4 + ["qiskit.circuit.library.standard_gates.y", "YGate"], + // ZGate = 5 + ["qiskit.circuit.library.standard_gates.z", "ZGate"], + // PhaseGate = 6 ["qiskit.circuit.library.standard_gates.p", "PhaseGate"], - // UGate = 17 - ["qiskit.circuit.library.standard_gates.u", "UGate"], - // SGate = 18 + // RGate 7 + ["qiskit.circuit.library.standard_gates.r", "RGate"], + // RXGate = 8 + ["qiskit.circuit.library.standard_gates.rx", "RXGate"], + // RYGate = 9 + ["qiskit.circuit.library.standard_gates.ry", "RYGate"], + // RZGate = 10 + ["qiskit.circuit.library.standard_gates.rz", "RZGate"], + // SGate = 11 ["qiskit.circuit.library.standard_gates.s", "SGate"], - // SdgGate = 19 + // SdgGate = 12 ["qiskit.circuit.library.standard_gates.s", "SdgGate"], - // TGate = 20 + // SXGate = 13 + ["qiskit.circuit.library.standard_gates.sx", "SXGate"], + // SXdgGate = 14 + ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"], + // TGate = 15 ["qiskit.circuit.library.standard_gates.t", "TGate"], - // TdgGate = 21 + // TdgGate = 16 ["qiskit.circuit.library.standard_gates.t", "TdgGate"], - // SXdgGate = 22 - ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"], - // iSWAPGate = 23 - ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"], - // XXMinusYYGate = 24 - [ - "qiskit.circuit.library.standard_gates.xx_minus_yy", - "XXMinusYYGate", - ], - // XXPlusYYGate = 25 - [ - "qiskit.circuit.library.standard_gates.xx_plus_yy", - "XXPlusYYGate", - ], - // U1Gate = 26 + // UGate = 17 + ["qiskit.circuit.library.standard_gates.u", "UGate"], + // U1Gate = 18 ["qiskit.circuit.library.standard_gates.u1", "U1Gate"], - // U2Gate = 27 + // U2Gate = 19 ["qiskit.circuit.library.standard_gates.u2", "U2Gate"], - // U3Gate = 28 + // U3Gate = 20 ["qiskit.circuit.library.standard_gates.u3", "U3Gate"], - // CRXGate = 29 + // CHGate = 21 + ["qiskit.circuit.library.standard_gates.h", "CHGate"], + // CXGate = 22 + ["qiskit.circuit.library.standard_gates.x", "CXGate"], + // CYGate = 23 + ["qiskit.circuit.library.standard_gates.y", "CYGate"], + // CZGate = 24 + ["qiskit.circuit.library.standard_gates.z", "CZGate"], + // DCXGate = 25 + ["qiskit.circuit.library.standard_gates.dcx", "DCXGate"], + // ECRGate = 26 + ["qiskit.circuit.library.standard_gates.ecr", "ECRGate"], + // SwapGate = 27 + ["qiskit.circuit.library.standard_gates.swap", "SwapGate"], + // iSWAPGate = 28 + ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"], + // CPhaseGate = 29 + ["qiskit.circuit.library.standard_gates.p", "CPhaseGate"], + // CRXGate = 30 ["qiskit.circuit.library.standard_gates.rx", "CRXGate"], - // CRYGate = 30 + // CRYGate = 31 ["qiskit.circuit.library.standard_gates.ry", "CRYGate"], - // CRZGate = 31 + // CRZGate = 32 ["qiskit.circuit.library.standard_gates.rz", "CRZGate"], - // RGate 32 - ["qiskit.circuit.library.standard_gates.r", "RGate"], - // CHGate = 33 - ["qiskit.circuit.library.standard_gates.h", "CHGate"], - // CPhaseGate = 34 - ["qiskit.circuit.library.standard_gates.p", "CPhaseGate"], - // CSGate = 35 + // CSGate = 33 ["qiskit.circuit.library.standard_gates.s", "CSGate"], - // CSdgGate = 36 + // CSdgGate = 34 ["qiskit.circuit.library.standard_gates.s", "CSdgGate"], - // CSXGate = 37 + // CSXGate = 35 ["qiskit.circuit.library.standard_gates.sx", "CSXGate"], - // CSwapGate = 38 - ["qiskit.circuit.library.standard_gates.swap", "CSwapGate"], - // CUGate = 39 + // CUGate = 36 ["qiskit.circuit.library.standard_gates.u", "CUGate"], - // CU1Gate = 40 + // CU1Gate = 37 ["qiskit.circuit.library.standard_gates.u1", "CU1Gate"], - // CU3Gate = 41 + // CU3Gate = 38 ["qiskit.circuit.library.standard_gates.u3", "CU3Gate"], - // C3XGate = 42 - ["qiskit.circuit.library.standard_gates.x", "C3XGate"], - // C3SXGate = 43 - ["qiskit.circuit.library.standard_gates.x", "C3SXGate"], - // C4XGate = 44 - ["qiskit.circuit.library.standard_gates.x", "C4XGate"], - // DCXGate = 45 - ["qiskit.circuit.library.standard_gates.dcx", "DCXGate"], - // CCZGate = 46 - ["qiskit.circuit.library.standard_gates.z", "CCZGate"], - // RCCXGate = 47 - ["qiskit.circuit.library.standard_gates.x", "RCCXGate"], - // RC3XGate = 48 - ["qiskit.circuit.library.standard_gates.x", "RC3XGate"], - // RXXGate = 49 + // RXXGate = 39 ["qiskit.circuit.library.standard_gates.rxx", "RXXGate"], - // RYYGate = 50 + // RYYGate = 40 ["qiskit.circuit.library.standard_gates.ryy", "RYYGate"], - // RZZGate = 51 + // RZZGate = 41 ["qiskit.circuit.library.standard_gates.rzz", "RZZGate"], - // RZXGate = 52 + // RZXGate = 42 ["qiskit.circuit.library.standard_gates.rzx", "RZXGate"], + // XXMinusYYGate = 43 + [ + "qiskit.circuit.library.standard_gates.xx_minus_yy", + "XXMinusYYGate", + ], + // XXPlusYYGate = 44 + [ + "qiskit.circuit.library.standard_gates.xx_plus_yy", + "XXPlusYYGate", + ], + // CCXGate = 45 + ["qiskit.circuit.library.standard_gates.x", "CCXGate"], + // CCZGate = 46 + ["qiskit.circuit.library.standard_gates.z", "CCZGate"], + // CSwapGate = 47 + ["qiskit.circuit.library.standard_gates.swap", "CSwapGate"], + // RCCXGate = 48 + ["qiskit.circuit.library.standard_gates.x", "RCCXGate"], + // C3XGate = 49 + ["qiskit.circuit.library.standard_gates.x", "C3XGate"], + // C3SXGate = 50 + ["qiskit.circuit.library.standard_gates.x", "C3SXGate"], + // RC3XGate = 51 + ["qiskit.circuit.library.standard_gates.x", "RC3XGate"], ]; /// 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 fcbfddb72181..1eed72347da6 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -187,59 +187,58 @@ impl ToPyObject for Param { #[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)] #[pyclass(module = "qiskit._accelerate.circuit")] pub enum StandardGate { - ZGate = 0, - YGate = 1, - XGate = 2, - CZGate = 3, - CYGate = 4, - CXGate = 5, - CCXGate = 6, - RXGate = 7, - RYGate = 8, - RZGate = 9, - ECRGate = 10, - SwapGate = 11, - SXGate = 12, - GlobalPhaseGate = 13, - IGate = 14, - HGate = 15, - PhaseGate = 16, + GlobalPhaseGate = 0, + HGate = 1, + IGate = 2, + XGate = 3, + YGate = 4, + ZGate = 5, + PhaseGate = 6, + RGate = 7, + RXGate = 8, + RYGate = 9, + RZGate = 10, + SGate = 11, + SdgGate = 12, + SXGate = 13, + SXdgGate = 14, + TGate = 15, + TdgGate = 16, UGate = 17, - SGate = 18, - SdgGate = 19, - TGate = 20, - TdgGate = 21, - SXdgGate = 22, - ISwapGate = 23, - XXMinusYYGate = 24, - XXPlusYYGate = 25, - U1Gate = 26, - U2Gate = 27, - U3Gate = 28, - CRXGate = 29, - CRYGate = 30, - CRZGate = 31, - RGate = 32, - CHGate = 33, - CPhaseGate = 34, - CSGate = 35, - CSdgGate = 36, - CSXGate = 37, - CSwapGate = 38, - CUGate = 39, - CU1Gate = 40, - CU3Gate = 41, - C3XGate = 42, - C3SXGate = 43, - C4XGate = 44, - DCXGate = 45, + U1Gate = 18, + U2Gate = 19, + U3Gate = 20, + CHGate = 21, + CXGate = 22, + CYGate = 23, + CZGate = 24, + DCXGate = 25, + ECRGate = 26, + SwapGate = 27, + ISwapGate = 28, + CPhaseGate = 29, + CRXGate = 30, + CRYGate = 31, + CRZGate = 32, + CSGate = 33, + CSdgGate = 34, + CSXGate = 35, + CUGate = 36, + CU1Gate = 37, + CU3Gate = 38, + RXXGate = 39, + RYYGate = 40, + RZZGate = 41, + RZXGate = 42, + XXMinusYYGate = 43, + XXPlusYYGate = 44, + CCXGate = 45, CCZGate = 46, - RCCXGate = 47, - RC3XGate = 48, - RXXGate = 49, - RYYGate = 50, - RZZGate = 51, - RZXGate = 52, + CSwapGate = 47, + RCCXGate = 48, + C3XGate = 49, + C3SXGate = 50, + RC3XGate = 51, } impl ToPyObject for StandardGate { @@ -249,77 +248,76 @@ impl ToPyObject for 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, 2, // 20-29 - 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39 - 2, 2, 4, 4, 5, 2, 3, 3, 4, 2, // 40-49 - 2, 2, 2, // 50-52 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0-9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10-19 + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 20-29 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 30-39 + 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, // 40-49 + 4, 4, // 50-51 ]; 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, 1, // 20-29 - 1, 1, 2, 0, 1, 0, 0, 0, 0, 4, // 30-39 - 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, // 40-49 - 1, 1, 1, // 50-52 + 1, 0, 0, 0, 0, 0, 1, 2, 1, 1, // 0-9 + 1, 0, 0, 0, 0, 0, 0, 3, 1, 2, // 10-19 + 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 20-29 + 1, 1, 1, 0, 0, 0, 4, 1, 3, 1, // 30-39 + 1, 1, 1, 2, 2, 0, 0, 0, 0, 0, // 40-49 + 0, 0, // 50-51 ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ - "z", // 0 - "y", // 1 - "x", // 2 - "cz", // 3 - "cy", // 4 - "cx", // 5 - "ccx", // 6 - "rx", // 7 - "ry", // 8 - "rz", // 9 - "ecr", // 10 - "swap", // 11 - "sx", // 12 - "global_phase", // 13 - "id", // 14 - "h", // 15 - "p", // 16 + "global_phase", // 0 + "h", // 1 + "id", // 2 + "x", // 3 + "y", // 4 + "z", // 5 + "p", // 6 + "r", // 7 + "rx", // 8 + "ry", // 9 + "rz", // 10 + "s", // 11 + "sdg", // 12 + "sx", // 13 + "sxdg", // 14 + "t", // 15 + "tdg", // 16 "u", // 17 - "s", // 18 - "sdg", // 19 - "t", // 20 - "tdg", // 21 - "sxdg", // 22 - "iswap", // 23 - "xx_minus_yy", // 24 - "xx_plus_yy", // 25 - "u1", // 26 - "u2", // 27 - "u3", // 28 - "crx", // 29 - "cry", // 30 - "crz", // 31 - "r", // 32 - "ch", // 33 - "cp", // 34 - "cs", // 35 - "csdg", // 36 - "csx", // 37 - "cswap", // 38 - "cu", // 39 - "cu1", // 40 - "cu3", // 41 - "mcx", // 42 ("c3x") - "c3sx", // 43 - "mcx", // 44 ("c4x") - "dcx", // 45 + "u1", // 18 + "u2", // 19 + "u3", // 20 + "ch", // 21 + "cx", // 22 + "cy", // 23 + "cz", // 24 + "dcx", // 25 + "ecr", // 26 + "swap", // 27 + "iswap", // 28 + "cp", // 29 + "crx", // 30 + "cry", // 31 + "crz", // 32 + "cs", // 33 + "csdg", // 34 + "csx", // 35 + "cu", // 36 + "cu1", // 37 + "cu3", // 38 + "rxx", // 39 + "ryy", // 40 + "rzz", // 41 + "rzx", // 42 + "xx_minus_yy", // 43 + "xx_plus_yy", // 44 + "ccx", // 45 "ccz", // 46 - "rccx", // 47 - "rcccx", // 48 ("rc3x") - "rxx", // 49 - "ryy", // 50 - "rzz", // 51 - "rzx", // 52 + "cswap", // 47 + "rccx", // 48 + "mcx", // 49 ("c3x") + "c3sx", // 50 + "rcccx", // 51 ("rc3x") ]; #[pymethods] @@ -368,7 +366,7 @@ impl StandardGate { // // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) -pub const STANDARD_GATE_SIZE: usize = 53; +pub const STANDARD_GATE_SIZE: usize = 52; impl Operation for StandardGate { fn name(&self) -> &str { @@ -397,32 +395,40 @@ impl Operation for StandardGate { fn matrix(&self, params: &[Param]) -> Option> { match self { - Self::ZGate => match params { - [] => Some(aview2(&gate_matrix::Z_GATE).to_owned()), + Self::GlobalPhaseGate => match params { + [Param::Float(theta)] => { + Some(aview2(&gate_matrix::global_phase_gate(*theta)).to_owned()) + } _ => None, }, - Self::YGate => match params { - [] => Some(aview2(&gate_matrix::Y_GATE).to_owned()), + Self::HGate => match params { + [] => Some(aview2(&gate_matrix::H_GATE).to_owned()), + _ => None, + }, + Self::IGate => match params { + [] => Some(aview2(&gate_matrix::ONE_QUBIT_IDENTITY).to_owned()), _ => None, }, Self::XGate => match params { [] => Some(aview2(&gate_matrix::X_GATE).to_owned()), _ => None, }, - Self::CZGate => match params { - [] => Some(aview2(&gate_matrix::CZ_GATE).to_owned()), + Self::YGate => match params { + [] => Some(aview2(&gate_matrix::Y_GATE).to_owned()), _ => None, }, - Self::CYGate => match params { - [] => Some(aview2(&gate_matrix::CY_GATE).to_owned()), + Self::ZGate => match params { + [] => Some(aview2(&gate_matrix::Z_GATE).to_owned()), _ => None, }, - Self::CXGate => match params { - [] => Some(aview2(&gate_matrix::CX_GATE).to_owned()), + Self::PhaseGate => match params { + [Param::Float(theta)] => Some(aview2(&gate_matrix::phase_gate(*theta)).to_owned()), _ => None, }, - Self::CCXGate => match params { - [] => Some(aview2(&gate_matrix::CCX_GATE).to_owned()), + Self::RGate => match params { + [Param::Float(theta), Param::Float(phi)] => { + Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned()) + } _ => None, }, Self::RXGate => match params { @@ -437,24 +443,12 @@ 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()), + Self::SGate => match params { + [] => Some(aview2(&gate_matrix::S_GATE).to_owned()), _ => None, }, - Self::SwapGate => match params { - [] => Some(aview2(&gate_matrix::SWAP_GATE).to_owned()), + Self::SdgGate => match params { + [] => Some(aview2(&gate_matrix::SDG_GATE).to_owned()), _ => None, }, Self::SXGate => match params { @@ -465,38 +459,6 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::SXDG_GATE).to_owned()), _ => None, }, - Self::GlobalPhaseGate => match params { - [Param::Float(theta)] => { - Some(aview2(&gate_matrix::global_phase_gate(*theta)).to_owned()) - } - _ => None, - }, - Self::IGate => match params { - [] => Some(aview2(&gate_matrix::ONE_QUBIT_IDENTITY).to_owned()), - _ => None, - }, - Self::HGate => match params { - [] => Some(aview2(&gate_matrix::H_GATE).to_owned()), - _ => None, - }, - Self::PhaseGate => match params { - [Param::Float(theta)] => Some(aview2(&gate_matrix::phase_gate(*theta)).to_owned()), - _ => None, - }, - Self::UGate => match params { - [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { - Some(aview2(&gate_matrix::u_gate(*theta, *phi, *lam)).to_owned()) - } - _ => None, - }, - Self::SGate => match params { - [] => Some(aview2(&gate_matrix::S_GATE).to_owned()), - _ => None, - }, - Self::SdgGate => match params { - [] => Some(aview2(&gate_matrix::SDG_GATE).to_owned()), - _ => None, - }, Self::TGate => match params { [] => Some(aview2(&gate_matrix::T_GATE).to_owned()), _ => None, @@ -505,19 +467,9 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::TDG_GATE).to_owned()), _ => None, }, - Self::ISwapGate => match params { - [] => Some(aview2(&gate_matrix::ISWAP_GATE).to_owned()), - _ => None, - }, - Self::XXMinusYYGate => match params { - [Param::Float(theta), Param::Float(beta)] => { - Some(aview2(&gate_matrix::xx_minus_yy_gate(*theta, *beta)).to_owned()) - } - _ => None, - }, - Self::XXPlusYYGate => match params { - [Param::Float(theta), Param::Float(beta)] => { - Some(aview2(&gate_matrix::xx_plus_yy_gate(*theta, *beta)).to_owned()) + Self::UGate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::u_gate(*theta, *phi, *lam)).to_owned()) } _ => None, }, @@ -537,42 +489,54 @@ impl Operation for StandardGate { } _ => None, }, - Self::CUGate => match params { - [Param::Float(theta), Param::Float(phi), Param::Float(lam), Param::Float(gamma)] => { - Some(aview2(&gate_matrix::cu_gate(*theta, *phi, *lam, *gamma)).to_owned()) - } + Self::CHGate => match params { + [] => Some(aview2(&gate_matrix::CH_GATE).to_owned()), _ => None, }, - Self::CU1Gate => match params[0] { - Param::Float(lam) => Some(aview2(&gate_matrix::cu1_gate(lam)).to_owned()), + Self::CXGate => match params { + [] => Some(aview2(&gate_matrix::CX_GATE).to_owned()), _ => None, }, - Self::CU3Gate => match params { - [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { - Some(aview2(&gate_matrix::cu3_gate(*theta, *phi, *lam)).to_owned()) - } + Self::CYGate => match params { + [] => Some(aview2(&gate_matrix::CY_GATE).to_owned()), _ => None, }, - Self::C3XGate => match params { - [] => Some(aview2(&gate_matrix::C3X_GATE).to_owned()), + Self::CZGate => match params { + [] => Some(aview2(&gate_matrix::CZ_GATE).to_owned()), _ => None, }, - Self::C3SXGate => match params { - [] => Some(aview2(&gate_matrix::C3SX_GATE).to_owned()), + Self::DCXGate => match params { + [] => Some(aview2(&gate_matrix::DCX_GATE).to_owned()), _ => None, }, - Self::CCZGate => match params { - [] => Some(aview2(&gate_matrix::CCZ_GATE).to_owned()), + Self::ECRGate => match params { + [] => Some(aview2(&gate_matrix::ECR_GATE).to_owned()), _ => None, }, - Self::CHGate => match params { - [] => Some(aview2(&gate_matrix::CH_GATE).to_owned()), + Self::SwapGate => match params { + [] => Some(aview2(&gate_matrix::SWAP_GATE).to_owned()), + _ => None, + }, + Self::ISwapGate => match params { + [] => Some(aview2(&gate_matrix::ISWAP_GATE).to_owned()), _ => None, }, Self::CPhaseGate => match params { [Param::Float(lam)] => Some(aview2(&gate_matrix::cp_gate(*lam)).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::CSGate => match params { [] => Some(aview2(&gate_matrix::CS_GATE).to_owned()), _ => None, @@ -585,21 +549,22 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::CSX_GATE).to_owned()), _ => None, }, - Self::CSwapGate => match params { - [] => Some(aview2(&gate_matrix::CSWAP_GATE).to_owned()), + Self::CUGate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam), Param::Float(gamma)] => { + Some(aview2(&gate_matrix::cu_gate(*theta, *phi, *lam, *gamma)).to_owned()) + } _ => None, }, - Self::RGate => match params { - [Param::Float(theta), Param::Float(phi)] => { - Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned()) - } + Self::CU1Gate => match params[0] { + Param::Float(lam) => Some(aview2(&gate_matrix::cu1_gate(lam)).to_owned()), _ => None, }, - Self::DCXGate => match params { - [] => Some(aview2(&gate_matrix::DCX_GATE).to_owned()), + Self::CU3Gate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::cu3_gate(*theta, *phi, *lam)).to_owned()) + } _ => None, }, - Self::C4XGate => todo!(), Self::RXXGate => match params[0] { Param::Float(theta) => Some(aview2(&gate_matrix::rxx_gate(theta)).to_owned()), _ => None, @@ -616,10 +581,42 @@ impl Operation for StandardGate { Param::Float(theta) => Some(aview2(&gate_matrix::rzx_gate(theta)).to_owned()), _ => None, }, + Self::XXMinusYYGate => match params { + [Param::Float(theta), Param::Float(beta)] => { + Some(aview2(&gate_matrix::xx_minus_yy_gate(*theta, *beta)).to_owned()) + } + _ => None, + }, + Self::XXPlusYYGate => match params { + [Param::Float(theta), Param::Float(beta)] => { + Some(aview2(&gate_matrix::xx_plus_yy_gate(*theta, *beta)).to_owned()) + } + _ => None, + }, + Self::CCXGate => match params { + [] => Some(aview2(&gate_matrix::CCX_GATE).to_owned()), + _ => None, + }, + Self::CCZGate => match params { + [] => Some(aview2(&gate_matrix::CCZ_GATE).to_owned()), + _ => None, + }, + Self::CSwapGate => match params { + [] => Some(aview2(&gate_matrix::CSWAP_GATE).to_owned()), + _ => None, + }, Self::RCCXGate => match params { [] => Some(aview2(&gate_matrix::RCCX_GATE).to_owned()), _ => None, }, + Self::C3XGate => match params { + [] => Some(aview2(&gate_matrix::C3X_GATE).to_owned()), + _ => None, + }, + Self::C3SXGate => match params { + [] => Some(aview2(&gate_matrix::C3SX_GATE).to_owned()), + _ => None, + }, Self::RC3XGate => match params { [] => Some(aview2(&gate_matrix::RC3X_GATE).to_owned()), _ => None, @@ -629,14 +626,20 @@ impl Operation for StandardGate { fn definition(&self, params: &[Param]) -> Option { match self { - Self::ZGate => Python::with_gil(|py| -> Option { + Self::GlobalPhaseGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates(py, 0, [], params[0].clone()) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::HGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::PhaseGate, - smallvec![Param::Float(PI)], + Self::UGate, + smallvec![Param::Float(PI / 2.), FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -644,18 +647,15 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::YGate => Python::with_gil(|py| -> Option { + Self::IGate => None, + Self::XGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( Self::UGate, - smallvec![ - Param::Float(PI), - Param::Float(PI / 2.), - Param::Float(PI / 2.), - ], + smallvec![Param::Float(PI), FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -663,14 +663,18 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::XGate => Python::with_gil(|py| -> Option { + Self::YGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( Self::UGate, - smallvec![Param::Float(PI), Param::Float(0.), Param::Float(PI)], + smallvec![ + Param::Float(PI), + Param::Float(PI / 2.), + Param::Float(PI / 2.), + ], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -678,69 +682,47 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CZGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + + Self::ZGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, - 2, - [ - (Self::HGate, smallvec![], q1.clone()), - (Self::CXGate, smallvec![], q0_1), - (Self::HGate, smallvec![], q1), - ], + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(PI)], + smallvec![Qubit(0)], + )], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::CYGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + Self::PhaseGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, - 2, - [ - (Self::SdgGate, smallvec![], q1.clone()), - (Self::CXGate, smallvec![], q0_1), - (Self::SGate, smallvec![], q1), - ], + 1, + [( + Self::UGate, + smallvec![FLOAT_ZERO, FLOAT_ZERO, params[0].clone()], + smallvec![Qubit(0)], + )], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::CXGate => None, - Self::CCXGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q2 = smallvec![Qubit(2)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; - let q0_2 = smallvec![Qubit(0), Qubit(2)]; - let q1_2 = smallvec![Qubit(1), Qubit(2)]; + 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); + 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, - 3, - [ - (Self::HGate, smallvec![], q2.clone()), - (Self::CXGate, smallvec![], q1_2.clone()), - (Self::TdgGate, smallvec![], q2.clone()), - (Self::CXGate, smallvec![], q0_2.clone()), - (Self::TGate, smallvec![], q2.clone()), - (Self::CXGate, smallvec![], q1_2), - (Self::TdgGate, smallvec![], q2.clone()), - (Self::CXGate, smallvec![], q0_2), - (Self::TGate, smallvec![], q1.clone()), - (Self::TGate, smallvec![], q2.clone()), - (Self::HGate, smallvec![], q2), - (Self::CXGate, smallvec![], q0_1.clone()), - (Self::TGate, smallvec![], q0), - (Self::TdgGate, smallvec![], q1), - (Self::CXGate, smallvec![], q0_1), - ], + 1, + [(Self::UGate, defparams, smallvec![Qubit(0)])], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), @@ -770,7 +752,7 @@ impl Operation for StandardGate { 1, [( Self::RGate, - smallvec![theta.clone(), Param::Float(PI / 2.0)], + smallvec![theta.clone(), Param::Float(PI / 2.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -794,105 +776,31 @@ 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]; + Self::SGate => Python::with_gil(|py| -> Option { 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), + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(PI / 2.)], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, ) - .expect("Unexpected Qiskit Python bug!"), + .expect("Unexpected Qiskit python bug"), ) }), - Self::ECRGate => todo!("Add when we have RZX"), - Self::SwapGate => Python::with_gil(|py| -> Option { + Self::SdgGate => 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)]), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - ], + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(-PI / 2.)], + smallvec![Qubit(0)], + )], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), @@ -928,21 +836,14 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::GlobalPhaseGate => Python::with_gil(|py| -> Option { - Some( - CircuitData::from_standard_gates(py, 0, [], params[0].clone()) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::IGate => None, - Self::HGate => Python::with_gil(|py| -> Option { + Self::TGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::UGate, - smallvec![Param::Float(PI / 2.), Param::Float(0.), Param::Float(PI)], + Self::PhaseGate, + smallvec![Param::Float(PI / 4.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -950,14 +851,14 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::PhaseGate => Python::with_gil(|py| -> Option { + Self::TdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::UGate, - smallvec![Param::Float(0.), Param::Float(0.), params[0].clone()], + Self::PhaseGate, + smallvec![Param::Float(-PI / 4.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -1011,61 +912,110 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::SGate => Python::with_gil(|py| -> Option { + 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, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(PI / 2.)], - smallvec![Qubit(0)], - )], + 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::SdgGate => Python::with_gil(|py| -> Option { + + Self::CXGate => None, + Self::CYGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; Some( CircuitData::from_standard_gates( py, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(-PI / 2.)], - smallvec![Qubit(0)], - )], + 2, + [ + (Self::SdgGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1), + (Self::SGate, smallvec![], q1), + ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::TGate => Python::with_gil(|py| -> Option { + Self::CZGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; Some( CircuitData::from_standard_gates( py, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(PI / 4.)], - smallvec![Qubit(0)], - )], + 2, + [ + (Self::HGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1), + (Self::HGate, smallvec![], q1), + ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::TdgGate => Python::with_gil(|py| -> Option { + Self::DCXGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(-PI / 4.)], - smallvec![Qubit(0)], - )], + 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::ECRGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::RZXGate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(0), Qubit(1)], + ), + (Self::XGate, smallvec![], smallvec![Qubit(0)]), + ( + Self::RZXGate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(0), Qubit(1)], + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::SwapGate => 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)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), @@ -1089,82 +1039,185 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::XXMinusYYGate => Python::with_gil(|py| -> Option { + 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)]; - let theta = ¶ms[0]; - let beta = ¶ms[1]; Some( CircuitData::from_standard_gates( py, 2, [ ( - Self::RZGate, - smallvec![multiply_param(beta, -1.0, py)], - q1.clone(), + Self::PhaseGate, + smallvec![multiply_param(¶ms[0], 0.5, py)], + q0, ), - (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), - (Self::SXGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0.clone()), - (Self::SGate, smallvec![], q1.clone()), (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::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)], - q0.clone(), + 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::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::SdgGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), - (Self::SXdgGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0), - (Self::RZGate, smallvec![beta.clone()], q1), + (Self::PhaseGate, smallvec![Param::Float(PI / 4.)], q1), ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::XXPlusYYGate => Python::with_gil(|py| -> Option { + Self::CSdgGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; - let q1_0 = smallvec![Qubit(1), Qubit(0)]; - let theta = ¶ms[0]; - let beta = ¶ms[1]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; Some( CircuitData::from_standard_gates( py, 2, [ - (Self::RZGate, smallvec![beta.clone()], q0.clone()), - (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), - (Self::SXGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1.clone()), - (Self::SGate, smallvec![], q0.clone()), - (Self::CXGate, smallvec![], q1_0.clone()), + (Self::PhaseGate, smallvec![Param::Float(-PI / 4.)], q0), + (Self::CXGate, smallvec![], q0_1.clone()), ( - Self::RYGate, - smallvec![multiply_param(theta, -0.5, py)], + Self::PhaseGate, + smallvec![Param::Float(PI / 4.)], q1.clone(), ), - ( - Self::RYGate, - smallvec![multiply_param(theta, -0.5, py)], - q0.clone(), - ), - (Self::CXGate, smallvec![], q1_0), - (Self::SdgGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), - (Self::SXdgGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1), - (Self::RZGate, smallvec![multiply_param(beta, -1.0, py)], q0), + (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, ) @@ -1212,7 +1265,7 @@ impl Operation for StandardGate { Self::UGate, smallvec![ multiply_param(¶ms[0], -0.5, py), - Param::Float(0.), + FLOAT_ZERO, param_first_u ], smallvec![Qubit(1)], @@ -1223,7 +1276,7 @@ impl Operation for StandardGate { smallvec![ multiply_param(¶ms[0], 0.5, py), params[1].clone(), - Param::Float(0.) + FLOAT_ZERO ], smallvec![Qubit(1)], ), @@ -1233,27 +1286,6 @@ 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::CU1Gate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1283,38 +1315,6 @@ impl Operation for StandardGate { .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::CU3Gate => Python::with_gil(|py| -> Option { let param_first_u1 = radd_param( multiply_param(¶ms[2], 0.5, py), @@ -1347,7 +1347,7 @@ impl Operation for StandardGate { Self::U3Gate, smallvec![ multiply_param(¶ms[0], -0.5, py), - Param::Float(0.), + FLOAT_ZERO, param_first_u3 ], smallvec![Qubit(1)], @@ -1358,7 +1358,7 @@ impl Operation for StandardGate { smallvec![ multiply_param(¶ms[0], 0.5, py), params[1].clone(), - Param::Float(0.) + FLOAT_ZERO ], smallvec![Qubit(1)], ), @@ -1368,33 +1368,276 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CSGate => Python::with_gil(|py| -> Option { + Self::RXXGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; 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), + (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::C3XGate => Python::with_gil(|py| -> Option { - Some( - CircuitData::from_standard_gates( + 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"), + ) + }), + Self::XXMinusYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + let beta = ¶ms[1]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::RZGate, + smallvec![multiply_param(beta, -1.0, py)], + q1.clone(), + ), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), + (Self::SXGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0.clone()), + (Self::SGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1.clone()), + ( + Self::RYGate, + smallvec![multiply_param(theta, 0.5, py)], + q0.clone(), + ), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q1.clone(), + ), + (Self::CXGate, smallvec![], q0_1), + (Self::SdgGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), + (Self::SXdgGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0), + (Self::RZGate, smallvec![beta.clone()], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::XXPlusYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q1_0 = smallvec![Qubit(1), Qubit(0)]; + let theta = ¶ms[0]; + let beta = ¶ms[1]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::RZGate, smallvec![beta.clone()], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), + (Self::SXGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1.clone()), + (Self::SGate, smallvec![], q0.clone()), + (Self::CXGate, smallvec![], q1_0.clone()), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q1.clone(), + ), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q0.clone(), + ), + (Self::CXGate, smallvec![], q1_0), + (Self::SdgGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), + (Self::SXdgGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1), + (Self::RZGate, smallvec![multiply_param(beta, -1.0, py)], q0), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CCXGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q2 = smallvec![Qubit(2)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q0_2 = smallvec![Qubit(0), Qubit(2)]; + let q1_2 = smallvec![Qubit(1), Qubit(2)]; + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + (Self::HGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q1_2.clone()), + (Self::TdgGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q0_2.clone()), + (Self::TGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q1_2), + (Self::TdgGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q0_2), + (Self::TGate, smallvec![], q1.clone()), + (Self::TGate, smallvec![], q2.clone()), + (Self::HGate, smallvec![], q2), + (Self::CXGate, smallvec![], q0_1.clone()), + (Self::TGate, smallvec![], q0), + (Self::TdgGate, smallvec![], q1), + (Self::CXGate, smallvec![], q0_1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + + Self::CCZGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + (Self::HGate, smallvec![], smallvec![Qubit(2)]), + ( + Self::CCXGate, + smallvec![], + smallvec![Qubit(0), Qubit(1), Qubit(2)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(2)]), + ], + 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::RCCXGate => Python::with_gil(|py| -> Option { + let q2 = smallvec![Qubit(2)]; + let q0_2 = smallvec![Qubit(0), Qubit(2)]; + let q1_2 = smallvec![Qubit(1), Qubit(2)]; + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + ( + Self::U2Gate, + smallvec![FLOAT_ZERO, Param::Float(PI)], + q2.clone(), + ), + (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q1_2.clone()), + (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q0_2), + (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q1_2), + (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), + (Self::U2Gate, smallvec![FLOAT_ZERO, Param::Float(PI)], q2), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::C3XGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( py, 4, [ @@ -1495,30 +1738,7 @@ impl Operation for StandardGate { .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::C3SXGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1586,207 +1806,6 @@ impl Operation for StandardGate { .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::CCZGate => Python::with_gil(|py| -> Option { - Some( - CircuitData::from_standard_gates( - py, - 3, - [ - (Self::HGate, smallvec![], smallvec![Qubit(2)]), - ( - Self::CCXGate, - smallvec![], - smallvec![Qubit(0), Qubit(1), Qubit(2)], - ), - (Self::HGate, smallvec![], smallvec![Qubit(2)]), - ], - 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); - 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::C4XGate => 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::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"), - ) - }), - Self::RCCXGate => Python::with_gil(|py| -> Option { - let q2 = smallvec![Qubit(2)]; - let q0_2 = smallvec![Qubit(0), Qubit(2)]; - let q1_2 = smallvec![Qubit(1), Qubit(2)]; - Some( - CircuitData::from_standard_gates( - py, - 3, - [ - ( - Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], - q2.clone(), - ), - (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), - (Self::CXGate, smallvec![], q1_2.clone()), - (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), - (Self::CXGate, smallvec![], q0_2), - (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), - (Self::CXGate, smallvec![], q1_2), - (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), - ( - Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], - q2, - ), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), Self::RC3XGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1795,7 +1814,7 @@ impl Operation for StandardGate { [ ( Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(3)], ), ( @@ -1811,7 +1830,7 @@ impl Operation for StandardGate { ), ( Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(3)], ), (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), @@ -1840,7 +1859,7 @@ impl Operation for StandardGate { ), ( Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(3)], ), ( @@ -1856,7 +1875,7 @@ impl Operation for StandardGate { ), ( Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(3)], ), ], diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index 29cc0723bb60..c226ae004896 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -24,7 +24,6 @@ from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping from qiskit.quantum_info import Operator -SKIP_LIST = {"rx", "ry", "ecr"} CUSTOM_NAME_MAPPING = {"mcx": C3XGate()} @@ -62,9 +61,6 @@ def test_definitions(self): """Test definitions are the same in rust space.""" for name, gate_class in self.standard_gates.items(): standard_gate = getattr(gate_class, "_standard_gate", None) - if name in SKIP_LIST: - # gate does not have a rust definition yet - continue if standard_gate is None: # gate is not in rust yet continue @@ -157,9 +153,6 @@ def test_name(self): if standard_gate is None: # gate is not in rust yet continue - if gate_class.name == "mcx": - # ambiguous gate name - continue with self.subTest(name=name): self.assertEqual(gate_class.name, standard_gate.name) From 1191fcbc7277216ce03d30f1bf0d2767ea681cd9 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 12 Jul 2024 22:58:10 -0400 Subject: [PATCH 83/89] Oxidize two qubit local invariance functions (#12739) * Oxidize two qubit local invariance functions This commit migrates the two functions in the private module `qiskit.synthesis.two_qubit.local_invariance` to primarily be implemented in rust. Since the two_qubit_local_invariants() function is being used in #12727 premptively porting these functions to rust will both potentially speed up the new transpiler pass in that PR and also facilitate a future porting of that pass to rust. The two python space functions continue to exist as a small wrapper to do input type checking/conversion and rounding of the result (since the python API for rounding is simpler). There is no release note included for these functions as they are internal utilities in Qiskit and not exposed as a public interface. * Add docstring to rust function with citation * Store adjoint magic array statically * Use arraview1 instead of slice for local_equivalence() * Fix rustfmt --- crates/accelerate/src/two_qubit_decompose.rs | 103 +++++++++++++++++- .../synthesis/two_qubit/local_invariance.py | 46 ++------ 2 files changed, 110 insertions(+), 39 deletions(-) diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index 568206925c2b..ac2cc1d2e50e 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -31,8 +31,8 @@ use faer_ext::{IntoFaer, IntoFaerComplex, IntoNdarray, IntoNdarrayComplex}; use ndarray::linalg::kron; use ndarray::prelude::*; use ndarray::Zip; -use numpy::PyReadonlyArray2; use numpy::{IntoPyArray, ToPyArray}; +use numpy::{PyReadonlyArray1, PyReadonlyArray2}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; @@ -1883,9 +1883,110 @@ impl TwoQubitBasisDecomposer { } } +static MAGIC: GateArray2Q = [ + [ + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + C_ZERO, + c64(0., FRAC_1_SQRT_2), + ], + [ + C_ZERO, + c64(0., FRAC_1_SQRT_2), + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + ], + [ + C_ZERO, + c64(0., FRAC_1_SQRT_2), + c64(-FRAC_1_SQRT_2, 0.), + C_ZERO, + ], + [ + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + C_ZERO, + c64(0., -FRAC_1_SQRT_2), + ], +]; + +static MAGIC_DAGGER: GateArray2Q = [ + [ + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + C_ZERO, + c64(FRAC_1_SQRT_2, 0.), + ], + [ + C_ZERO, + c64(0., -FRAC_1_SQRT_2), + c64(0., -FRAC_1_SQRT_2), + C_ZERO, + ], + [ + C_ZERO, + c64(FRAC_1_SQRT_2, 0.), + c64(-FRAC_1_SQRT_2, 0.), + C_ZERO, + ], + [ + c64(0., -FRAC_1_SQRT_2), + C_ZERO, + C_ZERO, + c64(0., FRAC_1_SQRT_2), + ], +]; + +/// Computes the local invariants for a two-qubit unitary. +/// +/// Based on: +/// +/// Y. Makhlin, Quant. Info. Proc. 1, 243-252 (2002). +/// +/// Zhang et al., Phys Rev A. 67, 042313 (2003). +#[pyfunction] +pub fn two_qubit_local_invariants(unitary: PyReadonlyArray2) -> [f64; 3] { + let mat = unitary.as_array(); + // Transform to bell basis + let bell_basis_unitary = aview2(&MAGIC_DAGGER).dot(&mat.dot(&aview2(&MAGIC))); + // Get determinate since +- one is allowed. + let det_bell_basis = bell_basis_unitary + .view() + .into_faer_complex() + .determinant() + .to_num_complex(); + let m = bell_basis_unitary.t().dot(&bell_basis_unitary); + let mut m_tr2 = m.diag().sum(); + m_tr2 *= m_tr2; + // Table II of Ref. 1 or Eq. 28 of Ref. 2. + let g1 = m_tr2 / (16. * det_bell_basis); + let g2 = (m_tr2 - m.dot(&m).diag().sum()) / (4. * det_bell_basis); + + // Here we split the real and imag pieces of G1 into two so as + // to better equate to the Weyl chamber coordinates (c0,c1,c2) + // and explore the parameter space. + // Also do a FP trick -0.0 + 0.0 = 0.0 + [g1.re + 0., g1.im + 0., g2.re + 0.] +} + +#[pyfunction] +pub fn local_equivalence(weyl: PyReadonlyArray1) -> PyResult<[f64; 3]> { + let weyl = weyl.as_array(); + let weyl_2_cos_squared_product: f64 = weyl.iter().map(|x| (x * 2.).cos().powi(2)).product(); + let weyl_2_sin_squared_product: f64 = weyl.iter().map(|x| (x * 2.).sin().powi(2)).product(); + let g0_equiv = weyl_2_cos_squared_product - weyl_2_sin_squared_product; + let g1_equiv = weyl.iter().map(|x| (x * 4.).sin()).product::() / 4.; + let g2_equiv = 4. * weyl_2_cos_squared_product + - 4. * weyl_2_sin_squared_product + - weyl.iter().map(|x| (4. * x).cos()).product::(); + Ok([g0_equiv + 0., g1_equiv + 0., g2_equiv + 0.]) +} + #[pymodule] pub fn two_qubit_decompose(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(_num_basis_gates))?; + m.add_wrapped(wrap_pyfunction!(two_qubit_local_invariants))?; + m.add_wrapped(wrap_pyfunction!(local_equivalence))?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/qiskit/synthesis/two_qubit/local_invariance.py b/qiskit/synthesis/two_qubit/local_invariance.py index 346febdddf9f..7b20c2e5655e 100644 --- a/qiskit/synthesis/two_qubit/local_invariance.py +++ b/qiskit/synthesis/two_qubit/local_invariance.py @@ -15,17 +15,9 @@ of two-qubit unitary operators. """ from __future__ import annotations -from math import sqrt import numpy as np - -INVARIANT_TOL = 1e-12 - -# Bell "Magic" basis -MAGIC = ( - 1.0 - / sqrt(2) - * np.array([[1, 0, 0, 1j], [0, 1j, 1, 0], [0, 1j, -1, 0], [1, 0, 0, -1j]], dtype=complex) -) +from qiskit._accelerate.two_qubit_decompose import two_qubit_local_invariants as tqli_rs +from qiskit._accelerate.two_qubit_decompose import local_equivalence as le_rs def two_qubit_local_invariants(U: np.ndarray) -> np.ndarray: @@ -44,28 +36,11 @@ def two_qubit_local_invariants(U: np.ndarray) -> np.ndarray: Y. Makhlin, Quant. Info. Proc. 1, 243-252 (2002). Zhang et al., Phys Rev A. 67, 042313 (2003). """ - U = np.asarray(U) + U = np.asarray(U, dtype=complex) if U.shape != (4, 4): raise ValueError("Unitary must correspond to a two-qubit gate.") - - # Transform to bell basis - Um = MAGIC.conj().T.dot(U.dot(MAGIC)) - # Get determinate since +- one is allowed. - det_um = np.linalg.det(Um) - M = Um.T.dot(Um) - # trace(M)**2 - m_tr2 = M.trace() - m_tr2 *= m_tr2 - - # Table II of Ref. 1 or Eq. 28 of Ref. 2. - G1 = m_tr2 / (16 * det_um) - G2 = (m_tr2 - np.trace(M.dot(M))) / (4 * det_um) - - # Here we split the real and imag pieces of G1 into two so as - # to better equate to the Weyl chamber coordinates (c0,c1,c2) - # and explore the parameter space. - # Also do a FP trick -0.0 + 0.0 = 0.0 - return np.round([G1.real, G1.imag, G2.real], 12) + 0.0 + (a, b, c) = tqli_rs(U) + return np.array([round(a, 12), round(b, 12), round(c, 12)]) def local_equivalence(weyl: np.ndarray) -> np.ndarray: @@ -83,11 +58,6 @@ def local_equivalence(weyl: np.ndarray) -> np.ndarray: but we multiply weyl coordinates by 2 since we are working in the reduced chamber. """ - g0_equiv = np.prod(np.cos(2 * weyl) ** 2) - np.prod(np.sin(2 * weyl) ** 2) - g1_equiv = np.prod(np.sin(4 * weyl)) / 4 - g2_equiv = ( - 4 * np.prod(np.cos(2 * weyl) ** 2) - - 4 * np.prod(np.sin(2 * weyl) ** 2) - - np.prod(np.cos(4 * weyl)) - ) - return np.round([g0_equiv, g1_equiv, g2_equiv], 12) + 0.0 + mat = np.asarray(weyl, dtype=float) + (a, b, c) = le_rs(mat) + return np.array([round(a, 12), round(b, 12), round(c, 12)]) From 938141b4b61035741cbb33efe99af5292544154b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:45:55 +0200 Subject: [PATCH 84/89] Apply suggestions from John's code review Co-authored-by: John Lapeyre --- crates/circuit/src/dag_circuit.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 971ac0126add..ab1b88ba5de9 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -2919,16 +2919,16 @@ def _format(operand): /// Remove all of the ancestor operation nodes of node. fn remove_ancestors_of(&mut self, node: &DAGNode) -> PyResult<()> { - let dag_binding = self.dag.clone(); - for ancestor in core_ancestors(&dag_binding, node.node.unwrap()) - .filter(|next| next != &node.node.unwrap()) - .filter(|next| match dag_binding.node_weight(*next) { - Some(NodeType::Operation(_)) => true, - _ => false, + let ancestors: Vec<_> = core_ancestors(&self.dag, node.node.unwrap()) + .filter(|next| { + next != &node.node.unwrap() + && match self.dag.node_weight(*next) { + Some(NodeType::Operation(_)) => true, + _ => false, + } }) - { - self.dag.remove_node(ancestor); - } + .collect(); + ancestors.iter().map(|a| self.dag.remove_node(*a)); Ok(()) } From d6ffa285affc4f45392a0863326f34976562b1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 15 Jul 2024 10:13:22 +0200 Subject: [PATCH 85/89] Apply suggestions from code review --- crates/circuit/src/dag_circuit.rs | 79 ++++++++++++++++++------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index ab1b88ba5de9..3da8613bae6d 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -2928,58 +2928,69 @@ def _format(operand): } }) .collect(); - ancestors.iter().map(|a| self.dag.remove_node(*a)); + for a in ancestors { + self.dag.remove_node(a); + } Ok(()) } /// Remove all of the descendant operation nodes of node. fn remove_descendants_of(&mut self, node: &DAGNode) -> PyResult<()> { - let dag_binding = self.dag.clone(); - for descendant in core_descendants(&dag_binding, node.node.unwrap()) - .filter(|next| next != &node.node.unwrap()) - .filter(|next| match dag_binding.node_weight(*next) { - Some(NodeType::Operation(_)) => true, - _ => false, + let descendants: Vec<_> = core_descendants(&self.dag, node.node.unwrap()) + .filter(|next| { + next != &node.node.unwrap() + && match self.dag.node_weight(*next) { + Some(NodeType::Operation(_)) => true, + _ => false, + } }) - { - self.dag.remove_node(descendant); + .collect(); + for d in descendants { + self.dag.remove_node(d); } Ok(()) } /// Remove all of the non-ancestors operation nodes of node. fn remove_nonancestors_of(&mut self, node: &DAGNode) -> PyResult<()> { - let dag_binding = self.dag.clone(); - let mut ancestors = core_ancestors(&dag_binding, node.node.unwrap()) - .filter(|next| next != &node.node.unwrap()) - .filter(|next| match dag_binding.node_weight(*next) { - Some(NodeType::Operation(_)) => true, - _ => false, - }); - for node_id in dag_binding.node_indices() { - if ancestors.find(|anc| *anc == node_id).is_some() { - continue; - } - self.dag.remove_node(node_id); + let ancestors: Vec<_> = core_ancestors(&self.dag, node.node.unwrap()) + .filter(|next| { + next != &node.node.unwrap() + && match self.dag.node_weight(*next) { + Some(NodeType::Operation(_)) => true, + _ => false, + } + }) + .collect(); + let non_ancestors: Vec<_> = self + .dag + .node_indices() + .filter(|node_id| ancestors.iter().find(|anc| *anc == node_id).is_none()) + .collect(); + for na in non_ancestors { + self.dag.remove_node(na); } Ok(()) } /// Remove all of the non-descendants operation nodes of node. fn remove_nondescendants_of(&mut self, node: &DAGNode) -> PyResult<()> { - let dag_binding = self.dag.clone(); - let mut descendants = core_descendants(&dag_binding, node.node.unwrap()) - .filter(|next| next != &node.node.unwrap()) - .filter(|next| match dag_binding.node_weight(*next) { - Some(NodeType::Operation(_)) => true, - _ => false, - }); - - for node_id in dag_binding.node_indices() { - if descendants.find(|desc| *desc == node_id).is_some() { - continue; - } - self.dag.remove_node(node_id); + let descendants: Vec<_> = core_descendants(&self.dag, node.node.unwrap()) + .filter(|next| { + next != &node.node.unwrap() + && match self.dag.node_weight(*next) { + Some(NodeType::Operation(_)) => true, + _ => false, + } + }) + .collect(); + let non_descendants: Vec<_> = self + .dag + .node_indices() + .filter(|node_id| descendants.iter().find(|desc| *desc == node_id).is_none()) + .collect(); + for nd in non_descendants { + self.dag.remove_node(nd); } Ok(()) } From 3575ffea2f229ba5a200c68ea57f6163c2e360d5 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 15 Jul 2024 14:08:20 -0400 Subject: [PATCH 86/89] More compose impl. --- crates/circuit/src/dag_circuit.rs | 82 +++++++++++++++++++++++++++++-- crates/circuit/src/dot_utils.rs | 1 + 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 6d575d213a1f..727e218e0f2c 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -1392,7 +1392,7 @@ def _format(operand): clbits: Option>, front: bool, inplace: bool, - ) -> PyResult>> { + ) -> PyResult>> { if front { return Err(DAGCircuitError::new_err( "Front composition not supported yet.", @@ -1484,8 +1484,8 @@ def _format(operand): .into_py_dict_bound(py) }; + // Chck duplicates in wire map. { - // TODO: is there a better way to do this? let edge_map_values: Vec<_> = edge_map.values().iter().collect(); if PySet::new_bound(py, edge_map_values.as_slice())?.len() != edge_map.len() { return Err(DAGCircuitError::new_err("duplicates in wire_map")); @@ -1514,6 +1514,74 @@ def _format(operand): None, Some(wrap_pyfunction_bound!(reject_new_register, py)?.to_object(py)), ); + + for node in other.topological_nodes()? { + match &other.dag[node] { + NodeType::QubitIn(q) => { + let bit = other.qubits.get(*q).unwrap().bind(py); + let m_wire = edge_map.get_item(bit)?.unwrap_or_else(|| bit.clone()); + let bit_in_dag = dag.qubits.find(bit); + if bit_in_dag.is_none() + || !dag.qubit_output_map.contains_key(&bit_in_dag.unwrap()) + { + return Err(DAGCircuitError::new_err(format!( + "wire {}[{}] not in self", + m_wire.getattr("name")?, + m_wire.getattr("index")? + ))); + } + // TODO: Python code has check here if node.wire is in other._wires. Why? + } + NodeType::ClbitIn(c) => { + let bit = other.clbits.get(*c).unwrap().bind(py); + let m_wire = edge_map.get_item(bit)?.unwrap_or_else(|| bit.clone()); + let bit_in_dag = dag.clbits.find(bit); + if bit_in_dag.is_none() + || !dag.clbit_output_map.contains_key(&bit_in_dag.unwrap()) + { + return Err(DAGCircuitError::new_err(format!( + "wire {}[{}] not in self", + m_wire.getattr("name")?, + m_wire.getattr("index")? + ))); + } + // TODO: Python code has check here if node.wire is in other._wires. Why? + } + NodeType::Operation(op) => { + let m_qargs = { + let qubits = other.qubits.map_indices(other.qargs_cache.intern(op.qubits_id).to_slice()); + let mut mapped = Vec::with_capacity(qubits.len()); + for bit in qubits { + mapped.push(edge_map.get_item(bit)?.unwrap_or_else(|| bit.bind(py).clone())); + } + PyTuple::new_bound(py, mapped) + }; + let m_cargs = { + let clbits = other.clbits.map_indices(other.cargs_cache.intern(op.clbits_id).to_slice()); + let mut mapped = Vec::with_capacity(clbits.len()); + for bit in clbits { + mapped.push(edge_map.get_item(bit)?.unwrap_or_else(|| bit.bind(py).clone())); + } + PyTuple::new_bound(py, mapped) + }; + + let py_op = if let Some(condition) = op.condition() { + // TODO: do we need to check for condition.is_none()? + if !op.op.control_flow() { + match op.op { + OperationType::Standard(_) => {} + OperationType::Instruction(_) => {} + OperationType::Gate(_) => {} + OperationType::Operation(_) => {} + } + } + } else { + op.into_py(py) + }; + } + NodeType::QubitOut(_) | NodeType::ClbitOut(_) => (), + } + } // if qubits is None: // qubit_map = identity_qubit_map // elif len(qubits) != len(other.qubits): @@ -1598,7 +1666,12 @@ def _format(operand): // return dag // else: // return None - todo!() + + if !inplace { + Some(dag) + } else { + None + } } /// Reverse the operations in the ``self`` circuit. @@ -2680,6 +2753,7 @@ def _format(operand): match edge.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), + Wire::Var(var) => var, }, )) } @@ -3498,7 +3572,7 @@ impl DAGCircuit { let wire = self.dag.edge_weight(edge_index).unwrap(); match wire { Wire::Qubit(index) => Ok(Some(index.0 as usize)), - Wire::Clbit(_) => Ok(None), + _ => Ok(None), } }; rustworkx_core::dag_algo::collect_bicolor_runs(&self.dag, filter_fn, color_fn).unwrap() diff --git a/crates/circuit/src/dot_utils.rs b/crates/circuit/src/dot_utils.rs index e04889fca0b5..66e0f99d97d6 100644 --- a/crates/circuit/src/dot_utils.rs +++ b/crates/circuit/src/dot_utils.rs @@ -59,6 +59,7 @@ where let edge_weight = match edge.weight() { Wire::Qubit(qubit) => dag.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => dag.clbits.get(*clbit).unwrap(), + Wire::Var(var) => var, }; writeln!( file, From ce6a0da3a451dac7aa14737e4b757792ebd15289 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 15 Jul 2024 15:54:32 -0400 Subject: [PATCH 87/89] Merge fixes, finish compose. --- crates/circuit/src/circuit_instruction.rs | 2 +- crates/circuit/src/dag_circuit.rs | 65 ++++++++++++++++------- crates/circuit/src/dag_node.rs | 8 +-- crates/circuit/src/lib.rs | 10 ++-- 4 files changed, 55 insertions(+), 30 deletions(-) diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index f716024c0ccd..2e7ac97c9bb0 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -93,7 +93,7 @@ impl PackedInstruction { params, extra_attrs, #[cfg(feature = "cache_pygates")] - py_op, + py_op: RefCell::new(py_op), } } diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 727e218e0f2c..dc1ad3d592da 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -11,11 +11,11 @@ // that they have been altered from the originals. use crate::bit_data::BitData; -use crate::circuit_instruction::PackedInstruction; use crate::circuit_instruction::{ convert_py_to_operation_type, CircuitInstruction, ExtraInstructionAttributes, OperationTypeConstruct, }; +use crate::circuit_instruction::{operation_type_and_data_to_py, PackedInstruction}; use crate::dag_node::{DAGInNode, DAGNode, DAGOpNode, DAGOutNode}; use crate::dot_utils::build_dot; use crate::error::DAGCircuitError; @@ -25,7 +25,7 @@ use crate::imports::{ }; use crate::interner::{Index, IndexedInterner, Interner}; use crate::operations::{Operation, OperationType, Param}; -use crate::{interner, BitType, Clbit, Qubit, SliceOrInt, TupleLikeArg}; +use crate::{interner, BitType, Clbit, Qubit, TupleLikeArg}; use hashbrown::hash_map::DefaultHashBuilder; use hashbrown::{hash_map, HashMap, HashSet}; use indexmap::set::Slice; @@ -1392,7 +1392,7 @@ def _format(operand): clbits: Option>, front: bool, inplace: bool, - ) -> PyResult>> { + ) -> PyResult> { if front { return Err(DAGCircuitError::new_err( "Front composition not supported yet.", @@ -1510,10 +1510,10 @@ def _format(operand): let variable_mapper = PyVariableMapper::new( py, dag.cregs.bind(py).values().into_any(), - Some(edge_map), + Some(edge_map.clone()), None, Some(wrap_pyfunction_bound!(reject_new_register, py)?.to_object(py)), - ); + )?; for node in other.topological_nodes()? { match &other.dag[node] { @@ -1549,35 +1549,60 @@ def _format(operand): } NodeType::Operation(op) => { let m_qargs = { - let qubits = other.qubits.map_indices(other.qargs_cache.intern(op.qubits_id).to_slice()); + let qubits = other + .qubits + .map_indices(other.qargs_cache.intern(op.qubits_id).as_slice()); let mut mapped = Vec::with_capacity(qubits.len()); for bit in qubits { - mapped.push(edge_map.get_item(bit)?.unwrap_or_else(|| bit.bind(py).clone())); + mapped.push( + edge_map + .get_item(bit)? + .unwrap_or_else(|| bit.bind(py).clone()), + ); } PyTuple::new_bound(py, mapped) }; let m_cargs = { - let clbits = other.clbits.map_indices(other.cargs_cache.intern(op.clbits_id).to_slice()); + let clbits = other + .clbits + .map_indices(other.cargs_cache.intern(op.clbits_id).as_slice()); let mut mapped = Vec::with_capacity(clbits.len()); for bit in clbits { - mapped.push(edge_map.get_item(bit)?.unwrap_or_else(|| bit.bind(py).clone())); + mapped.push( + edge_map + .get_item(bit)? + .unwrap_or_else(|| bit.bind(py).clone()), + ); } PyTuple::new_bound(py, mapped) }; - let py_op = if let Some(condition) = op.condition() { + let mut py_op = op.unpack_py_op(py)?.into_bound(py); + if let Some(condition) = op.condition() { // TODO: do we need to check for condition.is_none()? + let condition = variable_mapper.map_condition(condition.bind(py), true)?; if !op.op.control_flow() { - match op.op { - OperationType::Standard(_) => {} - OperationType::Instruction(_) => {} - OperationType::Gate(_) => {} - OperationType::Operation(_) => {} - } + py_op = py_op.call_method1( + intern!(py, "c_if"), + condition.downcast::()?, + )?; + } else { + py_op.setattr(intern!(py, "condition"), condition)?; } - } else { - op.into_py(py) + } else if py_op.is_instance(SWITCH_CASE_OP.get_bound(py))? { + py_op.setattr( + intern!(py, "target"), + variable_mapper.map_target(&py_op.getattr(intern!(py, "target"))?)?, + )?; }; + + dag.py_apply_operation_back( + py, + py_op, + Some(TupleLikeArg { value: m_qargs }), + Some(TupleLikeArg { value: m_cargs }), + false, + )?; } NodeType::QubitOut(_) | NodeType::ClbitOut(_) => (), } @@ -1668,9 +1693,9 @@ def _format(operand): // return None if !inplace { - Some(dag) + Ok(Some(dag.into_py(py))) } else { - None + Ok(None) } } diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index 1ea268e56f2a..f726ba63da52 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -14,14 +14,14 @@ use crate::circuit_instruction::{ convert_py_to_operation_type, operation_type_to_py, CircuitInstruction, ExtraInstructionAttributes, }; +use crate::imports::QUANTUM_CIRCUIT; use crate::operations::{Operation, OperationType, Param}; use crate::TupleLikeArg; -use crate::imports::QUANTUM_CIRCUIT; use numpy::IntoPyArray; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PySequence, PyString, PyTuple}; -use rustworkx_core::petgraph::stable_graph::NodeIndex; use pyo3::{intern, IntoPy, PyObject, PyResult, ToPyObject}; +use rustworkx_core::petgraph::stable_graph::NodeIndex; use smallvec::smallvec; /// Parent class for DAGOpNode, DAGInNode, and DAGOutNode. @@ -106,7 +106,7 @@ impl DAGOpNode { op: OperationType, qargs: impl IntoIterator, cargs: impl IntoIterator, - params: SmallVec<[Param; 3]>, + params: smallvec::SmallVec<[Param; 3]>, extra_attrs: Option>, sort_key: Py, ) -> (Self, DAGNode) @@ -228,7 +228,7 @@ impl DAGOpNode { } None => qargs.str()?.into_any(), }; - let base = PyClassInitializer::from(DAGNode { _node_id: -1 }); + let base = PyClassInitializer::from(DAGNode { node: None }); let sub = base.add_subclass(DAGOpNode { instruction, sort_key: sort_key.unbind(), diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index e854b4c01867..9ad6272dd01a 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -14,19 +14,19 @@ pub mod bit_data; pub mod circuit_data; pub mod circuit_instruction; pub mod dag_circuit; +pub mod dag_node; +mod dot_utils; +mod error; pub mod gate_matrix; pub mod imports; +mod interner; pub mod operations; pub mod parameter_table; pub mod slice; pub mod util; -mod dag_node; -mod dot_utils; -mod error; -mod interner; use pyo3::prelude::*; -use pyo3::types::PySlice; +use pyo3::types::{PySequence, PySlice, PyTuple}; use pyo3::DowncastError; use std::ops::Deref; From ba104f7a9a9f404204314c1bc77a076453e8a4d0 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 15 Jul 2024 16:07:52 -0400 Subject: [PATCH 88/89] Clone interners during copy_empty_like. --- crates/circuit/src/dag_circuit.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index dc1ad3d592da..a19600fa00fa 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -1141,13 +1141,14 @@ def _format(operand): /// Returns: /// DAGCircuit: An empty copy of self. fn copy_empty_like(&self, py: Python) -> PyResult { - // TODO: clone interners! let mut target_dag = DAGCircuit::new(py)?; target_dag.name = self.name.as_ref().map(|n| n.clone_ref(py)); target_dag.global_phase = self.global_phase.clone_ref(py); target_dag.duration = self.duration.as_ref().map(|d| d.clone_ref(py)); target_dag.unit = self.unit.clone(); target_dag.metadata = self.metadata.as_ref().map(|m| m.clone_ref(py)); + target_dag.qargs_cache = self.qargs_cache.clone(); + target_dag.cargs_cache = self.cargs_cache.clone(); for bit in self.qubits.bits() { target_dag.add_qubit_unchecked(py, bit.bind(py))?; From 8bb71897b529a77b41bbb696246549d985a651e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 16 Jul 2024 15:47:00 +0200 Subject: [PATCH 89/89] Use HashSet in nonancestors and nondescendants --- crates/circuit/src/dag_circuit.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 3da8613bae6d..75190c44ba25 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -2953,7 +2953,7 @@ def _format(operand): /// Remove all of the non-ancestors operation nodes of node. fn remove_nonancestors_of(&mut self, node: &DAGNode) -> PyResult<()> { - let ancestors: Vec<_> = core_ancestors(&self.dag, node.node.unwrap()) + let ancestors: HashSet<_> = core_ancestors(&self.dag, node.node.unwrap()) .filter(|next| { next != &node.node.unwrap() && match self.dag.node_weight(*next) { @@ -2965,7 +2965,7 @@ def _format(operand): let non_ancestors: Vec<_> = self .dag .node_indices() - .filter(|node_id| ancestors.iter().find(|anc| *anc == node_id).is_none()) + .filter(|node_id| !ancestors.contains(node_id)) .collect(); for na in non_ancestors { self.dag.remove_node(na); @@ -2975,7 +2975,7 @@ def _format(operand): /// Remove all of the non-descendants operation nodes of node. fn remove_nondescendants_of(&mut self, node: &DAGNode) -> PyResult<()> { - let descendants: Vec<_> = core_descendants(&self.dag, node.node.unwrap()) + let descendants: HashSet<_> = core_descendants(&self.dag, node.node.unwrap()) .filter(|next| { next != &node.node.unwrap() && match self.dag.node_weight(*next) { @@ -2987,7 +2987,7 @@ def _format(operand): let non_descendants: Vec<_> = self .dag .node_indices() - .filter(|node_id| descendants.iter().find(|desc| *desc == node_id).is_none()) + .filter(|node_id| !descendants.contains(node_id)) .collect(); for nd in non_descendants { self.dag.remove_node(nd);
' % result + ret = f'
{title} ' + ret += f'
' ret += "
" return ret @@ -119,11 +119,7 @@ def diff_images(self): if os.path.exists(os.path.join(SWD, fullpath_reference)): ratio, diff_name = Results._similarity_ratio(fullpath_name, fullpath_reference) - title = "{} | {} | ratio: {}".format( - name, - self.data[name]["testname"], - ratio, - ) + title = f"{name} | {self.data[name]['testname']} | ratio: {ratio}" if ratio == 1: self.exact_match.append(fullpath_name) else: @@ -158,8 +154,9 @@ def _repr_html_(self): ) else: title = ( - 'Download this image to %s' - " and add/push to the repo