From de6c6eb2f944c58ecf2258801e921e0846d41d89 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 24 Jun 2024 10:02:31 +0200 Subject: [PATCH 1/3] Follow up on #12327: circuit construction in Rust (#12605) * Follow up on #12327 also port circuit construction to rust and add a reno * move _get_ordered_swap to Rust only * drop redundant Ok(expect()) * proper synthesis structure --- crates/accelerate/src/lib.rs | 2 +- crates/accelerate/src/synthesis/mod.rs | 22 ++++++ .../src/synthesis/permutation/mod.rs | 68 +++++++++++++++++++ .../permutation/utils.rs} | 58 ++++------------ crates/pyext/src/lib.rs | 10 +-- qiskit/__init__.py | 2 +- .../library/generalized_gates/permutation.py | 17 +++-- .../synthesis/permutation/permutation_full.py | 14 +--- .../permutation/permutation_utils.py | 3 +- .../oxidize-permbasic-be27578187ac472f.yaml | 4 ++ .../synthesis/test_permutation_synthesis.py | 14 ---- 11 files changed, 124 insertions(+), 90 deletions(-) create mode 100644 crates/accelerate/src/synthesis/mod.rs create mode 100644 crates/accelerate/src/synthesis/permutation/mod.rs rename crates/accelerate/src/{permutation.rs => synthesis/permutation/utils.rs} (66%) create mode 100644 releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 3924c1de4092..dcfbdc9f1878 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -23,12 +23,12 @@ pub mod isometry; pub mod nlayout; pub mod optimize_1q_gates; pub mod pauli_exp_val; -pub mod permutation; pub mod results; pub mod sabre; pub mod sampled_exp_val; pub mod sparse_pauli_op; pub mod stochastic_swap; +pub mod synthesis; pub mod two_qubit_decompose; pub mod uc_gate; pub mod utils; diff --git a/crates/accelerate/src/synthesis/mod.rs b/crates/accelerate/src/synthesis/mod.rs new file mode 100644 index 000000000000..f1a720459211 --- /dev/null +++ b/crates/accelerate/src/synthesis/mod.rs @@ -0,0 +1,22 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +mod permutation; + +use pyo3::prelude::*; +use pyo3::wrap_pymodule; + +#[pymodule] +pub fn synthesis(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pymodule!(permutation::permutation))?; + Ok(()) +} diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs new file mode 100644 index 000000000000..bf0ff97848f2 --- /dev/null +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -0,0 +1,68 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use numpy::PyArrayLike1; +use smallvec::smallvec; + +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::operations::{Param, StandardGate}; +use qiskit_circuit::Qubit; + +mod utils; + +/// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _validate_permutation(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + utils::validate_permutation(&view)?; + Ok(py.None()) +} + +/// Finds inverse of a permutation pattern. +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + let inverse_i64: Vec = utils::invert(&view).iter().map(|&x| x as i64).collect(); + Ok(inverse_i64.to_object(py)) +} + +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + let num_qubits = view.len(); + CircuitData::from_standard_gates( + py, + num_qubits as u32, + utils::get_ordered_swap(&view).iter().map(|(i, j)| { + ( + StandardGate::SwapGate, + smallvec![], + smallvec![Qubit(*i as u32), Qubit(*j as u32)], + ) + }), + Param::Float(0.0), + ) +} + +#[pymodule] +pub fn permutation(m: &Bound) -> PyResult<()> { + m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; + m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; + m.add_function(wrap_pyfunction!(_synth_permutation_basic, m)?)?; + Ok(()) +} diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/synthesis/permutation/utils.rs similarity index 66% rename from crates/accelerate/src/permutation.rs rename to crates/accelerate/src/synthesis/permutation/utils.rs index 31ba433ddd30..a78088bfbfa9 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/synthesis/permutation/utils.rs @@ -11,12 +11,11 @@ // that they have been altered from the originals. use ndarray::{Array1, ArrayView1}; -use numpy::PyArrayLike1; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use std::vec::Vec; -fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { +pub fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { let n = pattern.len(); let mut seen: Vec = vec![false; n]; @@ -47,7 +46,7 @@ fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { Ok(()) } -fn invert(pattern: &ArrayView1) -> Array1 { +pub fn invert(pattern: &ArrayView1) -> Array1 { let mut inverse: Array1 = Array1::zeros(pattern.len()); pattern.iter().enumerate().for_each(|(ii, &jj)| { inverse[jj as usize] = ii; @@ -55,7 +54,16 @@ fn invert(pattern: &ArrayView1) -> Array1 { inverse } -fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { +/// Sorts the input permutation by iterating through the permutation list +/// and putting each element to its correct position via a SWAP (if it's not +/// at the correct position already). If ``n`` is the length of the input +/// permutation, this requires at most ``n`` SWAPs. +/// +/// More precisely, if the input permutation is a cycle of length ``m``, +/// then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``); +/// if the input permutation consists of several disjoint cycles, then each cycle +/// is essentially treated independently. +pub fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { let mut permutation: Vec = pattern.iter().map(|&x| x as usize).collect(); let mut index_map = invert(pattern); @@ -76,45 +84,3 @@ fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { swaps[..].reverse(); swaps } - -/// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. -#[pyfunction] -#[pyo3(signature = (pattern))] -fn _validate_permutation(py: Python, pattern: PyArrayLike1) -> PyResult { - let view = pattern.as_array(); - validate_permutation(&view)?; - Ok(py.None()) -} - -/// Finds inverse of a permutation pattern. -#[pyfunction] -#[pyo3(signature = (pattern))] -fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { - let view = pattern.as_array(); - let inverse_i64: Vec = invert(&view).iter().map(|&x| x as i64).collect(); - Ok(inverse_i64.to_object(py)) -} - -/// Sorts the input permutation by iterating through the permutation list -/// and putting each element to its correct position via a SWAP (if it's not -/// at the correct position already). If ``n`` is the length of the input -/// permutation, this requires at most ``n`` SWAPs. -/// -/// More precisely, if the input permutation is a cycle of length ``m``, -/// then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``); -/// if the input permutation consists of several disjoint cycles, then each cycle -/// is essentially treated independently. -#[pyfunction] -#[pyo3(signature = (permutation_in))] -fn _get_ordered_swap(py: Python, permutation_in: PyArrayLike1) -> PyResult { - let view = permutation_in.as_array(); - Ok(get_ordered_swap(&view).to_object(py)) -} - -#[pymodule] -pub fn permutation(m: &Bound) -> PyResult<()> { - m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; - m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; - m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; - Ok(()) -} diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index b80aad1a7a45..72f0d759099a 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -17,10 +17,10 @@ use qiskit_accelerate::{ convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, - pauli_exp_val::pauli_expval, permutation::permutation, results::results, sabre::sabre, - sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, - stochastic_swap::stochastic_swap, two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, - utils::utils, vf2_layout::vf2_layout, + pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, + sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap, synthesis::synthesis, + two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils, + vf2_layout::vf2_layout, }; #[pymodule] @@ -36,7 +36,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(nlayout))?; m.add_wrapped(wrap_pymodule!(optimize_1q_gates))?; m.add_wrapped(wrap_pymodule!(pauli_expval))?; - m.add_wrapped(wrap_pymodule!(permutation))?; + m.add_wrapped(wrap_pymodule!(synthesis))?; m.add_wrapped(wrap_pymodule!(results))?; m.add_wrapped(wrap_pymodule!(sabre))?; m.add_wrapped(wrap_pymodule!(sampled_exp_val))?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index fce544333478..5b8505654428 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -80,7 +80,7 @@ sys.modules["qiskit._accelerate.stochastic_swap"] = _accelerate.stochastic_swap sys.modules["qiskit._accelerate.two_qubit_decompose"] = _accelerate.two_qubit_decompose sys.modules["qiskit._accelerate.vf2_layout"] = _accelerate.vf2_layout -sys.modules["qiskit._accelerate.permutation"] = _accelerate.permutation +sys.modules["qiskit._accelerate.synthesis.permutation"] = _accelerate.synthesis.permutation from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/circuit/library/generalized_gates/permutation.py b/qiskit/circuit/library/generalized_gates/permutation.py index 776c69d94f01..b2d17d2bed23 100644 --- a/qiskit/circuit/library/generalized_gates/permutation.py +++ b/qiskit/circuit/library/generalized_gates/permutation.py @@ -80,15 +80,13 @@ def __init__( name = "permutation_" + np.array_str(pattern).replace(" ", ",") - circuit = QuantumCircuit(num_qubits, name=name) - super().__init__(num_qubits, name=name) # pylint: disable=cyclic-import - from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap + from qiskit.synthesis.permutation import synth_permutation_basic - for i, j in _get_ordered_swap(pattern): - circuit.swap(i, j) + circuit = synth_permutation_basic(pattern) + circuit.name = name all_qubits = self.qubits self.append(circuit.to_gate(), all_qubits) @@ -184,10 +182,11 @@ def inverse(self, annotated: bool = False): def _qasm2_decomposition(self): # pylint: disable=cyclic-import - from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap + from qiskit.synthesis.permutation import synth_permutation_basic name = f"permutation__{'_'.join(str(n) for n in self.pattern)}_" - out = QuantumCircuit(self.num_qubits, name=name) - for i, j in _get_ordered_swap(self.pattern): - out.swap(i, j) + + out = synth_permutation_basic(self.pattern) + out.name = name + return out.to_gate() diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index ff014cb3a051..c280065c2a57 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -16,8 +16,8 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit._accelerate.synthesis.permutation import _synth_permutation_basic from .permutation_utils import ( - _get_ordered_swap, _inverse_pattern, _pattern_to_cycles, _decompose_cycles, @@ -44,17 +44,7 @@ def synth_permutation_basic(pattern: list[int] | np.ndarray[int]) -> QuantumCirc Returns: The synthesized quantum circuit. """ - # This is the very original Qiskit algorithm for synthesizing permutations. - - num_qubits = len(pattern) - qc = QuantumCircuit(num_qubits) - - swaps = _get_ordered_swap(pattern) - - for swap in swaps: - qc.swap(swap[0], swap[1]) - - return qc + return QuantumCircuit._from_circuit_data(_synth_permutation_basic(pattern)) def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircuit: diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index dbd73bfe8111..4520e18f4d06 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -13,9 +13,8 @@ """Utility functions for handling permutations.""" # pylint: disable=unused-import -from qiskit._accelerate.permutation import ( +from qiskit._accelerate.synthesis.permutation import ( _inverse_pattern, - _get_ordered_swap, _validate_permutation, ) diff --git a/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml b/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml new file mode 100644 index 000000000000..e770aa1ca31b --- /dev/null +++ b/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml @@ -0,0 +1,4 @@ +--- +upgrade_synthesis: + - | + Port :func:`.synth_permutation_basic`, used to synthesize qubit permutations, to Rust. diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py index 050df5a3fe1c..b6a1ca9e1857 100644 --- a/test/python/synthesis/test_permutation_synthesis.py +++ b/test/python/synthesis/test_permutation_synthesis.py @@ -27,7 +27,6 @@ ) from qiskit.synthesis.permutation.permutation_utils import ( _inverse_pattern, - _get_ordered_swap, _validate_permutation, ) from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -47,19 +46,6 @@ def test_inverse_pattern(self, width): for ii, jj in enumerate(pattern): self.assertTrue(inverse[jj] == ii) - @data(4, 5, 10, 15, 20) - def test_get_ordered_swap(self, width): - """Test _get_ordered_swap function produces correct swap list.""" - np.random.seed(1) - for _ in range(5): - pattern = np.random.permutation(width) - swap_list = _get_ordered_swap(pattern) - output = list(range(width)) - for i, j in swap_list: - output[i], output[j] = output[j], output[i] - self.assertTrue(np.array_equal(pattern, output)) - self.assertLess(len(swap_list), width) - @data(10, 20) def test_invalid_permutations(self, width): """Check that _validate_permutation raises exceptions when the From bf8f398fa4ddf287c6182b39bd27b324ab11dda0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 24 Jun 2024 10:05:06 -0400 Subject: [PATCH 2/3] Add rust representation for the u1, u2, and u3 gates (#12572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add rust representation for the u1, u2, and u3 gates This commit adds the rust representation of the U1Gate, U2Gate, and U3Gate to the `StandardGates` enum in rust. Part of #12566 * Update crates/circuit/src/imports.rs Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Fix test failures * Fix pylint pedantry --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/gate_matrix.rs | 32 +++++ crates/circuit/src/imports.rs | 6 + crates/circuit/src/operations.rs | 74 +++++++++++- qiskit/circuit/library/standard_gates/u1.py | 3 + qiskit/circuit/library/standard_gates/u2.py | 3 + qiskit/circuit/library/standard_gates/u3.py | 3 + test/python/circuit/test_rust_equivalence.py | 25 ++-- test/python/qasm3/test_export.py | 114 +++++++++--------- .../transpiler/test_optimize_1q_gates.py | 36 +++++- 9 files changed, 224 insertions(+), 72 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 23ce94869227..2e5f55d6ddcb 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -271,6 +271,38 @@ pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { ] } +#[inline] +pub fn u1_gate(lam: f64) -> [[Complex64; 2]; 2] { + [ + [c64(1., 0.), c64(0., 0.)], + [c64(0., 0.), c64(0., lam).exp()], + ] +} + +#[inline] +pub fn u2_gate(phi: f64, lam: f64) -> [[Complex64; 2]; 2] { + [ + [ + c64(FRAC_1_SQRT_2, 0.), + (-c64(0., lam).exp()) * FRAC_1_SQRT_2, + ], + [ + c64(0., phi).exp() * FRAC_1_SQRT_2, + c64(0., phi + lam).exp() * FRAC_1_SQRT_2, + ], + ] +} + +#[inline] +pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [c64(cos, 0.), -(c64(0., lam).exp()) * sin], + [c64(0., phi).exp() * sin, c64(0., phi + lam).exp() * cos], + ] +} + #[inline] pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { let cos = (theta / 2.).cos(); diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 3a9a942db8df..7160798f56bb 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -141,6 +141,12 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ "qiskit.circuit.library.standard_gates.xx_plus_yy", "XXPlusYYGate", ], + // U1Gate = 26 + ["qiskit.circuit.library.standard_gates.u1", "U1Gate"], + // U2Gate = 27 + ["qiskit.circuit.library.standard_gates.u2", "U2Gate"], + // U3Gate = 28 + ["qiskit.circuit.library.standard_gates.u3", "U3Gate"], ]; /// A mapping from the enum variant in crate::operations::StandardGate to the python object for the diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 6dedd3ac2061..451b04947388 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -205,14 +205,17 @@ pub enum StandardGate { ISwapGate = 23, XXMinusYYGate = 24, XXPlusYYGate = 25, + U1Gate = 26, + U2Gate = 27, + U3Gate = 28, } static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ - 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, ]; static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, 1, 2, 3, ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ @@ -242,6 +245,9 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "iswap", "xx_minus_yy", "xx_plus_yy", + "u1", + "u2", + "u3", ]; #[pymethods] @@ -290,8 +296,7 @@ impl StandardGate { // // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) - -pub const STANDARD_GATE_SIZE: usize = 26; +pub const STANDARD_GATE_SIZE: usize = 29; impl Operation for StandardGate { fn name(&self) -> &str { @@ -432,6 +437,22 @@ impl Operation for StandardGate { } _ => None, }, + Self::U1Gate => match params[0] { + Param::Float(val) => Some(aview2(&gate_matrix::u1_gate(val)).to_owned()), + _ => None, + }, + Self::U2Gate => match params { + [Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::u2_gate(*phi, *lam)).to_owned()) + } + _ => None, + }, + Self::U3Gate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::u3_gate(*theta, *phi, *lam)).to_owned()) + } + _ => None, + }, } } @@ -667,6 +688,21 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::U1Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::PhaseGate, + params.iter().cloned().collect(), + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::SdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -682,6 +718,21 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::U2Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::UGate, + smallvec![Param::Float(PI / 2.), params[0].clone(), params[1].clone()], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::TGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -697,6 +748,21 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::U3Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::UGate, + params.iter().cloned().collect(), + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::TdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index 1d59cabae1f6..f141146b72dc 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -19,6 +19,7 @@ from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import _ctrl_state_to_int +from qiskit._accelerate.circuit import StandardGate class U1Gate(Gate): @@ -92,6 +93,8 @@ class U1Gate(Gate): `1612.00858 `_ """ + _standard_gate = StandardGate.U1Gate + def __init__( self, theta: ParameterValueType, label: str | None = None, *, duration=None, unit="dt" ): diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py index c8e4de96efec..9e59cd4c5bbd 100644 --- a/qiskit/circuit/library/standard_gates/u2.py +++ b/qiskit/circuit/library/standard_gates/u2.py @@ -18,6 +18,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumregister import QuantumRegister +from qiskit._accelerate.circuit import StandardGate class U2Gate(Gate): @@ -86,6 +87,8 @@ class U2Gate(Gate): using two X90 pulses. """ + _standard_gate = StandardGate.U2Gate + def __init__( self, phi: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index 0eef2518a85a..f191609ea8f1 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -19,6 +19,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumregister import QuantumRegister +from qiskit._accelerate.circuit import StandardGate class U3Gate(Gate): @@ -80,6 +81,8 @@ class U3Gate(Gate): U3(\theta, 0, 0) = RY(\theta) """ + _standard_gate = StandardGate.U3Gate + def __init__( self, theta: ParameterValueType, diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index bb09ae4caf3f..8d6d159c0b64 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -79,12 +79,23 @@ def test_definitions(self): ) # Rust uses P but python still uses u1 elif rs_inst.operation.name == "p": - self.assertEqual(py_inst.operation.name, "u1") - self.assertEqual(rs_inst.operation.params, py_inst.operation.params) - self.assertEqual( - [py_def.find_bit(x).index for x in py_inst.qubits], - [rs_def.find_bit(x).index for x in rs_inst.qubits], - ) + if py_inst.operation.name == "u1": + self.assertEqual(py_inst.operation.name, "u1") + self.assertEqual(rs_inst.operation.params, py_inst.operation.params) + self.assertEqual( + [py_def.find_bit(x).index for x in py_inst.qubits], + [rs_def.find_bit(x).index for x in rs_inst.qubits], + ) + else: + self.assertEqual(py_inst.operation.name, "u3") + self.assertEqual( + rs_inst.operation.params[0], py_inst.operation.params[2] + ) + self.assertEqual( + [py_def.find_bit(x).index for x in py_inst.qubits], + [rs_def.find_bit(x).index for x in rs_inst.qubits], + ) + else: self.assertEqual(py_inst.operation.name, rs_inst.operation.name) self.assertEqual(rs_inst.operation.params, py_inst.operation.params) @@ -102,7 +113,7 @@ def test_matrix(self): continue with self.subTest(name=name): - params = [pi] * standard_gate._num_params() + params = [0.1] * standard_gate._num_params() py_def = gate_class.base_class(*params).to_matrix() rs_def = standard_gate._to_matrix(params) np.testing.assert_allclose(rs_def, py_def) diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py index 048c5d7852b4..6df041420882 100644 --- a/test/python/qasm3/test_export.py +++ b/test/python/qasm3/test_export.py @@ -2018,65 +2018,63 @@ def test_teleportation(self): qc.z(2).c_if(qc.clbits[0], 1) transpiled = transpile(qc, initial_layout=[0, 1, 2]) - first_h = transpiled.data[0].operation - u2 = first_h.definition.data[0].operation - u3_1 = u2.definition.data[0].operation - first_x = transpiled.data[-2].operation - u3_2 = first_x.definition.data[0].operation - first_z = transpiled.data[-1].operation - u1 = first_z.definition.data[0].operation - u3_3 = u1.definition.data[0].operation + id_len = len(str(id(transpiled.data[0].operation))) - expected_qasm = "\n".join( - [ - "OPENQASM 3.0;", - f"gate u3_{id(u3_1)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{", - " U(pi/2, 0, pi) _gate_q_0;", - "}", - f"gate u2_{id(u2)}(_gate_p_0, _gate_p_1) _gate_q_0 {{", - f" u3_{id(u3_1)}(pi/2, 0, pi) _gate_q_0;", - "}", - "gate h _gate_q_0 {", - f" u2_{id(u2)}(0, pi) _gate_q_0;", - "}", - "gate cx c, t {", - " ctrl @ U(pi, 0, pi) c, t;", - "}", - f"gate u3_{id(u3_2)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{", - " U(pi, 0, pi) _gate_q_0;", - "}", - "gate x _gate_q_0 {", - f" u3_{id(u3_2)}(pi, 0, pi) _gate_q_0;", - "}", - f"gate u3_{id(u3_3)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{", - " U(0, 0, pi) _gate_q_0;", - "}", - f"gate u1_{id(u1)}(_gate_p_0) _gate_q_0 {{", - f" u3_{id(u3_3)}(0, 0, pi) _gate_q_0;", - "}", - "gate z _gate_q_0 {", - f" u1_{id(u1)}(pi) _gate_q_0;", - "}", - "bit[2] c;", - "h $1;", - "cx $1, $2;", - "barrier $0, $1, $2;", - "cx $0, $1;", - "h $0;", - "barrier $0, $1, $2;", - "c[0] = measure $0;", - "c[1] = measure $1;", - "barrier $0, $1, $2;", - "if (c[1]) {", - " x $2;", - "}", - "if (c[0]) {", - " z $2;", - "}", - "", - ] - ) - self.assertEqual(Exporter(includes=[]).dumps(transpiled), expected_qasm) + expected_qasm = [ + "OPENQASM 3.0;", + re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len), + " U(pi/2, 0, pi) _gate_q_0;", + "}", + re.compile(r"gate u2_\d{%s}\(_gate_p_0, _gate_p_1\) _gate_q_0 \{" % id_len), + re.compile(r" u3_\d{%s}\(pi/2, 0, pi\) _gate_q_0;" % id_len), + "}", + "gate h _gate_q_0 {", + re.compile(r" u2_\d{%s}\(0, pi\) _gate_q_0;" % id_len), + "}", + "gate cx c, t {", + " ctrl @ U(pi, 0, pi) c, t;", + "}", + re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len), + " U(pi, 0, pi) _gate_q_0;", + "}", + "gate x _gate_q_0 {", + re.compile(r" u3_\d{%s}\(pi, 0, pi\) _gate_q_0;" % id_len), + "}", + re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len), + " U(0, 0, pi) _gate_q_0;", + "}", + re.compile(r"gate u1_\d{%s}\(_gate_p_0\) _gate_q_0 \{" % id_len), + re.compile(r" u3_\d{%s}\(0, 0, pi\) _gate_q_0;" % id_len), + "}", + "gate z _gate_q_0 {", + re.compile(r" u1_\d{%s}\(pi\) _gate_q_0;" % id_len), + "}", + "bit[2] c;", + "h $1;", + "cx $1, $2;", + "barrier $0, $1, $2;", + "cx $0, $1;", + "h $0;", + "barrier $0, $1, $2;", + "c[0] = measure $0;", + "c[1] = measure $1;", + "barrier $0, $1, $2;", + "if (c[1]) {", + " x $2;", + "}", + "if (c[0]) {", + " z $2;", + "}", + "", + ] + res = Exporter(includes=[]).dumps(transpiled).splitlines() + for result, expected in zip(res, expected_qasm): + if isinstance(expected, str): + self.assertEqual(result, expected) + else: + self.assertTrue( + expected.search(result), f"Line {result} doesn't match regex: {expected}" + ) def test_custom_gate_with_params_bound_main_call(self): """Custom gate with unbound parameters that are bound in the main circuit""" diff --git a/test/python/transpiler/test_optimize_1q_gates.py b/test/python/transpiler/test_optimize_1q_gates.py index 9253130bedbf..e5483dd47499 100644 --- a/test/python/transpiler/test_optimize_1q_gates.py +++ b/test/python/transpiler/test_optimize_1q_gates.py @@ -19,7 +19,7 @@ from qiskit.transpiler import PassManager from qiskit.transpiler.passes import Optimize1qGates, BasisTranslator from qiskit.converters import circuit_to_dag -from qiskit.circuit import Parameter +from qiskit.circuit import Parameter, Gate from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, UGate, PhaseGate from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.target import Target @@ -323,9 +323,24 @@ def test_parameterized_expressions_in_circuits(self): def test_global_phase_u3_on_left(self): """Check proper phase accumulation with instruction with no definition.""" + + class CustomGate(Gate): + """Custom u1 gate definition.""" + + def __init__(self, lam): + super().__init__("u1", 1, [lam]) + + def _define(self): + qc = QuantumCircuit(1) + qc.p(*self.params, 0) + self.definition = qc + + def _matrix(self): + return U1Gate(*self.params).to_matrix() + qr = QuantumRegister(1) qc = QuantumCircuit(qr) - u1 = U1Gate(0.1) + u1 = CustomGate(0.1) u1.definition.global_phase = np.pi / 2 qc.append(u1, [0]) qc.global_phase = np.pi / 3 @@ -337,9 +352,24 @@ def test_global_phase_u3_on_left(self): def test_global_phase_u_on_left(self): """Check proper phase accumulation with instruction with no definition.""" + + class CustomGate(Gate): + """Custom u1 gate.""" + + def __init__(self, lam): + super().__init__("u1", 1, [lam]) + + def _define(self): + qc = QuantumCircuit(1) + qc.p(*self.params, 0) + self.definition = qc + + def _matrix(self): + return U1Gate(*self.params).to_matrix() + qr = QuantumRegister(1) qc = QuantumCircuit(qr) - u1 = U1Gate(0.1) + u1 = CustomGate(0.1) u1.definition.global_phase = np.pi / 2 qc.append(u1, [0]) qc.global_phase = np.pi / 3 From 35f6297f20be6d9b58671948adc179dad77894af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:23:42 -0400 Subject: [PATCH 3/3] Bump faer from 0.19.0 to 0.19.1 (#12645) Bumps [faer](https://github.com/sarah-ek/faer-rs) from 0.19.0 to 0.19.1. - [Changelog](https://github.com/sarah-ek/faer-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/sarah-ek/faer-rs/commits) --- updated-dependencies: - dependency-name: faer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/accelerate/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aefa3c932a04..454823748e8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,9 +307,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "faer" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ef9e1a4098a9e3a03c47bc5061406c04820552d869fd0fcd92587d07b271f0" +checksum = "41543c4de4bfb32efdffdd75cbcca5ef41b800e8a811ea4a41fb9393c6ef3bc0" dependencies = [ "bytemuck", "coe-rs", diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index d9865d545437..b377a9b38a6d 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -20,7 +20,7 @@ num-traits = "0.2" num-complex.workspace = true num-bigint = "0.4" rustworkx-core = "0.14" -faer = "0.19.0" +faer = "0.19.1" itertools = "0.13.0" qiskit-circuit.workspace = true