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 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 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/circuit_data.rs b/crates/circuit/src/circuit_data.rs index bf290b72d113..f9a03375e3d3 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -511,20 +511,7 @@ impl CircuitData { }); } } else { - // Clippy complains in some versions if you attempt to just feature-gate out the - // ref-cell setting line from the middle. - #[cfg(feature = "cache_pygates")] - { - res.data.extend(self.data.iter().map(|inst| { - let out = inst.clone(); - *out.py_op.borrow_mut() = None; - out - })); - } - #[cfg(not(feature = "cache_pygates"))] - { - res.data.extend(self.data.iter().cloned()); - } + res.data.extend(self.data.iter().cloned()); } Ok(res) } @@ -715,14 +702,15 @@ impl CircuitData { let inst = &self.data[index]; let qubits = self.qargs_interner.intern(inst.qubits); let clbits = self.cargs_interner.intern(inst.clbits); - CircuitInstruction::new( - py, - inst.op.clone(), - self.qubits.map_indices(qubits.value), - self.clbits.map_indices(clbits.value), - inst.params_view().iter().cloned().collect(), - inst.extra_attrs.clone(), - ) + CircuitInstruction { + operation: inst.op.clone(), + qubits: PyTuple::new_bound(py, self.qubits.map_indices(qubits.value)).unbind(), + clbits: PyTuple::new_bound(py, self.clbits.map_indices(clbits.value)).unbind(), + params: inst.params_view().iter().cloned().collect(), + extra_attrs: inst.extra_attrs.clone(), + #[cfg(feature = "cache_pygates")] + py_op: inst.py_op.clone(), + } .into_py(py) }; match index.with_len(self.data.len())? { diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index c436bdd297a2..698fb734f2f0 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -22,9 +22,7 @@ use pyo3::{intern, IntoPy, PyObject, PyResult}; use smallvec::SmallVec; -use crate::imports::{ - GATE, INSTRUCTION, OPERATION, SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, WARNINGS_WARN, -}; +use crate::imports::{GATE, INSTRUCTION, OPERATION, WARNINGS_WARN}; use crate::operations::{ Operation, OperationRef, Param, PyGate, PyInstruction, PyOperation, StandardGate, }; @@ -114,31 +112,6 @@ pub struct CircuitInstruction { } impl CircuitInstruction { - pub fn new( - py: Python, - operation: PackedOperation, - qubits: impl IntoIterator, - clbits: impl IntoIterator, - params: SmallVec<[Param; 3]>, - extra_attrs: Option>, - ) -> Self - where - T1: ToPyObject, - T2: ToPyObject, - U1: ExactSizeIterator, - U2: ExactSizeIterator, - { - CircuitInstruction { - operation, - qubits: PyTuple::new_bound(py, qubits).unbind(), - clbits: PyTuple::new_bound(py, clbits).unbind(), - params, - extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: RefCell::new(None), - } - } - /// View the operation in this `CircuitInstruction`. pub fn op(&self) -> OperationRef { self.operation.view() @@ -521,18 +494,17 @@ impl<'py> FromPyObject<'py> for OperationFromPython { .and_then(|standard| standard.extract::()) .ok() else { break 'standard }; - // 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. + // If the instruction is a controlled gate with a not-all-ones control state, it doesn't + // fit our definition of standard. We abuse the fact that we know our standard-gate + // mapping to avoid an `isinstance` check on `ControlledGate` - a standard gate has + // nonzero `num_ctrl_qubits` iff it is a `ControlledGate`. // - // 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 ob.getattr(intern!(py, "mutable"))?.is_truthy()? - && (ob.is_instance(SINGLETON_GATE.get_bound(py))? - || ob.is_instance(SINGLETON_CONTROLLED_GATE.get_bound(py))?) + // `ControlledGate` also has a `base_gate` attribute, and we don't track enough in Rust + // space to handle the case that that was mutated away from a standard gate. + if standard.num_ctrl_qubits() != 0 + && ((ob.getattr(intern!(py, "ctrl_state"))?.extract::()? + != (1 << standard.num_ctrl_qubits()) - 1) + || ob.getattr(intern!(py, "mutable"))?.extract()?) { break 'standard; } diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index 51ef09eea543..7b517bdab16a 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -240,7 +240,7 @@ impl DAGOpNode { self.instruction.extra_attrs = res.extra_attrs; #[cfg(feature = "cache_pygates")] { - *self.instruction.py_op.borrow_mut() = None; + *self.instruction.py_op.borrow_mut() = Some(op.into_py(op.py())); } Ok(()) } 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 5ab96d977aa4..95f4d25954b5 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -254,26 +254,33 @@ 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 ]; +static STANDARD_GATE_NUM_CTRL_QUBITS: [u32; STANDARD_GATE_SIZE] = [ + 0, 0, 0, 1, 1, 1, 2, 0, 0, 0, // 0-9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10-19 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 20-29 + 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, // 30-39 + 1, 1, 3, 3, 4, 0, 2, 0, 0, 0, // 40-49 + 0, 0, 0, // 50-52 +]; + static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "z", // 0 "y", // 1 @@ -317,13 +324,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 @@ -359,6 +366,10 @@ impl StandardGate { gate_class.call_bound(py, args, None) } } + + pub fn num_ctrl_qubits(&self) -> u32 { + STANDARD_GATE_NUM_CTRL_QUBITS[*self as usize] + } } #[pymethods] @@ -576,6 +587,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, @@ -600,8 +639,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()) @@ -612,8 +649,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, @@ -630,6 +666,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, + }, } } @@ -1177,6 +1221,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)]; @@ -1198,6 +1304,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)]; @@ -1230,6 +1365,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)]; @@ -1254,6 +1442,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)]; @@ -1278,6 +1569,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)]; @@ -1295,6 +1653,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( @@ -1329,10 +1706,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( @@ -1347,8 +1721,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)]; @@ -1433,6 +1805,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"), + ) + }), } } @@ -1455,7 +1937,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) @@ -1479,6 +1961,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)] // We bit-pack pointers to this, so having a known alignment even on 32-bit systems is good. 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, + )); + } + } } 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 cd4a61963823..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) @@ -1409,6 +1417,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 +1444,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 +1472,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 +1492,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 +1505,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 +1512,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/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 e2dcf8f86629..515ab393e6e0 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 @@ -4676,10 +4679,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, [control_qubit1, control_qubit2, target_qubit], () ) def rcccx( @@ -4702,13 +4703,10 @@ def rcccx( Returns: A handle to the instructions created. """ - from .library.standard_gates.x import RC3XGate - - return self.append( - RC3XGate(), + return self._append_standard_gate( + StandardGate.RC3XGate, [control_qubit1, control_qubit2, control_qubit3, target_qubit], - [], - copy=False, + (), ) def rx( @@ -5270,6 +5268,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, + [control_qubit, target_qubit], + [theta, phi, lam, gamma], + label=label, + ) + from .library.standard_gates.u import CUGate return self.append( @@ -5369,8 +5376,8 @@ 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, [control_qubit1, control_qubit2, target_qubit], @@ -5597,6 +5604,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, + [control_qubit1, control_qubit2, target_qubit], + (), + label=label, + ) + from .library.standard_gates.z import CCZGate return self.append( 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/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, 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/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() diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index f26ab987f4fd..9a693a337c5c 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -768,7 +768,7 @@ def test_mcxgraycode_gates_yield_explicit_gates(self, num_ctrl_qubits): qc = QuantumCircuit(num_ctrl_qubits + 1) 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]) + self.assertEqual(qc[0].operation.base_class, explicit[num_ctrl_qubits]) @data(3, 4, 5, 8) def test_mcx_gates(self, num_ctrl_qubits): @@ -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.""" diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index 74d6362ceb29..05022ec70141 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)