From 4867e8aae354cb52b50c4cabaa5869b98405ddfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:54:00 +0200 Subject: [PATCH] Add Rust representation for most controlled gates (#12659) * Add C3X (MCX), extend rust tests to multi-controlled gates. * Add macro to generate multi-controlled gates. Add CU, CU1, CU3, C3SX, C4X, CCZ. * Kill C4XGate * Finish adding gates, add circuit construction methods when possible. * Add import paths, fix drawer test. * Establish CGates with non-default control states as non-standard in circuit_instruction.rs. Add unit test. * Fix merge conflicts * Apply macro on missing gates * Add RCCX gate and RC3X (RCCCX) gate. * Make equivalence tests more explicit * Fix lint * Modify circuit methods for consistency * Fix default ctrl state for 3q+ gates, add test for CCZ * Apply comments from Matt's code review * Fix ctrl_state logic * Rename c3x to mcx? * Brackets didn't match explanation * Make sure controlled test doesn't use custom ControlledGate instances. * Rename c4x to mcx in Rust space. * Return PyResult rather than panic on error * Add suggestion from Matt's code review Co-authored-by: Matthew Treinish --------- Co-authored-by: John Lapeyre Co-authored-by: Matthew Treinish --- crates/circuit/src/circuit_instruction.rs | 37 +- crates/circuit/src/gate_matrix.rs | 455 +++++++++------- crates/circuit/src/imports.rs | 15 +- crates/circuit/src/operations.rs | 522 ++++++++++++++++++- crates/circuit/src/util.rs | 1 + qiskit/circuit/library/standard_gates/u.py | 2 + qiskit/circuit/library/standard_gates/u1.py | 2 + qiskit/circuit/library/standard_gates/u3.py | 2 + qiskit/circuit/library/standard_gates/x.py | 8 + qiskit/circuit/library/standard_gates/z.py | 2 + qiskit/circuit/quantumcircuit.py | 61 ++- test/python/circuit/test_rust_equivalence.py | 81 ++- 12 files changed, 928 insertions(+), 260 deletions(-) diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index ed1c358cbc5b..501105f9f17e 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -22,7 +22,7 @@ use pyo3::{intern, IntoPy, PyObject, PyResult}; use smallvec::{smallvec, SmallVec}; use crate::imports::{ - get_std_gate_class, populate_std_gate_map, GATE, INSTRUCTION, OPERATION, + get_std_gate_class, populate_std_gate_map, CONTROLLED_GATE, GATE, INSTRUCTION, OPERATION, SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, WARNINGS_WARN, }; use crate::interner::Index; @@ -884,24 +884,49 @@ pub fn convert_py_to_operation_type( Ok(stdgate) => stdgate.extract().ok().unwrap_or_default(), Err(_) => None, }; - // If the input instruction is a standard gate and a singleton instance + // If the input instruction is a standard gate and a singleton instance, // we should check for mutable state. A mutable instance should be treated // as a custom gate not a standard gate because it has custom properties. - // - // In the futuer we can revisit this when we've dropped `duration`, `unit`, + // Controlled gates with non-default control states are also considered + // custom gates even if a standard representation exists for the default + // control state. + + // In the future we can revisit this when we've dropped `duration`, `unit`, // and `condition` from the api as we should own the label in the // `CircuitInstruction`. The other piece here is for controlled gates there // is the control state, so for `SingletonControlledGates` we'll still need // this check. if standard.is_some() { let mutable: bool = py_op.getattr(py, intern!(py, "mutable"))?.extract(py)?; - if mutable + // The default ctrl_states are the all 1 state and None. + // These are the only cases where controlled gates can be standard. + let is_default_ctrl_state = || -> PyResult { + match py_op.getattr(py, intern!(py, "ctrl_state")) { + Ok(c_state) => match c_state.extract::>(py) { + Ok(c_state_int) => match c_state_int { + Some(c_int) => { + let qubits: u32 = + py_op.getattr(py, intern!(py, "num_qubits"))?.extract(py)?; + Ok(c_int == (2_i32.pow(qubits - 1) - 1)) + } + None => Ok(true), + }, + Err(_) => Ok(false), + }, + Err(_) => Ok(false), + } + }; + + if (mutable && (py_op_bound.is_instance(SINGLETON_GATE.get_bound(py))? - || py_op_bound.is_instance(SINGLETON_CONTROLLED_GATE.get_bound(py))?) + || py_op_bound.is_instance(SINGLETON_CONTROLLED_GATE.get_bound(py))?)) + || (py_op_bound.is_instance(CONTROLLED_GATE.get_bound(py))? + && !is_default_ctrl_state()?) { standard = None; } } + if let Some(op) = standard { let base_class = op_type.to_object(py); populate_std_gate_map(py, op, base_class); diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 46585ff6da6e..6f527af2e30f 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -10,59 +10,62 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use num_complex::Complex64; use std::f64::consts::FRAC_1_SQRT_2; use crate::util::{ - c64, GateArray0Q, GateArray1Q, GateArray2Q, GateArray3Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM, + c64, GateArray0Q, GateArray1Q, GateArray2Q, GateArray3Q, GateArray4Q, C_M_ONE, C_ONE, C_ZERO, + IM, M_IM, }; pub static ONE_QUBIT_IDENTITY: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_ONE]]; -#[inline] -pub fn r_gate(theta: f64, phi: f64) -> GateArray1Q { - let half_theta = theta / 2.; - let cost = c64(half_theta.cos(), 0.); - let sint = half_theta.sin(); - let cosphi = phi.cos(); - let sinphi = phi.sin(); - [ - [cost, c64(-sint * sinphi, -sint * cosphi)], - [c64(sint * sinphi, -sint * cosphi), cost], - ] +// Utility for generating static matrices for controlled gates with "n" control qubits. +// Assumptions: +// 1. the reference "gate-matrix" is a single-qubit gate matrix (2x2) +// 2. the first "n" qubits are controls and the last qubit is the target +macro_rules! make_n_controlled_gate { + ($gate_matrix:expr, $n_control_qubits:expr) => {{ + const DIM: usize = 2_usize.pow($n_control_qubits as u32 + 1_u32); + // DIM x DIM matrix of all zeros + let mut matrix: [[Complex64; DIM]; DIM] = [[C_ZERO; DIM]; DIM]; + // DIM x DIM diagonal matrix + { + let mut i = 0; + while i < DIM { + matrix[i][i] = C_ONE; + i += 1; + } + } + // Insert elements of gate_matrix in columns DIM/2-1 and DIM-1 + matrix[DIM / 2 - 1][DIM / 2 - 1] = $gate_matrix[0][0]; + matrix[DIM - 1][DIM - 1] = $gate_matrix[1][1]; + matrix[DIM / 2 - 1][DIM - 1] = $gate_matrix[0][1]; + matrix[DIM - 1][DIM / 2 - 1] = $gate_matrix[1][0]; + matrix + }}; } -#[inline] -pub fn rx_gate(theta: f64) -> GateArray1Q { - let half_theta = theta / 2.; - let cos = c64(half_theta.cos(), 0.); - let isin = c64(0., -half_theta.sin()); - [[cos, isin], [isin, cos]] -} +pub static X_GATE: GateArray1Q = [[C_ZERO, C_ONE], [C_ONE, C_ZERO]]; -#[inline] -pub fn ry_gate(theta: f64) -> GateArray1Q { - let half_theta = theta / 2.; - let cos = c64(half_theta.cos(), 0.); - let sin = c64(half_theta.sin(), 0.); - [[cos, -sin], [sin, cos]] -} +pub static Z_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_M_ONE]]; -#[inline] -pub fn rz_gate(theta: f64) -> GateArray1Q { - let ilam2 = c64(0., 0.5 * theta); - [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] -} +pub static Y_GATE: GateArray1Q = [[C_ZERO, M_IM], [IM, C_ZERO]]; pub static H_GATE: GateArray1Q = [ [c64(FRAC_1_SQRT_2, 0.), c64(FRAC_1_SQRT_2, 0.)], [c64(FRAC_1_SQRT_2, 0.), c64(-FRAC_1_SQRT_2, 0.)], ]; -pub static CX_GATE: GateArray2Q = [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ZERO, C_ONE], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, C_ONE, C_ZERO, C_ZERO], +pub static S_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, IM]]; + +pub static SDG_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, M_IM]]; + +pub static T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)]]; + +pub static TDG_GATE: GateArray1Q = [ + [C_ONE, C_ZERO], + [C_ZERO, c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], ]; pub static SX_GATE: GateArray1Q = [ @@ -75,52 +78,27 @@ pub static SXDG_GATE: GateArray1Q = [ [c64(0.5, 0.5), c64(0.5, -0.5)], ]; -pub static X_GATE: GateArray1Q = [[C_ZERO, C_ONE], [C_ONE, C_ZERO]]; +pub static CX_GATE: GateArray2Q = make_n_controlled_gate!(X_GATE, 1); -pub static Z_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_M_ONE]]; +pub static CZ_GATE: GateArray2Q = make_n_controlled_gate!(Z_GATE, 1); -pub static Y_GATE: GateArray1Q = [[C_ZERO, M_IM], [IM, C_ZERO]]; +pub static CY_GATE: GateArray2Q = make_n_controlled_gate!(Y_GATE, 1); -pub static CZ_GATE: GateArray2Q = [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, C_ONE, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, C_ZERO, C_ZERO, C_M_ONE], -]; +pub static CCX_GATE: GateArray3Q = make_n_controlled_gate!(X_GATE, 2); -pub static CY_GATE: GateArray2Q = [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ZERO, M_IM], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, IM, C_ZERO, C_ZERO], -]; +pub static CCZ_GATE: GateArray3Q = make_n_controlled_gate!(Z_GATE, 2); -pub static CCX_GATE: GateArray3Q = [ - [ - C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, - ], - [ - C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, - ], - [ - C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, - ], - [ - C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, - ], - [ - C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, - ], - [ - C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, - ], - [ - C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, - ], - [ - C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, - ], -]; +pub static C3X_GATE: GateArray4Q = make_n_controlled_gate!(X_GATE, 3); + +pub static C3SX_GATE: GateArray4Q = make_n_controlled_gate!(SX_GATE, 3); + +pub static CH_GATE: GateArray2Q = make_n_controlled_gate!(H_GATE, 1); + +pub static CS_GATE: GateArray2Q = make_n_controlled_gate!(S_GATE, 1); + +pub static CSDG_GATE: GateArray2Q = make_n_controlled_gate!(SDG_GATE, 1); + +pub static CSX_GATE: GateArray2Q = make_n_controlled_gate!(SX_GATE, 1); pub static ECR_GATE: GateArray2Q = [ [ @@ -162,55 +140,6 @@ pub static ISWAP_GATE: GateArray2Q = [ [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; -pub static S_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, IM]]; - -pub static SDG_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, M_IM]]; - -pub static T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)]]; - -pub static TDG_GATE: GateArray1Q = [ - [C_ONE, C_ZERO], - [C_ZERO, c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], -]; - -pub static CH_GATE: GateArray2Q = [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [ - C_ZERO, - c64(FRAC_1_SQRT_2, 0.), - C_ZERO, - c64(FRAC_1_SQRT_2, 0.), - ], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [ - C_ZERO, - c64(FRAC_1_SQRT_2, 0.), - C_ZERO, - c64(-FRAC_1_SQRT_2, 0.), - ], -]; - -pub static CS_GATE: GateArray2Q = [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, C_ONE, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, C_ZERO, C_ZERO, IM], -]; - -pub static CSDG_GATE: GateArray2Q = [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, C_ONE, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, C_ZERO, C_ZERO, M_IM], -]; - -pub static CSX_GATE: GateArray2Q = [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, c64(0.5, 0.5), C_ZERO, c64(0.5, -0.5)], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, c64(0.5, -0.5), C_ZERO, c64(0.5, 0.5)], -]; - pub static CSWAP_GATE: GateArray3Q = [ [ C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, @@ -245,42 +174,95 @@ pub static DCX_GATE: GateArray2Q = [ [C_ZERO, C_ZERO, C_ONE, C_ZERO], ]; -#[inline] -pub fn crx_gate(theta: f64) -> GateArray2Q { - let half_theta = theta / 2.; - let cos = c64(half_theta.cos(), 0.); - let isin = c64(0., half_theta.sin()); +pub static RCCX_GATE: GateArray3Q = [ [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, cos, C_ZERO, -isin], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, -isin, C_ZERO, cos], - ] -} - -#[inline] -pub fn cry_gate(theta: f64) -> GateArray2Q { - let half_theta = theta / 2.; - let cos = c64(half_theta.cos(), 0.); - let sin = c64(half_theta.sin(), 0.); + C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, cos, C_ZERO, -sin], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, sin, C_ZERO, cos], - ] -} + C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, M_IM], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_M_ONE, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, + ], + [C_ZERO, C_ZERO, C_ZERO, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO], +]; -#[inline] -pub fn crz_gate(theta: f64) -> GateArray2Q { - let i_half_theta = c64(0., theta / 2.); +pub static RC3X_GATE: GateArray4Q = [ [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, (-i_half_theta).exp(), C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, C_ZERO, C_ZERO, i_half_theta.exp()], - ] -} + C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + M_IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, + ], + [ + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_M_ONE, C_ZERO, C_ZERO, C_ZERO, + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + ], +]; #[inline] pub fn global_phase_gate(theta: f64) -> GateArray0Q { @@ -302,28 +284,6 @@ pub fn u_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { ] } -#[inline] -pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { - let cos = (theta / 2.).cos(); - let sin = (theta / 2.).sin(); - [ - [ - c64(cos, 0.), - C_ZERO, - C_ZERO, - c64(0., -sin) * c64(0., -beta).exp(), - ], - [C_ZERO, C_ONE, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [ - c64(0., -sin) * c64(0., beta).exp(), - C_ZERO, - C_ZERO, - c64(cos, 0.), - ], - ] -} - #[inline] pub fn u1_gate(lam: f64) -> GateArray1Q { [[C_ONE, C_ZERO], [C_ZERO, c64(0., lam).exp()]] @@ -354,37 +314,102 @@ pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { } #[inline] -pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { - let cos = (theta / 2.).cos(); - let sin = (theta / 2.).sin(); +pub fn cp_gate(lam: f64) -> GateArray2Q { + [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, c64(0., lam).exp()], + ] +} + +#[inline] +pub fn cu_gate(theta: f64, phi: f64, lam: f64, gamma: f64) -> GateArray2Q { + let cos_theta = (theta / 2.).cos(); + let sin_theta = (theta / 2.).sin(); [ [C_ONE, C_ZERO, C_ZERO, C_ZERO], [ C_ZERO, - c64(cos, 0.), - c64(0., -sin) * c64(0., -beta).exp(), + c64(0., gamma).exp() * cos_theta, C_ZERO, + c64(0., gamma + phi).exp() * (-1.) * sin_theta, ], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], [ C_ZERO, - c64(0., -sin) * c64(0., beta).exp(), - c64(cos, 0.), + c64(0., gamma + lam).exp() * sin_theta, C_ZERO, + c64(0., gamma + phi + lam).exp() * cos_theta, ], - [C_ZERO, C_ZERO, C_ZERO, C_ONE], ] } #[inline] -pub fn cp_gate(lam: f64) -> GateArray2Q { +pub fn cu1_gate(lam: f64) -> GateArray2Q { + let gate_matrix = u1_gate(lam); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn cu3_gate(theta: f64, phi: f64, lam: f64) -> GateArray2Q { + let gate_matrix = u3_gate(theta, phi, lam); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn r_gate(theta: f64, phi: f64) -> GateArray1Q { + let half_theta = theta / 2.; + let cost = c64(half_theta.cos(), 0.); + let sint = half_theta.sin(); + let cosphi = phi.cos(); + let sinphi = phi.sin(); [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, C_ONE, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], - [C_ZERO, C_ZERO, C_ZERO, c64(0., lam).exp()], + [cost, c64(-sint * sinphi, -sint * cosphi)], + [c64(sint * sinphi, -sint * cosphi), cost], ] } +#[inline] +pub fn rx_gate(theta: f64) -> GateArray1Q { + let half_theta = theta / 2.; + let cos = c64(half_theta.cos(), 0.); + let isin = c64(0., -half_theta.sin()); + [[cos, isin], [isin, cos]] +} + +#[inline] +pub fn ry_gate(theta: f64) -> GateArray1Q { + let half_theta = theta / 2.; + let cos = c64(half_theta.cos(), 0.); + let sin = c64(half_theta.sin(), 0.); + [[cos, -sin], [sin, cos]] +} + +#[inline] +pub fn rz_gate(theta: f64) -> GateArray1Q { + let ilam2 = c64(0., 0.5 * theta); + [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] +} + +#[inline] +pub fn crx_gate(theta: f64) -> GateArray2Q { + let gate_matrix = rx_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn cry_gate(theta: f64) -> GateArray2Q { + let gate_matrix = ry_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn crz_gate(theta: f64) -> GateArray2Q { + let gate_matrix = rz_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + #[inline] pub fn rxx_gate(theta: f64) -> GateArray2Q { let (sint, cost) = (theta / 2.0).sin_cos(); @@ -440,3 +465,47 @@ pub fn rzx_gate(theta: f64) -> GateArray2Q { [C_ZERO, csin, C_ZERO, ccos], ] } + +#[inline] +pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [ + c64(cos, 0.), + C_ZERO, + C_ZERO, + c64(0., -sin) * c64(0., -beta).exp(), + ], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [ + c64(0., -sin) * c64(0., beta).exp(), + C_ZERO, + C_ZERO, + c64(cos, 0.), + ], + ] +} + +#[inline] +pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [ + C_ZERO, + c64(cos, 0.), + c64(0., -sin) * c64(0., -beta).exp(), + C_ZERO, + ], + [ + C_ZERO, + c64(0., -sin) * c64(0., beta).exp(), + c64(cos, 0.), + C_ZERO, + ], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], + ] +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 554f553d9427..dfefb0a348f8 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -71,8 +71,9 @@ pub static SINGLETON_GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.singleton", "SingletonGate"); pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.singleton", "SingletonControlledGate"); +pub static CONTROLLED_GATE: ImportOnceCell = + ImportOnceCell::new("qiskit.circuit", "ControlledGate"); pub static DEEPCOPY: ImportOnceCell = ImportOnceCell::new("copy", "deepcopy"); - pub static WARNINGS_WARN: ImportOnceCell = ImportOnceCell::new("warnings", "warn"); /// A mapping from the enum variant in crate::operations::StandardGate to the python @@ -178,19 +179,19 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // CU3Gate = 41 ["qiskit.circuit.library.standard_gates.u3", "CU3Gate"], // C3XGate = 42 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "C3XGate"], // C3SXGate = 43 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "C3SXGate"], // C4XGate = 44 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "C4XGate"], // DCXGate = 45 ["qiskit.circuit.library.standard_gates.dcx", "DCXGate"], // CCZGate = 46 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.z", "CCZGate"], // RCCXGate = 47 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "RCCXGate"], // RC3XGate = 48 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.x", "RC3XGate"], // RXXGate = 49 ["qiskit.circuit.library.standard_gates.rxx", "RXXGate"], // RYYGate = 50 diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 3bfef81d29ce..fcbfddb72181 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -248,23 +248,21 @@ impl ToPyObject for StandardGate { } } -// TODO: replace all 34s (placeholders) with actual number static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, // 0-9 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, // 10-19 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, // 20-29 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39 - 2, 2, 34, 34, 34, 2, 34, 34, 34, 2, // 40-49 + 2, 2, 4, 4, 5, 2, 3, 3, 4, 2, // 40-49 2, 2, 2, // 50-52 ]; -// TODO: replace all 34s (placeholders) with actual number static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, // 0-9 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, // 10-19 0, 0, 0, 0, 2, 2, 1, 2, 3, 1, // 20-29 - 1, 1, 2, 0, 1, 0, 0, 0, 0, 3, // 30-39 - 1, 3, 34, 34, 34, 0, 34, 34, 34, 1, // 40-49 + 1, 1, 2, 0, 1, 0, 0, 0, 0, 4, // 30-39 + 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, // 40-49 1, 1, 1, // 50-52 ]; @@ -311,13 +309,13 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "cu", // 39 "cu1", // 40 "cu3", // 41 - "c3x", // 42 + "mcx", // 42 ("c3x") "c3sx", // 43 - "c4x", // 44 + "mcx", // 44 ("c4x") "dcx", // 45 "ccz", // 46 "rccx", // 47 - "rc3x", // 48 + "rcccx", // 48 ("rc3x") "rxx", // 49 "ryy", // 50 "rzz", // 51 @@ -539,6 +537,34 @@ impl Operation for StandardGate { } _ => None, }, + Self::CUGate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam), Param::Float(gamma)] => { + Some(aview2(&gate_matrix::cu_gate(*theta, *phi, *lam, *gamma)).to_owned()) + } + _ => None, + }, + Self::CU1Gate => match params[0] { + Param::Float(lam) => Some(aview2(&gate_matrix::cu1_gate(lam)).to_owned()), + _ => None, + }, + Self::CU3Gate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::cu3_gate(*theta, *phi, *lam)).to_owned()) + } + _ => None, + }, + Self::C3XGate => match params { + [] => Some(aview2(&gate_matrix::C3X_GATE).to_owned()), + _ => None, + }, + Self::C3SXGate => match params { + [] => Some(aview2(&gate_matrix::C3SX_GATE).to_owned()), + _ => None, + }, + Self::CCZGate => match params { + [] => Some(aview2(&gate_matrix::CCZ_GATE).to_owned()), + _ => None, + }, Self::CHGate => match params { [] => Some(aview2(&gate_matrix::CH_GATE).to_owned()), _ => None, @@ -563,8 +589,6 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::CSWAP_GATE).to_owned()), _ => None, }, - Self::CUGate | Self::CU1Gate | Self::CU3Gate => todo!(), - Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), Self::RGate => match params { [Param::Float(theta), Param::Float(phi)] => { Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned()) @@ -575,8 +599,7 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::DCX_GATE).to_owned()), _ => None, }, - Self::CCZGate => todo!(), - Self::RCCXGate | Self::RC3XGate => todo!(), + Self::C4XGate => todo!(), Self::RXXGate => match params[0] { Param::Float(theta) => Some(aview2(&gate_matrix::rxx_gate(theta)).to_owned()), _ => None, @@ -593,6 +616,14 @@ impl Operation for StandardGate { Param::Float(theta) => Some(aview2(&gate_matrix::rzx_gate(theta)).to_owned()), _ => None, }, + Self::RCCXGate => match params { + [] => Some(aview2(&gate_matrix::RCCX_GATE).to_owned()), + _ => None, + }, + Self::RC3XGate => match params { + [] => Some(aview2(&gate_matrix::RC3X_GATE).to_owned()), + _ => None, + }, } } @@ -1140,6 +1171,68 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CUGate => Python::with_gil(|py| -> Option { + let param_second_p = radd_param( + multiply_param(¶ms[2], 0.5, py), + multiply_param(¶ms[1], 0.5, py), + py, + ); + let param_third_p = radd_param( + multiply_param(¶ms[2], 0.5, py), + multiply_param(¶ms[1], -0.5, py), + py, + ); + let param_first_u = radd_param( + multiply_param(¶ms[1], -0.5, py), + multiply_param(¶ms[2], -0.5, py), + py, + ); + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::PhaseGate, + smallvec![params[3].clone()], + smallvec![Qubit(0)], + ), + ( + Self::PhaseGate, + smallvec![param_second_p], + smallvec![Qubit(0)], + ), + ( + Self::PhaseGate, + smallvec![param_third_p], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::UGate, + smallvec![ + multiply_param(¶ms[0], -0.5, py), + Param::Float(0.), + param_first_u + ], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::UGate, + smallvec![ + multiply_param(¶ms[0], 0.5, py), + params[1].clone(), + Param::Float(0.) + ], + smallvec![Qubit(1)], + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CHGate => Python::with_gil(|py| -> Option { let q1 = smallvec![Qubit(1)]; let q0_1 = smallvec![Qubit(0), Qubit(1)]; @@ -1161,6 +1254,35 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CU1Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::U1Gate, + smallvec![multiply_param(¶ms[0], 0.5, py)], + smallvec![Qubit(0)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::U1Gate, + smallvec![multiply_param(¶ms[0], -0.5, py)], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::U1Gate, + smallvec![multiply_param(¶ms[0], 0.5, py)], + smallvec![Qubit(1)], + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CPhaseGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; @@ -1193,6 +1315,59 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CU3Gate => Python::with_gil(|py| -> Option { + let param_first_u1 = radd_param( + multiply_param(¶ms[2], 0.5, py), + multiply_param(¶ms[1], 0.5, py), + py, + ); + let param_second_u1 = radd_param( + multiply_param(¶ms[2], 0.5, py), + multiply_param(¶ms[1], -0.5, py), + py, + ); + let param_first_u3 = radd_param( + multiply_param(¶ms[1], -0.5, py), + multiply_param(¶ms[2], -0.5, py), + py, + ); + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::U1Gate, smallvec![param_first_u1], smallvec![Qubit(0)]), + ( + Self::U1Gate, + smallvec![param_second_u1], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::U3Gate, + smallvec![ + multiply_param(¶ms[0], -0.5, py), + Param::Float(0.), + param_first_u3 + ], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::U3Gate, + smallvec![ + multiply_param(¶ms[0], 0.5, py), + params[1].clone(), + Param::Float(0.) + ], + smallvec![Qubit(1)], + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CSGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; @@ -1217,6 +1392,109 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::C3XGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 4, + [ + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(0)], + ), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(1)], + ), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(2)], + ), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(2)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(2)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(2)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CSdgGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; @@ -1241,6 +1519,73 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::C3SXGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 4, + [ + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(0), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(1), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(1), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(2), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(2), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(-PI / 8.)], + smallvec![Qubit(2), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ( + Self::CU1Gate, + smallvec![Param::Float(PI / 8.)], + smallvec![Qubit(2), Qubit(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(3)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CSXGate => Python::with_gil(|py| -> Option { let q1 = smallvec![Qubit(1)]; let q0_1 = smallvec![Qubit(0), Qubit(1)]; @@ -1258,6 +1603,25 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CCZGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + (Self::HGate, smallvec![], smallvec![Qubit(2)]), + ( + Self::CCXGate, + smallvec![], + smallvec![Qubit(0), Qubit(1), Qubit(2)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(2)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CSwapGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1292,10 +1656,7 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CUGate => todo!(), - Self::CU1Gate => todo!(), - Self::CU3Gate => todo!(), - Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), + Self::C4XGate => todo!(), Self::DCXGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1310,8 +1671,6 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CCZGate => todo!(), - Self::RCCXGate | Self::RC3XGate => todo!(), Self::RXXGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; @@ -1396,6 +1755,116 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::RCCXGate => Python::with_gil(|py| -> Option { + let q2 = smallvec![Qubit(2)]; + let q0_2 = smallvec![Qubit(0), Qubit(2)]; + let q1_2 = smallvec![Qubit(1), Qubit(2)]; + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + q2.clone(), + ), + (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q1_2.clone()), + (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q0_2), + (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q1_2), + (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + q2, + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::RC3XGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 4, + [ + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![Qubit(3)], + ), + ( + Self::U1Gate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(3)], + ), + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(3)], + ), + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![Qubit(3)], + ), + ( + Self::U1Gate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(3)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), + ( + Self::U1Gate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(3)], + ), + ( + Self::U2Gate, + smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![Qubit(3)], + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), } } @@ -1418,7 +1887,7 @@ fn clone_param(param: &Param, py: Python) -> Param { fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { match param { - Param::Float(theta) => Param::Float(*theta * mult), + Param::Float(theta) => Param::Float(theta * mult), Param::ParameterExpression(theta) => Param::ParameterExpression( theta .clone_ref(py) @@ -1442,6 +1911,21 @@ fn add_param(param: &Param, summand: f64, py: Python) -> Param { } } +fn radd_param(param1: Param, param2: Param, py: Python) -> Param { + match [param1, param2] { + [Param::Float(theta), Param::Float(lambda)] => Param::Float(theta + lambda), + [Param::ParameterExpression(theta), Param::ParameterExpression(lambda)] => { + Param::ParameterExpression( + theta + .clone_ref(py) + .call_method1(py, intern!(py, "__radd__"), (lambda,)) + .expect("Parameter expression addition failed"), + ) + } + _ => unreachable!(), + } +} + /// This class is used to wrap a Python side Instruction that is not in the standard library #[derive(Clone, Debug)] #[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")] diff --git a/crates/circuit/src/util.rs b/crates/circuit/src/util.rs index 11562b0a48cd..1b6402720c46 100644 --- a/crates/circuit/src/util.rs +++ b/crates/circuit/src/util.rs @@ -39,6 +39,7 @@ pub type GateArray0Q = [[Complex64; 1]; 1]; pub type GateArray1Q = [[Complex64; 2]; 2]; pub type GateArray2Q = [[Complex64; 4]; 4]; pub type GateArray3Q = [[Complex64; 8]; 8]; +pub type GateArray4Q = [[Complex64; 16]; 16]; // Use prefix `C_` to distinguish from real, for example pub const C_ZERO: Complex64 = c64(0., 0.); diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py index 07684097f8cc..7f1d32eb914c 100644 --- a/qiskit/circuit/library/standard_gates/u.py +++ b/qiskit/circuit/library/standard_gates/u.py @@ -262,6 +262,8 @@ class CUGate(ControlledGate): \end{pmatrix} """ + _standard_gate = StandardGate.CUGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index f141146b72dc..e62a132670ff 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -208,6 +208,8 @@ class CU1Gate(ControlledGate): phase difference. """ + _standard_gate = StandardGate.CU1Gate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index f191609ea8f1..80581bf55a5d 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -230,6 +230,8 @@ class CU3Gate(ControlledGate): \end{pmatrix} """ + _standard_gate = StandardGate.CU3Gate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index a08e4a55a960..5605038680d1 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -522,6 +522,8 @@ class RCCXGate(SingletonGate): with the :meth:`~qiskit.circuit.QuantumCircuit.rccx` method. """ + _standard_gate = StandardGate.RCCXGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create a new simplified CCX gate.""" super().__init__("rccx", 3, [], label=label, duration=duration, unit=unit) @@ -578,6 +580,8 @@ class C3SXGate(SingletonControlledGate): [1] Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf """ + _standard_gate = StandardGate.C3SXGate + def __init__( self, label: Optional[str] = None, @@ -682,6 +686,8 @@ class C3XGate(SingletonControlledGate): This implementation uses :math:`\sqrt{T}` and 14 CNOT gates. """ + _standard_gate = StandardGate.C3XGate + def __init__( self, label: Optional[str] = None, @@ -869,6 +875,8 @@ class RC3XGate(SingletonGate): with the :meth:`~qiskit.circuit.QuantumCircuit.rcccx` method. """ + _standard_gate = StandardGate.RC3XGate + def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"): """Create a new RC3X gate.""" super().__init__("rcccx", 4, [], label=label, duration=duration, unit=unit) diff --git a/qiskit/circuit/library/standard_gates/z.py b/qiskit/circuit/library/standard_gates/z.py index 19e4382cd846..4b2364178a94 100644 --- a/qiskit/circuit/library/standard_gates/z.py +++ b/qiskit/circuit/library/standard_gates/z.py @@ -286,6 +286,8 @@ class CCZGate(SingletonControlledGate): the target qubit if the control qubits are in the :math:`|11\rangle` state. """ + _standard_gate = StandardGate.CCZGate + def __init__( self, label: Optional[str] = None, diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index e2acf9dc2026..9d7cdfa2fb50 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -4545,7 +4545,7 @@ def id(self, qubit: QubitSpecifier) -> InstructionSet: # pylint: disable=invali Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.IGate, None, qargs=[qubit]) + return self._append_standard_gate(StandardGate.IGate, [], qargs=[qubit]) def ms(self, theta: ParameterValueType, qubits: Sequence[QubitSpecifier]) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.MSGate`. @@ -4711,10 +4711,8 @@ def rccx( Returns: A handle to the instructions created. """ - from .library.standard_gates.x import RCCXGate - - return self.append( - RCCXGate(), [control_qubit1, control_qubit2, target_qubit], [], copy=False + return self._append_standard_gate( + StandardGate.RCCXGate, [], qargs=[control_qubit1, control_qubit2, target_qubit] ) def rcccx( @@ -4737,13 +4735,10 @@ def rcccx( Returns: A handle to the instructions created. """ - from .library.standard_gates.x import RC3XGate - - return self.append( - RC3XGate(), - [control_qubit1, control_qubit2, control_qubit3, target_qubit], + return self._append_standard_gate( + StandardGate.RC3XGate, [], - copy=False, + qargs=[control_qubit1, control_qubit2, control_qubit3, target_qubit], ) def rx( @@ -4761,7 +4756,7 @@ def rx( Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.RXGate, [theta], [qubit], None, label=label) + return self._append_standard_gate(StandardGate.RXGate, [theta], [qubit], label=label) def crx( self, @@ -4790,7 +4785,7 @@ def crx( # if the control state is |1> use the fast Rust version of the gate if ctrl_state is None or ctrl_state in ["1", 1]: return self._append_standard_gate( - StandardGate.CRXGate, [theta], [control_qubit, target_qubit], None, label=label + StandardGate.CRXGate, [theta], [control_qubit, target_qubit], label=label ) from .library.standard_gates.rx import CRXGate @@ -4834,7 +4829,7 @@ def ry( Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.RYGate, [theta], [qubit], None, label=label) + return self._append_standard_gate(StandardGate.RYGate, [theta], [qubit], label=label) def cry( self, @@ -4863,7 +4858,7 @@ def cry( # if the control state is |1> use the fast Rust version of the gate if ctrl_state is None or ctrl_state in ["1", 1]: return self._append_standard_gate( - StandardGate.CRYGate, [theta], [control_qubit, target_qubit], None, label=label + StandardGate.CRYGate, [theta], [control_qubit, target_qubit], label=label ) from .library.standard_gates.ry import CRYGate @@ -4904,7 +4899,7 @@ def rz(self, phi: ParameterValueType, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.RZGate, [phi], [qubit], None) + return self._append_standard_gate(StandardGate.RZGate, [phi], [qubit]) def crz( self, @@ -4933,7 +4928,7 @@ def crz( # if the control state is |1> use the fast Rust version of the gate if ctrl_state is None or ctrl_state in ["1", 1]: return self._append_standard_gate( - StandardGate.CRZGate, [theta], [control_qubit, target_qubit], None, label=label + StandardGate.CRZGate, [theta], [control_qubit, target_qubit], label=label ) from .library.standard_gates.rz import CRZGate @@ -5305,6 +5300,15 @@ def cu( Returns: A handle to the instructions created. """ + # if the control state is |1> use the fast Rust version of the gate + if ctrl_state is None or ctrl_state in ["1", 1]: + return self._append_standard_gate( + StandardGate.CUGate, + [theta, phi, lam, gamma], + qargs=[control_qubit, target_qubit], + label=label, + ) + from .library.standard_gates.u import CUGate return self.append( @@ -5405,13 +5409,10 @@ def ccx( Returns: A handle to the instructions created. """ - # if the control state is |1> use the fast Rust version of the gate - if ctrl_state is None or ctrl_state in ["1", 1]: + # if the control state is |11> use the fast Rust version of the gate + if ctrl_state is None or ctrl_state in ["11", 3]: return self._append_standard_gate( - StandardGate.CCXGate, - [], - qargs=[control_qubit1, control_qubit2, target_qubit], - cargs=None, + StandardGate.CCXGate, [], qargs=[control_qubit1, control_qubit2, target_qubit] ) from .library.standard_gates.x import CCXGate @@ -5518,7 +5519,7 @@ def y(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.YGate, None, qargs=[qubit]) + return self._append_standard_gate(StandardGate.YGate, [], qargs=[qubit]) def cy( self, @@ -5548,7 +5549,6 @@ def cy( StandardGate.CYGate, [], qargs=[control_qubit, target_qubit], - cargs=None, label=label, ) @@ -5572,7 +5572,7 @@ def z(self, qubit: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - return self._append_standard_gate(StandardGate.ZGate, None, qargs=[qubit]) + return self._append_standard_gate(StandardGate.ZGate, [], qargs=[qubit]) def cz( self, @@ -5635,6 +5635,15 @@ def ccz( Returns: A handle to the instructions created. """ + # if the control state is |11> use the fast Rust version of the gate + if ctrl_state is None or ctrl_state in ["11", 3]: + return self._append_standard_gate( + StandardGate.CCZGate, + [], + qargs=[control_qubit1, control_qubit2, target_qubit], + label=label, + ) + from .library.standard_gates.z import CCZGate return self.append( diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index 6c0cc977e58a..c344ff309964 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -12,6 +12,7 @@ """Rust gate definition tests""" +from functools import partial from math import pi from test import QiskitTestCase @@ -19,10 +20,13 @@ import numpy as np from qiskit.circuit import QuantumCircuit, CircuitInstruction +from qiskit.circuit.library.standard_gates import C3XGate, CU1Gate, CZGate, CCZGate from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping +from qiskit.quantum_info import Operator SKIP_LIST = {"rx", "ry", "ecr"} -CUSTOM_MAPPING = {"x", "rz"} +CUSTOM_NAME_MAPPING = {"mcx": C3XGate()} +MATRIX_SKIP_LIST = {"c3sx"} class TestRustGateEquivalence(QiskitTestCase): @@ -31,8 +35,9 @@ class TestRustGateEquivalence(QiskitTestCase): def setUp(self): super().setUp() self.standard_gates = get_standard_gate_name_mapping() + self.standard_gates.update(CUSTOM_NAME_MAPPING) # Pre-warm gate mapping cache, this is needed so rust -> py conversion is done - qc = QuantumCircuit(3) + qc = QuantumCircuit(5) for gate in self.standard_gates.values(): if getattr(gate, "_standard_gate", None): if gate.params: @@ -73,10 +78,12 @@ def test_definitions(self): self.assertIsNone(rs_def) else: rs_def = QuantumCircuit._from_circuit_data(rs_def) - for rs_inst, py_inst in zip(rs_def._data, py_def._data): - # Rust uses U but python still uses U3 and u2 - if rs_inst.operation.name == "u": + # In the following cases, Rust uses U but python still uses U3 and U2 + if ( + name in {"x", "y", "h", "r", "p", "u2", "u3", "cu", "crx"} + and rs_inst.operation.name == "u" + ): if py_inst.operation.name == "u3": self.assertEqual(rs_inst.operation.params, py_inst.operation.params) elif py_inst.operation.name == "u2": @@ -93,8 +100,11 @@ def test_definitions(self): [py_def.find_bit(x).index for x in py_inst.qubits], [rs_def.find_bit(x).index for x in rs_inst.qubits], ) - # Rust uses p but python still uses u1/u3 in some cases - elif rs_inst.operation.name == "p" and not name in ["cp", "cs", "csdg"]: + # In the following cases, Rust uses P but python still uses U1 and U3 + elif ( + name in {"z", "s", "sdg", "t", "tdg", "rz", "u1", "crx"} + and rs_inst.operation.name == "p" + ): if py_inst.operation.name == "u1": self.assertEqual(py_inst.operation.name, "u1") self.assertEqual(rs_inst.operation.params, py_inst.operation.params) @@ -111,8 +121,8 @@ def test_definitions(self): [py_def.find_bit(x).index for x in py_inst.qubits], [rs_def.find_bit(x).index for x in rs_inst.qubits], ) - # Rust uses cp but python still uses cu1 in some cases - elif rs_inst.operation.name == "cp": + # In the following cases, Rust uses CP but python still uses CU1 + elif name in {"csx"} and rs_inst.operation.name == "cp": self.assertEqual(py_inst.operation.name, "cu1") self.assertEqual(rs_inst.operation.params, py_inst.operation.params) self.assertEqual( @@ -131,6 +141,9 @@ def test_matrix(self): """Test matrices are the same in rust space.""" for name, gate_class in self.standard_gates.items(): standard_gate = getattr(gate_class, "_standard_gate", None) + if name in MATRIX_SKIP_LIST: + # to_matrix not defined for type + continue if standard_gate is None: # gate is not in rust yet continue @@ -148,6 +161,9 @@ def test_name(self): if standard_gate is None: # gate is not in rust yet continue + if gate_class.name == "mcx": + # ambiguous gate name + continue with self.subTest(name=name): self.assertEqual(gate_class.name, standard_gate.name) @@ -175,3 +191,50 @@ def test_num_params(self): self.assertEqual( len(gate_class.params), standard_gate.num_params, msg=f"{name} not equal" ) + + def test_non_default_controls(self): + """Test that controlled gates with a non-default ctrl_state + are not using the standard rust representation.""" + # CZ and CU1 are diagonal matrices with one non-1 term + # in the diagonal (see op_terms) + gate_classes = [CZGate, partial(CU1Gate, 0.1)] + op_terms = [-1, 0.99500417 + 0.09983342j] + + for gate_cls, term in zip(gate_classes, op_terms): + with self.subTest(name="2q gates"): + default_op = np.diag([1, 1, 1, term]) + non_default_op = np.diag([1, 1, term, 1]) + state_out_map = { + 1: default_op, + "1": default_op, + None: default_op, + 0: non_default_op, + "0": non_default_op, + } + for state, op in state_out_map.items(): + circuit = QuantumCircuit(2) + gate = gate_cls(ctrl_state=state) + circuit.append(gate, [0, 1]) + self.assertIsNotNone(getattr(gate, "_standard_gate", None)) + np.testing.assert_almost_equal(circuit.data[0].operation.to_matrix(), op) + + with self.subTest(name="3q gate"): + default_op = np.diag([1, 1, 1, 1, 1, 1, 1, -1]) + non_default_op_0 = np.diag([1, 1, 1, 1, -1, 1, 1, 1]) + non_default_op_1 = np.diag([1, 1, 1, 1, 1, -1, 1, 1]) + non_default_op_2 = np.diag([1, 1, 1, 1, 1, 1, -1, 1]) + state_out_map = { + 3: default_op, + "11": default_op, + None: default_op, + 0: non_default_op_0, + 1: non_default_op_1, + "01": non_default_op_1, + "10": non_default_op_2, + } + for state, op in state_out_map.items(): + circuit = QuantumCircuit(3) + gate = CCZGate(ctrl_state=state) + circuit.append(gate, [0, 1, 2]) + self.assertIsNotNone(getattr(gate, "_standard_gate", None)) + np.testing.assert_almost_equal(Operator(circuit.data[0].operation).to_matrix(), op)