From 1ff6361303c4b4bd472eee5249fadcbace40ca96 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 12 Sep 2024 15:07:26 +0200 Subject: [PATCH 1/5] Oxidize `PauliFeatureMap` (#13045) * port PauliFeatureMap to Rust * more docs, less error expecting * cleanup, docstrings * clippy & speedup * base for dictionary entanglement * rm unnecessary pub * add reno * refactor ``rmultiply_param`` * fix & test dict entanglement * clippy * Refactor Rust code a bit - improve readability - only create barrier if necessary * RIP iterators (for proper error handling) performance seems to be unaffected :) --- .../src/circuit_library/entanglement.rs | 50 ++- crates/accelerate/src/circuit_library/mod.rs | 2 + .../src/circuit_library/pauli_feature_map.rs | 346 +++++++++++++++++ crates/circuit/src/imports.rs | 1 + crates/circuit/src/operations.rs | 22 +- qiskit/circuit/library/__init__.py | 3 + .../library/data_preparation/__init__.py | 9 +- .../{z_feature_map.py => _z_feature_map.py} | 77 ++-- .../{zz_feature_map.py => _zz_feature_map.py} | 35 +- .../data_preparation/pauli_feature_map.py | 359 ++++++++++++++++-- .../notes/rust-paulifm-1dc7b1c2dc756614.yaml | 16 + .../circuit/library/test_pauli_feature_map.py | 295 +++++++++++++- 12 files changed, 1119 insertions(+), 96 deletions(-) create mode 100644 crates/accelerate/src/circuit_library/pauli_feature_map.rs rename qiskit/circuit/library/data_preparation/{z_feature_map.py => _z_feature_map.py} (52%) rename qiskit/circuit/library/data_preparation/{zz_feature_map.py => _zz_feature_map.py} (71%) create mode 100644 releasenotes/notes/rust-paulifm-1dc7b1c2dc756614.yaml diff --git a/crates/accelerate/src/circuit_library/entanglement.rs b/crates/accelerate/src/circuit_library/entanglement.rs index db602ef717fa..fbfb5c0193f1 100644 --- a/crates/accelerate/src/circuit_library/entanglement.rs +++ b/crates/accelerate/src/circuit_library/entanglement.rs @@ -12,6 +12,7 @@ use itertools::Itertools; use pyo3::prelude::*; +use pyo3::types::PyDict; use pyo3::{ types::{PyAnyMethods, PyInt, PyList, PyListMethods, PyString, PyTuple}, Bound, PyAny, PyResult, @@ -173,30 +174,45 @@ pub fn get_entanglement<'a>( return Ok(Box::new( get_entanglement_from_str(num_qubits, block_size, as_str.as_str(), offset)?.map(Ok), )); + } else if let Ok(dict) = entanglement.downcast::() { + if let Some(value) = dict.get_item(block_size)? { + let list = value.downcast::()?; + return _check_entanglement_list(list.to_owned(), block_size); + } else { + return Ok(Box::new(std::iter::empty())); + } } else if let Ok(list) = entanglement.downcast::() { - let entanglement_iter = list.iter().map(move |el| { - let connections = el - .downcast::()? - // .expect("Entanglement must be list of tuples") // clearer error message than `?` - .iter() - .map(|index| index.downcast::()?.extract()) - .collect::, _>>()?; - - if connections.len() != block_size as usize { - return Err(QiskitError::new_err(format!( - "Entanglement {:?} does not match block size {}", - connections, block_size - ))); - } - Ok(connections) - }); - return Ok(Box::new(entanglement_iter)); + return _check_entanglement_list(list.to_owned(), block_size); } Err(QiskitError::new_err( "Entanglement must be a string or list of qubit indices.", )) } +fn _check_entanglement_list<'a>( + list: Bound<'a, PyList>, + block_size: u32, +) -> PyResult>> + 'a>> { + let entanglement_iter = list.iter().map(move |el| { + let connections = el + .downcast::()? + // .expect("Entanglement must be list of tuples") // clearer error message than `?` + .iter() + .map(|index| index.downcast::()?.extract()) + .collect::, _>>()?; + + if connections.len() != block_size as usize { + return Err(QiskitError::new_err(format!( + "Entanglement {:?} does not match block size {}", + connections, block_size + ))); + } + + Ok(connections) + }); + Ok(Box::new(entanglement_iter)) +} + /// Get the entanglement for given number of qubits and block size. /// /// Args: diff --git a/crates/accelerate/src/circuit_library/mod.rs b/crates/accelerate/src/circuit_library/mod.rs index 9df16a04fc6c..94db90ccd234 100644 --- a/crates/accelerate/src/circuit_library/mod.rs +++ b/crates/accelerate/src/circuit_library/mod.rs @@ -13,8 +13,10 @@ use pyo3::prelude::*; mod entanglement; +mod pauli_feature_map; pub fn circuit_library(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(pauli_feature_map::pauli_feature_map))?; m.add_wrapped(wrap_pyfunction!(entanglement::get_entangler_map))?; Ok(()) } diff --git a/crates/accelerate/src/circuit_library/pauli_feature_map.rs b/crates/accelerate/src/circuit_library/pauli_feature_map.rs new file mode 100644 index 000000000000..53e84a197f1d --- /dev/null +++ b/crates/accelerate/src/circuit_library/pauli_feature_map.rs @@ -0,0 +1,346 @@ +// 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 itertools::Itertools; +use pyo3::prelude::*; +use pyo3::types::PySequence; +use pyo3::types::PyString; +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::imports; +use qiskit_circuit::operations::PyInstruction; +use qiskit_circuit::operations::{add_param, multiply_param, multiply_params, Param, StandardGate}; +use qiskit_circuit::packed_instruction::PackedOperation; +use qiskit_circuit::{Clbit, Qubit}; +use smallvec::{smallvec, SmallVec}; +use std::f64::consts::PI; + +use crate::circuit_library::entanglement; +use crate::QiskitError; + +// custom math and types for a more readable code +const PI2: f64 = PI / 2.; +type Instruction = ( + PackedOperation, + SmallVec<[Param; 3]>, + Vec, + Vec, +); +type StandardInstruction = (StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>); + +/// Return instructions (using only StandardGate operations) to implement a Pauli evolution +/// of a given Pauli string over a given time (as Param). +/// +/// The Pauli evolution is implemented as a basis transformation to the Pauli-Z basis, +/// followed by a CX-chain and then a single Pauli-Z rotation on the last qubit. Then the CX-chain +/// is uncomputed and the inverse basis transformation applied. E.g. for the evolution under the +/// Pauli string XIYZ we have the circuit +/// ┌───┐┌───────┐┌───┐ +/// 0: ─────────────────┤ X ├┤ Rz(2) ├┤ X ├────────────────── +/// ┌──────────┐┌───┐└─┬─┘└───────┘└─┬─┘┌───┐┌───────────┐ +/// 1: ┤ Rx(pi/2) ├┤ X ├──■─────────────■──┤ X ├┤ Rx(-pi/2) ├ +/// └──────────┘└─┬─┘ └─┬─┘└───────────┘ +/// 2: ──────────────┼───────────────────────┼─────────────── +/// ┌───┐ │ │ ┌───┐ +/// 3: ─┤ H ├────────■───────────────────────■──┤ H ├──────── +/// └───┘ └───┘ +fn pauli_evolution( + pauli: &str, + indices: Vec, + time: Param, +) -> impl Iterator + '_ { + // Get pairs of (pauli, qubit) that are active, i.e. that are not the identity. Note that + // the rest of the code also works if there are only identities, in which case we will + // effectively return an empty iterator. + let qubits = indices.iter().map(|i| Qubit(*i)).collect_vec(); + let binding = pauli.to_lowercase(); // lowercase for convenience + let active_paulis = binding + .as_str() + .chars() + .rev() // reverse due to Qiskit's bit ordering convention + .zip(qubits) + .filter(|(p, _)| *p != 'i') + .collect_vec(); + + // get the basis change: x -> HGate, y -> RXGate(pi/2), z -> nothing + let basis_change = active_paulis + .clone() + .into_iter() + .filter(|(p, _)| *p != 'z') + .map(|(p, q)| match p { + 'x' => (StandardGate::HGate, smallvec![], smallvec![q]), + 'y' => ( + StandardGate::RXGate, + smallvec![Param::Float(PI2)], + smallvec![q], + ), + _ => unreachable!("Invalid Pauli string."), // "z" and "i" have been filtered out + }); + + // get the inverse basis change + let inverse_basis_change = basis_change.clone().map(|(gate, _, qubit)| match gate { + StandardGate::HGate => (gate, smallvec![], qubit), + StandardGate::RXGate => (gate, smallvec![Param::Float(-PI2)], qubit), + _ => unreachable!(), + }); + + // get the CX chain down to the target rotation qubit + let chain_down = active_paulis + .clone() + .into_iter() + .map(|(_, q)| q) + .tuple_windows() // iterates over (q[i], q[i+1]) windows + .map(|(ctrl, target)| (StandardGate::CXGate, smallvec![], smallvec![ctrl, target])); + + // get the CX chain up (cannot use chain_down.rev since tuple_windows is not double ended) + let chain_up = active_paulis + .clone() + .into_iter() + .rev() + .map(|(_, q)| q) + .tuple_windows() + .map(|(target, ctrl)| (StandardGate::CXGate, smallvec![], smallvec![ctrl, target])); + + // get the RZ gate on the last qubit + let last_qubit = active_paulis.last().unwrap().1; + let z_rotation = std::iter::once(( + StandardGate::PhaseGate, + smallvec![time], + smallvec![last_qubit], + )); + + // and finally chain everything together + basis_change + .chain(chain_down) + .chain(z_rotation) + .chain(chain_up) + .chain(inverse_basis_change) +} + +/// Build a Pauli feature map circuit. +/// +/// Args: +/// feature_dimension: The feature dimension (i.e. the number of qubits). +/// parameters: A parameter vector with ``feature_dimension`` elements. Taken as input +/// here to avoid a call to Python constructing the vector. +/// reps: The number of repetitions of Hadamard + evolution layers. +/// entanglement: The entanglement, given as Python string or None (defaults to "full"). +/// paulis: The Pauli strings as list of strings or None (default to ["z", "zz"]). +/// alpha: A float multiplier for rotation angles. +/// insert_barriers: Whether to insert barriers in between the Hadamard and evolution layers. +/// data_map_func: An accumulation function that takes as input a vector of parameters the +/// current gate acts on and returns a scalar. +/// +/// Returns: +/// The ``CircuitData`` to construct the Pauli feature map. +#[pyfunction] +#[pyo3(signature = (feature_dimension, parameters, *, reps=1, entanglement=None, paulis=None, alpha=2.0, insert_barriers=false, data_map_func=None))] +#[allow(clippy::too_many_arguments)] +pub fn pauli_feature_map( + py: Python, + feature_dimension: u32, + parameters: Bound, + reps: usize, + entanglement: Option<&Bound>, + paulis: Option<&Bound>, + alpha: f64, + insert_barriers: bool, + data_map_func: Option<&Bound>, +) -> PyResult { + // normalize the Pauli strings + let pauli_strings = _get_paulis(feature_dimension, paulis)?; + + // set the default value for entanglement + let default = PyString::new_bound(py, "full"); + let entanglement = entanglement.unwrap_or(&default); + + // extract the parameters from the input variable ``parameters`` + let parameter_vector = parameters + .iter()? + .map(|el| Param::extract_no_coerce(&el?)) + .collect::>>()?; + + // construct a Barrier object Python side to (possibly) add to the circuit + let packed_barrier = if insert_barriers { + Some(_get_barrier(py, feature_dimension)?) + } else { + None + }; + + // Main work: construct the circuit instructions as iterator. Each repetition is constituted + // by a layer of Hadamards and the Pauli evolutions of the specified Paulis. + // Note that we eagerly trigger errors, since the final CircuitData::from_packed_operations + // does not allow Result objects in the iterator. + let mut packed_insts: Vec = Vec::new(); + for rep in 0..reps { + // add H layer + packed_insts.extend(_get_h_layer(feature_dimension)); + + if insert_barriers { + packed_insts.push(packed_barrier.clone().unwrap()); + } + + // add evolutions + let evo_layer = _get_evolution_layer( + py, + feature_dimension, + rep, + alpha, + ¶meter_vector, + &pauli_strings, + entanglement, + data_map_func, + )?; + packed_insts.extend(evo_layer); + + // add barriers, if necessary + if insert_barriers && rep < reps - 1 { + packed_insts.push(packed_barrier.clone().unwrap()); + } + } + + CircuitData::from_packed_operations(py, feature_dimension, 0, packed_insts, Param::Float(0.0)) +} + +fn _get_h_layer(feature_dimension: u32) -> impl Iterator { + (0..feature_dimension).map(|i| { + ( + StandardGate::HGate.into(), + smallvec![], + vec![Qubit(i)], + vec![] as Vec, + ) + }) +} + +#[allow(clippy::too_many_arguments)] +fn _get_evolution_layer<'a>( + py: Python<'a>, + feature_dimension: u32, + rep: usize, + alpha: f64, + parameter_vector: &'a [Param], + pauli_strings: &'a [String], + entanglement: &'a Bound, + data_map_func: Option<&'a Bound>, +) -> PyResult> { + let mut insts: Vec = Vec::new(); + + for pauli in pauli_strings { + let block_size = pauli.len() as u32; + let entanglement = + entanglement::get_entanglement(feature_dimension, block_size, entanglement, rep)?; + + for indices in entanglement { + let indices = indices?; + let active_parameters: Vec = indices + .clone() + .iter() + .map(|i| parameter_vector[*i as usize].clone()) + .collect(); + + let angle = match data_map_func { + Some(fun) => fun.call1((active_parameters,))?.extract()?, + None => _default_reduce(py, active_parameters), + }; + + // Get the pauli evolution and map it into + // (PackedOperation, SmallVec<[Params; 3]>, Vec, Vec) + // to call CircuitData::from_packed_operations. This is needed since we might + // have to interject barriers, which are not a standard gate and prevents us + // from using CircuitData::from_standard_gates. + let evo = pauli_evolution(pauli, indices.clone(), multiply_param(&angle, alpha, py)) + .map(|(gate, params, qargs)| { + (gate.into(), params, qargs.to_vec(), vec![] as Vec) + }) + .collect::>(); + insts.extend(evo); + } + } + + Ok(insts) +} + +/// The default data_map_func for Pauli feature maps. For a parameter vector (x1, ..., xN), this +/// implements +/// (pi - x1) (pi - x2) ... (pi - xN) +/// unless there is only one parameter, in which case it returns just the value. +fn _default_reduce(py: Python, parameters: Vec) -> Param { + if parameters.len() == 1 { + parameters[0].clone() + } else { + let acc = parameters.iter().fold(Param::Float(1.0), |acc, param| { + multiply_params(acc, add_param(param, -PI, py), py) + }); + if parameters.len() % 2 == 0 { + acc + } else { + multiply_param(&acc, -1.0, py) // take care of parity + } + } +} + +/// Normalize the Pauli strings to a Vec. We first define the default, which is +/// ["z", "zz"], unless we only have a single qubit, in which case we default to ["z"]. +/// Then, ``pauli_strings`` is either set to the default, or we try downcasting to a +/// PyString->String, followed by a check whether the feature dimension is large enough +/// for the Pauli (e.g. we cannot implement a "zzz" Pauli on a 2 qubit circuit). +fn _get_paulis( + feature_dimension: u32, + paulis: Option<&Bound>, +) -> PyResult> { + let default_pauli: Vec = if feature_dimension == 1 { + vec!["z".to_string()] + } else { + vec!["z".to_string(), "zz".to_string()] + }; + + paulis.map_or_else( + || Ok(default_pauli), // use Ok() since we might raise an error in the other arm + |v| { + let v = PySequenceMethods::to_list(v)?; // sequence to list + v.iter() // iterate over the list of Paulis + .map(|el| { + // Get the string and check whether it fits the feature dimension + let as_string = (*el.downcast::()?).to_string(); + if as_string.len() > feature_dimension as usize { + Err(QiskitError::new_err(format!( + "feature_dimension ({}) smaller than the Pauli ({})", + feature_dimension, as_string + ))) + } else { + Ok(as_string) + } + }) + .collect::>>() + }, + ) +} + +/// Get a barrier object from Python space. +fn _get_barrier(py: Python, feature_dimension: u32) -> PyResult { + let barrier_cls = imports::BARRIER.get_bound(py); + let barrier = barrier_cls.call1((feature_dimension,))?; + let barrier_inst = PyInstruction { + qubits: feature_dimension, + clbits: 0, + params: 0, + op_name: "barrier".to_string(), + control_flow: false, + instruction: barrier.into(), + }; + Ok(( + barrier_inst.into(), + smallvec![], + (0..feature_dimension).map(Qubit).collect(), + vec![] as Vec, + )) +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index a8f6d9562559..5c77f8ef7d17 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -109,6 +109,7 @@ pub static SWITCH_CASE_OP_CHECK: ImportOnceCell = pub static FOR_LOOP_OP_CHECK: ImportOnceCell = ImportOnceCell::new("qiskit.dagcircuit.dagnode", "_for_loop_eq"); pub static UUID: ImportOnceCell = ImportOnceCell::new("uuid", "UUID"); +pub static BARRIER: ImportOnceCell = ImportOnceCell::new("qiskit.circuit", "Barrier"); pub static UNITARY_GATE: ImportOnceCell = ImportOnceCell::new( "qiskit.circuit.library.generalized_gates.unitary", "UnitaryGate", diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index a3fd2fc83c3d..2a341d687f6c 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -2051,7 +2051,8 @@ fn clone_param(param: &Param, py: Python) -> Param { } } -fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { +/// Multiply a ``Param`` with a float. +pub fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { match param { Param::Float(theta) => Param::Float(theta * mult), Param::ParameterExpression(theta) => Param::ParameterExpression( @@ -2064,7 +2065,24 @@ fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { } } -fn add_param(param: &Param, summand: f64, py: Python) -> Param { +/// Multiply two ``Param``s. +pub fn multiply_params(param1: Param, param2: Param, py: Python) -> Param { + match (¶m1, ¶m2) { + (Param::Float(theta), Param::Float(lambda)) => Param::Float(theta * lambda), + (param, Param::Float(theta)) => multiply_param(param, *theta, py), + (Param::Float(theta), param) => multiply_param(param, *theta, py), + (Param::ParameterExpression(p1), Param::ParameterExpression(p2)) => { + Param::ParameterExpression( + p1.clone_ref(py) + .call_method1(py, intern!(py, "__rmul__"), (p2,)) + .expect("Parameter expression multiplication failed"), + ) + } + _ => unreachable!("Unsupported multiplication."), + } +} + +pub fn add_param(param: &Param, summand: f64, py: Python) -> Param { match param { Param::Float(theta) => Param::Float(*theta + summand), Param::ParameterExpression(theta) => Param::ParameterExpression( diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 39266cf4ca17..9dd1fdebf08a 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -555,6 +555,9 @@ QAOAAnsatz, ) from .data_preparation import ( + z_feature_map, + zz_feature_map, + pauli_feature_map, PauliFeatureMap, ZFeatureMap, ZZFeatureMap, diff --git a/qiskit/circuit/library/data_preparation/__init__.py b/qiskit/circuit/library/data_preparation/__init__.py index 192308a3a7f5..475046d78338 100644 --- a/qiskit/circuit/library/data_preparation/__init__.py +++ b/qiskit/circuit/library/data_preparation/__init__.py @@ -38,15 +38,18 @@ """ -from .pauli_feature_map import PauliFeatureMap -from .z_feature_map import ZFeatureMap -from .zz_feature_map import ZZFeatureMap +from .pauli_feature_map import PauliFeatureMap, pauli_feature_map, z_feature_map, zz_feature_map +from ._z_feature_map import ZFeatureMap +from ._zz_feature_map import ZZFeatureMap from .state_preparation import StatePreparation, UniformSuperpositionGate from .initializer import Initialize __all__ = [ + "pauli_feature_map", "PauliFeatureMap", + "z_feature_map", "ZFeatureMap", + "zz_feature_map", "ZZFeatureMap", "StatePreparation", "UniformSuperpositionGate", diff --git a/qiskit/circuit/library/data_preparation/z_feature_map.py b/qiskit/circuit/library/data_preparation/_z_feature_map.py similarity index 52% rename from qiskit/circuit/library/data_preparation/z_feature_map.py rename to qiskit/circuit/library/data_preparation/_z_feature_map.py index 902de1a6a5ac..451776067114 100644 --- a/qiskit/circuit/library/data_preparation/z_feature_map.py +++ b/qiskit/circuit/library/data_preparation/_z_feature_map.py @@ -14,6 +14,7 @@ from typing import Callable, Optional import numpy as np +from qiskit.utils.deprecation import deprecate_func from .pauli_feature_map import PauliFeatureMap @@ -25,13 +26,13 @@ class ZFeatureMap(PauliFeatureMap): .. parsed-literal:: - ┌───┐┌──────────────┐┌───┐┌──────────────┐ - ┤ H ├┤ U1(2.0*x[0]) ├┤ H ├┤ U1(2.0*x[0]) ├ - ├───┤├──────────────┤├───┤├──────────────┤ - ┤ H ├┤ U1(2.0*x[1]) ├┤ H ├┤ U1(2.0*x[1]) ├ - ├───┤├──────────────┤├───┤├──────────────┤ - ┤ H ├┤ U1(2.0*x[2]) ├┤ H ├┤ U1(2.0*x[2]) ├ - └───┘└──────────────┘└───┘└──────────────┘ + ┌───┐┌─────────────┐┌───┐┌─────────────┐ + ┤ H ├┤ P(2.0*x[0]) ├┤ H ├┤ P(2.0*x[0]) ├ + ├───┤├─────────────┤├───┤├─────────────┤ + ┤ H ├┤ U(2.0*x[1]) ├┤ H ├┤ P(2.0*x[1]) ├ + ├───┤├─────────────┤├───┤├─────────────┤ + ┤ H ├┤ P(2.0*x[2]) ├┤ H ├┤ P(2.0*x[2]) ├ + └───┘└─────────────┘└───┘└─────────────┘ This is a sub-class of :class:`~qiskit.circuit.library.PauliFeatureMap` where the Pauli strings are fixed as `['Z']`. As a result the first order expansion will be a circuit without @@ -40,38 +41,48 @@ class ZFeatureMap(PauliFeatureMap): Examples: >>> prep = ZFeatureMap(3, reps=3, insert_barriers=True) - >>> print(prep) - ┌───┐ ░ ┌──────────────┐ ░ ┌───┐ ░ ┌──────────────┐ ░ ┌───┐ ░ ┌──────────────┐ - q_0: ┤ H ├─░─┤ U1(2.0*x[0]) ├─░─┤ H ├─░─┤ U1(2.0*x[0]) ├─░─┤ H ├─░─┤ U1(2.0*x[0]) ├ - ├───┤ ░ ├──────────────┤ ░ ├───┤ ░ ├──────────────┤ ░ ├───┤ ░ ├──────────────┤ - q_1: ┤ H ├─░─┤ U1(2.0*x[1]) ├─░─┤ H ├─░─┤ U1(2.0*x[1]) ├─░─┤ H ├─░─┤ U1(2.0*x[1]) ├ - ├───┤ ░ ├──────────────┤ ░ ├───┤ ░ ├──────────────┤ ░ ├───┤ ░ ├──────────────┤ - q_2: ┤ H ├─░─┤ U1(2.0*x[2]) ├─░─┤ H ├─░─┤ U1(2.0*x[2]) ├─░─┤ H ├─░─┤ U1(2.0*x[2]) ├ - └───┘ ░ └──────────────┘ ░ └───┘ ░ └──────────────┘ ░ └───┘ ░ └──────────────┘ + >>> print(prep.decompose()) + ┌───┐ ░ ┌─────────────┐ ░ ┌───┐ ░ ┌─────────────┐ ░ ┌───┐ ░ ┌─────────────┐ + q_0: ┤ H ├─░─┤ P(2.0*x[0]) ├─░─┤ H ├─░─┤ P(2.0*x[0]) ├─░─┤ H ├─░─┤ P(2.0*x[0]) ├ + ├───┤ ░ ├─────────────┤ ░ ├───┤ ░ ├─────────────┤ ░ ├───┤ ░ ├─────────────┤ + q_1: ┤ H ├─░─┤ P(2.0*x[1]) ├─░─┤ H ├─░─┤ P(2.0*x[1]) ├─░─┤ H ├─░─┤ P(2.0*x[1]) ├ + ├───┤ ░ ├─────────────┤ ░ ├───┤ ░ ├─────────────┤ ░ ├───┤ ░ ├─────────────┤ + q_2: ┤ H ├─░─┤ P(2.0*x[2]) ├─░─┤ H ├─░─┤ P(2.0*x[2]) ├─░─┤ H ├─░─┤ P(2.0*x[2]) ├ + └───┘ ░ └─────────────┘ ░ └───┘ ░ └─────────────┘ ░ └───┘ ░ └─────────────┘ >>> data_map = lambda x: x[0]*x[0] + 1 # note: input is an array >>> prep = ZFeatureMap(3, reps=1, data_map_func=data_map) - >>> print(prep) - ┌───┐┌───────────────────────┐ - q_0: ┤ H ├┤ U1(2.0*x[0]**2 + 2.0) ├ - ├───┤├───────────────────────┤ - q_1: ┤ H ├┤ U1(2.0*x[1]**2 + 2.0) ├ - ├───┤├───────────────────────┤ - q_2: ┤ H ├┤ U1(2.0*x[2]**2 + 2.0) ├ - └───┘└───────────────────────┘ - - >>> classifier = ZFeatureMap(3, reps=1) + RY(3, reps=1) - >>> print(classifier) - ┌───┐┌──────────────┐┌──────────┐ ┌──────────┐ - q_0: ┤ H ├┤ U1(2.0*x[0]) ├┤ RY(θ[0]) ├─■──■─┤ RY(θ[3]) ├──────────── - ├───┤├──────────────┤├──────────┤ │ │ └──────────┘┌──────────┐ - q_1: ┤ H ├┤ U1(2.0*x[1]) ├┤ RY(θ[1]) ├─■──┼──────■──────┤ RY(θ[4]) ├ - ├───┤├──────────────┤├──────────┤ │ │ ├──────────┤ - q_2: ┤ H ├┤ U1(2.0*x[2]) ├┤ RY(θ[2]) ├────■──────■──────┤ RY(θ[5]) ├ - └───┘└──────────────┘└──────────┘ └──────────┘ + >>> print(prep.decompose()) + ┌───┐┌──────────────────────┐ + q_0: ┤ H ├┤ P(2.0*x[0]**2 + 2.0) ├ + ├───┤├──────────────────────┤ + q_1: ┤ H ├┤ P(2.0*x[1]**2 + 2.0) ├ + ├───┤├──────────────────────┤ + q_2: ┤ H ├┤ P(2.0*x[2]**2 + 2.0) ├ + └───┘└──────────────────────┘ + + >>> from qiskit.circuit.library import TwoLocal + >>> ry = TwoLocal(3, "ry", "cz", reps=1) + >>> classifier = ZFeatureMap(3, reps=1) + ry + >>> print(classifier.decompose()) + ┌───┐┌─────────────┐┌──────────┐ ┌──────────┐ + q_0: ┤ H ├┤ P(2.0*x[0]) ├┤ RY(θ[0]) ├─■──■─┤ RY(θ[3]) ├──────────── + ├───┤├─────────────┤├──────────┤ │ │ └──────────┘┌──────────┐ + q_1: ┤ H ├┤ P(2.0*x[1]) ├┤ RY(θ[1]) ├─■──┼──────■──────┤ RY(θ[4]) ├ + ├───┤├─────────────┤├──────────┤ │ │ ├──────────┤ + q_2: ┤ H ├┤ P(2.0*x[2]) ├┤ RY(θ[2]) ├────■──────■──────┤ RY(θ[5]) ├ + └───┘└─────────────┘└──────────┘ └──────────┘ """ + @deprecate_func( + since="1.3", + additional_msg=( + "Use the z_feature_map function as a replacement. Note that this will no longer " + "return a BlueprintCircuit, but just a plain QuantumCircuit." + ), + pending=True, + ) def __init__( self, feature_dimension: int, diff --git a/qiskit/circuit/library/data_preparation/zz_feature_map.py b/qiskit/circuit/library/data_preparation/_zz_feature_map.py similarity index 71% rename from qiskit/circuit/library/data_preparation/zz_feature_map.py rename to qiskit/circuit/library/data_preparation/_zz_feature_map.py index 2f51b8f63482..2a170513dfdb 100644 --- a/qiskit/circuit/library/data_preparation/zz_feature_map.py +++ b/qiskit/circuit/library/data_preparation/_zz_feature_map.py @@ -14,6 +14,7 @@ from typing import Callable, List, Union, Optional, Dict, Tuple import numpy as np +from qiskit.utils.deprecation import deprecate_func from .pauli_feature_map import PauliFeatureMap @@ -24,13 +25,13 @@ class ZZFeatureMap(PauliFeatureMap): .. parsed-literal:: - ┌───┐┌─────────────────┐ - ┤ H ├┤ U1(2.0*φ(x[0])) ├──■────────────────────────────■──────────────────────────────────── - ├───┤├─────────────────┤┌─┴─┐┌──────────────────────┐┌─┴─┐ - ┤ H ├┤ U1(2.0*φ(x[1])) ├┤ X ├┤ U1(2.0*φ(x[0],x[1])) ├┤ X ├──■────────────────────────────■── - ├───┤├─────────────────┤└───┘└──────────────────────┘└───┘┌─┴─┐┌──────────────────────┐┌─┴─┐ - ┤ H ├┤ U1(2.0*φ(x[2])) ├──────────────────────────────────┤ X ├┤ U1(2.0*φ(x[1],x[2])) ├┤ X ├ - └───┘└─────────────────┘ └───┘└──────────────────────┘└───┘ + ┌───┐┌────────────────┐ + ┤ H ├┤ P(2.0*φ(x[0])) ├──■───────────────────────────■─────────────────────────────────── + ├───┤├────────────────┤┌─┴─┐┌─────────────────────┐┌─┴─┐ + ┤ H ├┤ P(2.0*φ(x[1])) ├┤ X ├┤ P(2.0*φ(x[0],x[1])) ├┤ X ├──■───────────────────────────■── + ├───┤├────────────────┤└───┘└─────────────────────┘└───┘┌─┴─┐┌─────────────────────┐┌─┴─┐ + ┤ H ├┤ P(2.0*φ(x[2])) ├─────────────────────────────────┤ X ├┤ P(2.0*φ(x[1],x[2])) ├┤ X ├ + └───┘└────────────────┘ └───┘└─────────────────────┘└───┘ where :math:`\varphi` is a classical non-linear function, which defaults to :math:`\varphi(x) = x` if and :math:`\varphi(x,y) = (\pi - x)(\pi - y)`. @@ -39,12 +40,12 @@ class ZZFeatureMap(PauliFeatureMap): >>> from qiskit.circuit.library import ZZFeatureMap >>> prep = ZZFeatureMap(2, reps=1) - >>> print(prep) - ┌───┐┌──────────────┐ - q_0: ┤ H ├┤ U1(2.0*x[0]) ├──■───────────────────────────────────────■── - ├───┤├──────────────┤┌─┴─┐┌─────────────────────────────────┐┌─┴─┐ - q_1: ┤ H ├┤ U1(2.0*x[1]) ├┤ X ├┤ U1(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├ - └───┘└──────────────┘└───┘└─────────────────────────────────┘└───┘ + >>> print(prep.decompose()) + ┌───┐┌─────────────┐ + q_0: ┤ H ├┤ P(2.0*x[0]) ├──■──────────────────────────────────────■── + ├───┤├─────────────┤┌─┴─┐┌────────────────────────────────┐┌─┴─┐ + q_1: ┤ H ├┤ P(2.0*x[1]) ├┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├ + └───┘└─────────────┘└───┘└────────────────────────────────┘└───┘ >>> from qiskit.circuit.library import EfficientSU2 >>> classifier = ZZFeatureMap(3) + EfficientSU2(3) @@ -71,6 +72,14 @@ class ZZFeatureMap(PauliFeatureMap): OrderedDict([('ZZFeatureMap', 1), ('EfficientSU2', 1)]) """ + @deprecate_func( + since="1.3", + additional_msg=( + "Use the z_feature_map function as a replacement. Note that this will no longer " + "return a BlueprintCircuit, but just a plain QuantumCircuit." + ), + pending=True, + ) def __init__( self, feature_dimension: int, diff --git a/qiskit/circuit/library/data_preparation/pauli_feature_map.py b/qiskit/circuit/library/data_preparation/pauli_feature_map.py index 8cad7d23835b..a40deb6ea18b 100644 --- a/qiskit/circuit/library/data_preparation/pauli_feature_map.py +++ b/qiskit/circuit/library/data_preparation/pauli_feature_map.py @@ -11,17 +11,316 @@ # that they have been altered from the originals. """The Pauli expansion circuit module.""" -from typing import Optional, Callable, List, Union, Sequence, Dict, Tuple + +from __future__ import annotations + +from collections.abc import Sequence, Mapping +from typing import Optional, Callable, List, Union, Dict, Tuple from functools import reduce import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.circuit import Parameter, ParameterVector +from qiskit.circuit import Parameter, ParameterVector, ParameterExpression from qiskit.circuit.library.standard_gates import HGate +from qiskit.utils.deprecation import deprecate_func +from qiskit._accelerate.circuit_library import pauli_feature_map as _fast_map from ..n_local.n_local import NLocal +def _normalize_entanglement( + entanglement: str | Mapping[int, Sequence[Sequence[int]]] +) -> str | dict[int, list[tuple[int]]]: + if isinstance(entanglement, str): + return entanglement + + return { + num_paulis: [tuple(connections) for connections in ent] + for num_paulis, ent in entanglement.items() + } + + +def pauli_feature_map( + feature_dimension: int, + reps: int = 2, + entanglement: ( + str + | Mapping[int, Sequence[Sequence[int]]] + | Callable[[int], str | Mapping[int, Sequence[Sequence[int]]]] + ) = "full", + alpha: float = 2.0, + paulis: list[str] | None = None, + data_map_func: Callable[[Parameter], ParameterExpression] | None = None, + parameter_prefix: str = "x", + insert_barriers: bool = False, + name: str = "PauliFeatureMap", +) -> QuantumCircuit: + r"""The Pauli expansion circuit. + + The Pauli expansion circuit is a data encoding circuit that transforms input data + :math:`\vec{x} \in \mathbb{R}^n`, where :math:`n` is the ``feature_dimension``, as + + .. math:: + + U_{\Phi(\vec{x})}=\exp\left(i\sum_{S \in \mathcal{I}} + \phi_S(\vec{x})\prod_{i\in S} P_i\right). + + Here, :math:`S` is a set of qubit indices that describes the connections in the feature map, + :math:`\mathcal{I}` is a set containing all these index sets, and + :math:`P_i \in \{I, X, Y, Z\}`. Per default the data-mapping + :math:`\phi_S` is + + .. math:: + + \phi_S(\vec{x}) = \begin{cases} + x_i \text{ if } S = \{i\} \\ + \prod_{j \in S} (\pi - x_j) \text{ if } |S| > 1 + \end{cases}. + + The possible connections can be set using the ``entanglement`` and ``paulis`` arguments. + For example, for single-qubit :math:`Z` rotations and two-qubit :math:`YY` interactions + between all qubit pairs, we can set:: + + + circuit = pauli_feature_map(..., paulis=["Z", "YY"], entanglement="full") + + which will produce blocks of the form + + .. parsed-literal:: + + ┌───┐┌─────────────┐┌──────────┐ ┌───────────┐ + ┤ H ├┤ P(2.0*x[0]) ├┤ RX(pi/2) ├──■──────────────────────────────────────■──┤ RX(-pi/2) ├ + ├───┤├─────────────┤├──────────┤┌─┴─┐┌────────────────────────────────┐┌─┴─┐├───────────┤ + ┤ H ├┤ P(2.0*x[1]) ├┤ RX(pi/2) ├┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├┤ RX(-pi/2) ├ + └───┘└─────────────┘└──────────┘└───┘└────────────────────────────────┘└───┘└───────────┘ + + The circuit contains ``reps`` repetitions of this transformation. + + Please refer to :func:`.z_feature_map` for the case of single-qubit Pauli-:math:`Z` rotations + and to :func:`.zz_feature_map` for the single- and two-qubit Pauli-:math:`Z` rotations. + + Examples: + + >>> prep = pauli_feature_map(2, reps=1, paulis=["ZZ"]) + >>> print(prep) + ┌───┐ + q_0: ┤ H ├──■──────────────────────────────────────■── + ├───┤┌─┴─┐┌────────────────────────────────┐┌─┴─┐ + q_1: ┤ H ├┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├ + └───┘└───┘└────────────────────────────────┘└───┘ + + >>> prep = pauli_feature_map(2, reps=1, paulis=["Z", "XX"]) + >>> print(prep) + ┌───┐┌─────────────┐┌───┐ ┌───┐ + q_0: ┤ H ├┤ P(2.0*x[0]) ├┤ H ├──■──────────────────────────────────────■──┤ H ├ + ├───┤├─────────────┤├───┤┌─┴─┐┌────────────────────────────────┐┌─┴─┐├───┤ + q_1: ┤ H ├┤ P(2.0*x[1]) ├┤ H ├┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├┤ H ├ + └───┘└─────────────┘└───┘└───┘└────────────────────────────────┘└───┘└───┘ + + >>> prep = pauli_feature_map(2, reps=1, paulis=["ZY"]) + >>> print(prep) + ┌───┐┌──────────┐ ┌───────────┐ + q_0: ┤ H ├┤ RX(pi/2) ├──■──────────────────────────────────────■──┤ RX(-pi/2) ├ + ├───┤└──────────┘┌─┴─┐┌────────────────────────────────┐┌─┴─┐└───────────┘ + q_1: ┤ H ├────────────┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├───────────── + └───┘ └───┘└────────────────────────────────┘└───┘ + + >>> from qiskit.circuit.library import EfficientSU2 + >>> prep = pauli_feature_map(3, reps=3, paulis=["Z", "YY", "ZXZ"]) + >>> wavefunction = EfficientSU2(3) + >>> classifier = prep.compose(wavefunction) + >>> classifier.num_parameters + 27 + >>> classifier.count_ops() + OrderedDict([('cx', 39), ('rx', 36), ('u1', 21), ('h', 15), ('ry', 12), ('rz', 12)]) + + References: + + [1] Havlicek et al. Supervised learning with quantum enhanced feature spaces, + `Nature 567, 209-212 (2019) `__. + """ + # create parameter vector used in the Pauli feature map + parameters = ParameterVector(parameter_prefix, feature_dimension) + + # the Rust implementation expects the entanglement to be a str or list[tuple[int]] (or the + # callable to return these types), therefore we normalize the entanglement here + if callable(entanglement): + normalized = lambda offset: _normalize_entanglement(entanglement(offset)) + else: + normalized = _normalize_entanglement(entanglement) + + # construct from Rust + circuit = QuantumCircuit._from_circuit_data( + _fast_map( + feature_dimension, + paulis=paulis, + entanglement=normalized, + reps=reps, + parameters=parameters, + data_map_func=data_map_func, + alpha=alpha, + insert_barriers=insert_barriers, + ) + ) + circuit.name = name + + return circuit + + +def z_feature_map( + feature_dimension: int, + reps: int = 2, + entanglement: ( + str | Sequence[Sequence[int]] | Callable[[int], str | Sequence[Sequence[int]]] + ) = "full", + alpha: float = 2.0, + data_map_func: Callable[[Parameter], ParameterExpression] | None = None, + parameter_prefix: str = "x", + insert_barriers: bool = False, + name: str = "ZFeatureMap", +) -> QuantumCircuit: + """The first order Pauli Z-evolution circuit. + + On 3 qubits and with 2 repetitions the circuit is represented by: + + .. parsed-literal:: + + ┌───┐┌─────────────┐┌───┐┌─────────────┐ + ┤ H ├┤ P(2.0*x[0]) ├┤ H ├┤ P(2.0*x[0]) ├ + ├───┤├─────────────┤├───┤├─────────────┤ + ┤ H ├┤ U(2.0*x[1]) ├┤ H ├┤ P(2.0*x[1]) ├ + ├───┤├─────────────┤├───┤├─────────────┤ + ┤ H ├┤ P(2.0*x[2]) ├┤ H ├┤ P(2.0*x[2]) ├ + └───┘└─────────────┘└───┘└─────────────┘ + + This is a sub-class of :class:`~qiskit.circuit.library.PauliFeatureMap` where the Pauli + strings are fixed as `['Z']`. As a result the first order expansion will be a circuit without + entangling gates. + + Examples: + + >>> prep = z_feature_map(3, reps=3, insert_barriers=True) + >>> print(prep) + ┌───┐ ░ ┌─────────────┐ ░ ┌───┐ ░ ┌─────────────┐ ░ ┌───┐ ░ ┌─────────────┐ + q_0: ┤ H ├─░─┤ P(2.0*x[0]) ├─░─┤ H ├─░─┤ P(2.0*x[0]) ├─░─┤ H ├─░─┤ P(2.0*x[0]) ├ + ├───┤ ░ ├─────────────┤ ░ ├───┤ ░ ├─────────────┤ ░ ├───┤ ░ ├─────────────┤ + q_1: ┤ H ├─░─┤ P(2.0*x[1]) ├─░─┤ H ├─░─┤ P(2.0*x[1]) ├─░─┤ H ├─░─┤ P(2.0*x[1]) ├ + ├───┤ ░ ├─────────────┤ ░ ├───┤ ░ ├─────────────┤ ░ ├───┤ ░ ├─────────────┤ + q_2: ┤ H ├─░─┤ P(2.0*x[2]) ├─░─┤ H ├─░─┤ P(2.0*x[2]) ├─░─┤ H ├─░─┤ P(2.0*x[2]) ├ + └───┘ ░ └─────────────┘ ░ └───┘ ░ └─────────────┘ ░ └───┘ ░ └─────────────┘ + + >>> data_map = lambda x: x[0]*x[0] + 1 # note: input is an array + >>> prep = z_feature_map(3, reps=1, data_map_func=data_map) + >>> print(prep) + ┌───┐┌──────────────────────┐ + q_0: ┤ H ├┤ P(2.0*x[0]**2 + 2.0) ├ + ├───┤├──────────────────────┤ + q_1: ┤ H ├┤ P(2.0*x[1]**2 + 2.0) ├ + ├───┤├──────────────────────┤ + q_2: ┤ H ├┤ P(2.0*x[2]**2 + 2.0) ├ + └───┘└──────────────────────┘ + + >>> from qiskit.circuit.library import TwoLocal + >>> ry = TwoLocal(3, "ry", "cz", reps=1).decompose() + >>> classifier = z_feature_map(3, reps=1) + ry + >>> print(classifier) + ┌───┐┌─────────────┐┌──────────┐ ┌──────────┐ + q_0: ┤ H ├┤ P(2.0*x[0]) ├┤ RY(θ[0]) ├─■──■─┤ RY(θ[3]) ├──────────── + ├───┤├─────────────┤├──────────┤ │ │ └──────────┘┌──────────┐ + q_1: ┤ H ├┤ P(2.0*x[1]) ├┤ RY(θ[1]) ├─■──┼──────■──────┤ RY(θ[4]) ├ + ├───┤├─────────────┤├──────────┤ │ │ ├──────────┤ + q_2: ┤ H ├┤ P(2.0*x[2]) ├┤ RY(θ[2]) ├────■──────■──────┤ RY(θ[5]) ├ + └───┘└─────────────┘└──────────┘ └──────────┘ + + """ + return pauli_feature_map( + feature_dimension=feature_dimension, + reps=reps, + entanglement=entanglement, + alpha=alpha, + paulis=["z"], + data_map_func=data_map_func, + parameter_prefix=parameter_prefix, + insert_barriers=insert_barriers, + name=name, + ) + + +def zz_feature_map( + feature_dimension: int, + reps: int = 2, + entanglement: ( + str | Sequence[Sequence[int]] | Callable[[int], str | Sequence[Sequence[int]]] + ) = "full", + alpha: float = 2.0, + data_map_func: Callable[[Parameter], ParameterExpression] | None = None, + parameter_prefix: str = "x", + insert_barriers: bool = False, + name: str = "ZZFeatureMap", +) -> QuantumCircuit: + r"""Second-order Pauli-Z evolution circuit. + + For 3 qubits and 1 repetition and linear entanglement the circuit is represented by: + + .. parsed-literal:: + + ┌───┐┌────────────────┐ + ┤ H ├┤ P(2.0*φ(x[0])) ├──■───────────────────────────■─────────────────────────────────── + ├───┤├────────────────┤┌─┴─┐┌─────────────────────┐┌─┴─┐ + ┤ H ├┤ P(2.0*φ(x[1])) ├┤ X ├┤ P(2.0*φ(x[0],x[1])) ├┤ X ├──■───────────────────────────■── + ├───┤├────────────────┤└───┘└─────────────────────┘└───┘┌─┴─┐┌─────────────────────┐┌─┴─┐ + ┤ H ├┤ P(2.0*φ(x[2])) ├─────────────────────────────────┤ X ├┤ P(2.0*φ(x[1],x[2])) ├┤ X ├ + └───┘└────────────────┘ └───┘└─────────────────────┘└───┘ + + where :math:`\varphi` is a classical non-linear function, which defaults to :math:`\varphi(x) = x` + if and :math:`\varphi(x,y) = (\pi - x)(\pi - y)`. + + Examples: + + >>> from qiskit.circuit.library import ZZFeatureMap + >>> prep = zz_feature_map(2, reps=1) + >>> print(prep) + ┌───┐┌─────────────┐ + q_0: ┤ H ├┤ P(2.0*x[0]) ├──■──────────────────────────────────────■── + ├───┤├─────────────┤┌─┴─┐┌────────────────────────────────┐┌─┴─┐ + q_1: ┤ H ├┤ P(2.0*x[1]) ├┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├ + └───┘└─────────────┘└───┘└────────────────────────────────┘└───┘ + + >>> from qiskit.circuit.library import EfficientSU2 + >>> classifier = zz_feature_map(3) + EfficientSU2(3) + >>> classifier.num_parameters + 15 + >>> classifier.parameters # 'x' for the data preparation, 'θ' for the SU2 parameters + ParameterView([ + ParameterVectorElement(x[0]), ParameterVectorElement(x[1]), + ParameterVectorElement(x[2]), ParameterVectorElement(θ[0]), + ParameterVectorElement(θ[1]), ParameterVectorElement(θ[2]), + ParameterVectorElement(θ[3]), ParameterVectorElement(θ[4]), + ParameterVectorElement(θ[5]), ParameterVectorElement(θ[6]), + ParameterVectorElement(θ[7]), ParameterVectorElement(θ[8]), + ParameterVectorElement(θ[9]), ParameterVectorElement(θ[10]), + ParameterVectorElement(θ[11]), ParameterVectorElement(θ[12]), + ParameterVectorElement(θ[13]), ParameterVectorElement(θ[14]), + ParameterVectorElement(θ[15]), ParameterVectorElement(θ[16]), + ParameterVectorElement(θ[17]), ParameterVectorElement(θ[18]), + ParameterVectorElement(θ[19]), ParameterVectorElement(θ[20]), + ParameterVectorElement(θ[21]), ParameterVectorElement(θ[22]), + ParameterVectorElement(θ[23]) + ]) + """ + return pauli_feature_map( + feature_dimension=feature_dimension, + reps=reps, + entanglement=entanglement, + alpha=alpha, + paulis=["z", "zz"], + data_map_func=data_map_func, + parameter_prefix=parameter_prefix, + insert_barriers=insert_barriers, + name=name, + ) + + class PauliFeatureMap(NLocal): r"""The Pauli Expansion circuit. @@ -56,11 +355,11 @@ class PauliFeatureMap(NLocal): .. parsed-literal:: - ┌───┐┌──────────────┐┌──────────┐ ┌───────────┐ - ┤ H ├┤ U1(2.0*x[0]) ├┤ RX(pi/2) ├──■───────────────────────────────────────■──┤ RX(-pi/2) ├ - ├───┤├──────────────┤├──────────┤┌─┴─┐┌─────────────────────────────────┐┌─┴─┐├───────────┤ - ┤ H ├┤ U1(2.0*x[1]) ├┤ RX(pi/2) ├┤ X ├┤ U1(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├┤ RX(-pi/2) ├ - └───┘└──────────────┘└──────────┘└───┘└─────────────────────────────────┘└───┘└───────────┘ + ┌───┐┌─────────────┐┌──────────┐ ┌───────────┐ + ┤ H ├┤ P(2.0*x[0]) ├┤ RX(pi/2) ├──■──────────────────────────────────────■──┤ RX(-pi/2) ├ + ├───┤├─────────────┤├──────────┤┌─┴─┐┌────────────────────────────────┐┌─┴─┐├───────────┤ + ┤ H ├┤ P(2.0*x[1]) ├┤ RX(pi/2) ├┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├┤ RX(-pi/2) ├ + └───┘└─────────────┘└──────────┘└───┘└────────────────────────────────┘└───┘└───────────┘ The circuit contains ``reps`` repetitions of this transformation. @@ -70,28 +369,28 @@ class PauliFeatureMap(NLocal): Examples: >>> prep = PauliFeatureMap(2, reps=1, paulis=['ZZ']) - >>> print(prep) + >>> print(prep.decompose()) ┌───┐ - q_0: ┤ H ├──■───────────────────────────────────────■── - ├───┤┌─┴─┐┌─────────────────────────────────┐┌─┴─┐ - q_1: ┤ H ├┤ X ├┤ U1(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├ - └───┘└───┘└─────────────────────────────────┘└───┘ + q_0: ┤ H ├──■──────────────────────────────────────■── + ├───┤┌─┴─┐┌────────────────────────────────┐┌─┴─┐ + q_1: ┤ H ├┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├ + └───┘└───┘└────────────────────────────────┘└───┘ >>> prep = PauliFeatureMap(2, reps=1, paulis=['Z', 'XX']) - >>> print(prep) - ┌───┐┌──────────────┐┌───┐ ┌───┐ - q_0: ┤ H ├┤ U1(2.0*x[0]) ├┤ H ├──■───────────────────────────────────────■──┤ H ├ - ├───┤├──────────────┤├───┤┌─┴─┐┌─────────────────────────────────┐┌─┴─┐├───┤ - q_1: ┤ H ├┤ U1(2.0*x[1]) ├┤ H ├┤ X ├┤ U1(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├┤ H ├ - └───┘└──────────────┘└───┘└───┘└─────────────────────────────────┘└───┘└───┘ + >>> print(prep.decompose()) + ┌───┐┌─────────────┐┌───┐ ┌───┐ + q_0: ┤ H ├┤ P(2.0*x[0]) ├┤ H ├──■──────────────────────────────────────■──┤ H ├ + ├───┤├─────────────┤├───┤┌─┴─┐┌────────────────────────────────┐┌─┴─┐├───┤ + q_1: ┤ H ├┤ P(2.0*x[1]) ├┤ H ├┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├┤ H ├ + └───┘└─────────────┘└───┘└───┘└────────────────────────────────┘└───┘└───┘ >>> prep = PauliFeatureMap(2, reps=1, paulis=['ZY']) - >>> print(prep) - ┌───┐┌──────────┐ ┌───────────┐ - q_0: ┤ H ├┤ RX(pi/2) ├──■───────────────────────────────────────■──┤ RX(-pi/2) ├ - ├───┤└──────────┘┌─┴─┐┌─────────────────────────────────┐┌─┴─┐└───────────┘ - q_1: ┤ H ├────────────┤ X ├┤ U1(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├───────────── - └───┘ └───┘└─────────────────────────────────┘└───┘ + >>> print(prep.decompose()) + ┌───┐┌──────────┐ ┌───────────┐ + q_0: ┤ H ├┤ RX(pi/2) ├──■──────────────────────────────────────■──┤ RX(-pi/2) ├ + ├───┤└──────────┘┌─┴─┐┌────────────────────────────────┐┌─┴─┐└───────────┘ + q_1: ┤ H ├────────────┤ X ├┤ P(2.0*(pi - x[0])*(pi - x[1])) ├┤ X ├───────────── + └───┘ └───┘└────────────────────────────────┘└───┘ >>> from qiskit.circuit.library import EfficientSU2 >>> prep = PauliFeatureMap(3, reps=3, paulis=['Z', 'YY', 'ZXZ']) @@ -104,13 +403,18 @@ class PauliFeatureMap(NLocal): References: - - [1] Havlicek et al. Supervised learning with quantum enhanced feature spaces, `Nature 567, 209-212 (2019) `__. - """ + @deprecate_func( + since="1.3", + additional_msg=( + "Use the pauli_feature_map function as a replacement. Note that this will no longer " + "return a BlueprintCircuit, but just a plain QuantumCircuit." + ), + pending=True, + ) def __init__( self, feature_dimension: Optional[int] = None, @@ -161,6 +465,7 @@ def __init__( name=name, ) + self._prefix = parameter_prefix self._data_map_func = data_map_func or self_product self._paulis = paulis or ["Z", "ZZ"] self._alpha = alpha diff --git a/releasenotes/notes/rust-paulifm-1dc7b1c2dc756614.yaml b/releasenotes/notes/rust-paulifm-1dc7b1c2dc756614.yaml new file mode 100644 index 000000000000..103e792381ab --- /dev/null +++ b/releasenotes/notes/rust-paulifm-1dc7b1c2dc756614.yaml @@ -0,0 +1,16 @@ +--- +features_circuits: + - | + Added circuit library functions :func:`.pauli_feature_map`, :func:`.z_feature_map`, + :func:`.zz_feature_map` to construct Pauli feature map circuits. These functions + are approximately 8x faster than the current circuit library objects, + :class:`.PauliFeatureMap`, :class:`.ZFeatureMap`, and :class:`.ZZFeatureMap`, + and will replace them in the future. Note, that the new functions return a plain + :class:`.QuantumCircuit` instead of a :class:`.BlueprintCircuit`. + + The functions can be used as drop-in replacement:: + + from qiskit.circuit.library import pauli_feature_map, PauliFeatureMap + + fm = pauli_feature_map(20, paulis=["z", "xx", "yyy"]) + also_fm = PauliFeatureMap(20, paulis=["z", "xx", "yyy"]).decompose() diff --git a/test/python/circuit/library/test_pauli_feature_map.py b/test/python/circuit/library/test_pauli_feature_map.py index 0e15af654edd..5d61944d4032 100644 --- a/test/python/circuit/library/test_pauli_feature_map.py +++ b/test/python/circuit/library/test_pauli_feature_map.py @@ -19,7 +19,16 @@ from ddt import ddt, data, unpack from qiskit.circuit import QuantumCircuit, Parameter, ParameterVector -from qiskit.circuit.library import PauliFeatureMap, ZFeatureMap, ZZFeatureMap, HGate +from qiskit.circuit.library import ( + PauliFeatureMap, + ZFeatureMap, + ZZFeatureMap, + HGate, + pauli_feature_map, + z_feature_map, + zz_feature_map, +) +from qiskit.exceptions import QiskitError from qiskit.quantum_info import Operator from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -295,5 +304,289 @@ def test_entanglement_not_specified(self): feat_map.count_ops() +@ddt +class TestPauliFeatureMap(QiskitTestCase): + """Test the Pauli feature map.""" + + @data((2, 3, ["X", "YY"]), (5, 2, ["ZZZXZ", "XZ"])) + @unpack + def test_num_parameters(self, num_qubits, reps, pauli_strings): + """Test the number of parameters equals the number of qubits, independent of reps.""" + encoding = pauli_feature_map(num_qubits, paulis=pauli_strings, reps=reps) + self.assertEqual(encoding.num_parameters, num_qubits) + + def test_pauli_zz_with_barriers(self): + """Test the generation of Pauli blocks.""" + encoding = QuantumCircuit(3) + encoding.compose(pauli_feature_map(3, paulis=["zz"], insert_barriers=True), inplace=True) + + params = encoding.parameters + + def zz(circuit, i, j): + circuit.cx(i, j) + circuit.p(2 * (np.pi - params[i]) * (np.pi - params[j]), j) + circuit.cx(i, j) + + ref = QuantumCircuit(3) + for i in range(2): + ref.h(range(3)) + ref.barrier() + zz(ref, 0, 1) + zz(ref, 0, 2) + zz(ref, 1, 2) + if i == 0: + ref.barrier() + + self.assertEqual(ref, encoding) + + def test_pauli_xyz(self): + """Test the generation of Pauli blocks.""" + encoding = QuantumCircuit(3) + encoding.compose(pauli_feature_map(3, paulis=["xyz"], reps=1), inplace=True) + # encoding = PauliFeatureMap(3, paulis=["XYZ"], reps=1).decompose() + + params = encoding.parameters + + # q_0: ─────────────■────────────────────────■────────────── + # ┌─────────┐┌─┴─┐ ┌─┴─┐┌──────────┐ + # q_1: ┤ Rx(π/2) ├┤ X ├──■──────────────■──┤ X ├┤ Rx(-π/2) ├ + # └──┬───┬──┘└───┘┌─┴─┐┌────────┐┌─┴─┐├───┤└──────────┘ + # q_2: ───┤ H ├────────┤ X ├┤ P(2.8) ├┤ X ├┤ H ├──────────── + # └───┘ └───┘└────────┘└───┘└───┘ + # X on the most-significant, bottom qubit, Z on the top + ref = QuantumCircuit(3) + ref.h(range(3)) + ref.h(2) + ref.rx(np.pi / 2, 1) + ref.cx(0, 1) + ref.cx(1, 2) + ref.p(2 * np.prod([np.pi - p for p in params]), 2) + ref.cx(1, 2) + ref.cx(0, 1) + ref.rx(-np.pi / 2, 1) + ref.h(2) + + self.assertEqual(ref, encoding) + + def test_first_order_circuit(self): + """Test a first order expansion circuit.""" + times = [0.2, 1, np.pi, -1.2] + encoding = z_feature_map(4, reps=3).assign_parameters(times) + + # ┌───┐ ┌────────┐┌───┐ ┌────────┐┌───┐ ┌────────┐ + # q_0: ┤ H ├─┤ P(0.4) ├┤ H ├─┤ P(0.4) ├┤ H ├─┤ P(0.4) ├ + # ├───┤ └┬──────┬┘├───┤ └┬──────┬┘├───┤ └┬──────┬┘ + # q_1: ┤ H ├──┤ P(2) ├─┤ H ├──┤ P(2) ├─┤ H ├──┤ P(2) ├─ + # ├───┤ ┌┴──────┤ ├───┤ ┌┴──────┤ ├───┤ ┌┴──────┤ + # q_2: ┤ H ├─┤ P(2π) ├─┤ H ├─┤ P(2π) ├─┤ H ├─┤ P(2π) ├─ + # ├───┤┌┴───────┴┐├───┤┌┴───────┴┐├───┤┌┴───────┴┐ + # q_3: ┤ H ├┤ P(-2.4) ├┤ H ├┤ P(-2.4) ├┤ H ├┤ P(-2.4) ├ + # └───┘└─────────┘└───┘└─────────┘└───┘└─────────┘ + ref = QuantumCircuit(4) + for _ in range(3): + ref.h([0, 1, 2, 3]) + for i in range(4): + ref.p(2 * times[i], i) + + self.assertTrue(Operator(encoding).equiv(ref)) + + def test_second_order_circuit(self): + """Test a second order expansion circuit.""" + times = [0.2, 1, np.pi] + encoding = zz_feature_map(3, reps=2).assign_parameters(times) + + def zz_evolution(circuit, qubit1, qubit2): + time = (np.pi - times[qubit1]) * (np.pi - times[qubit2]) + circuit.cx(qubit1, qubit2) + circuit.p(2 * time, qubit2) + circuit.cx(qubit1, qubit2) + + # ┌───┐┌────────┐ ┌───┐┌────────┐» + # q_0: ┤ H ├┤ P(0.4) ├──■─────────────────■────■────────────■──┤ H ├┤ P(0.4) ├» + # ├───┤└┬──────┬┘┌─┴─┐┌───────────┐┌─┴─┐ │ │ └───┘└────────┘» + # q_1: ┤ H ├─┤ P(2) ├─┤ X ├┤ P(12.599) ├┤ X ├──┼────────────┼────■────────────» + # ├───┤┌┴──────┤ └───┘└───────────┘└───┘┌─┴─┐┌──────┐┌─┴─┐┌─┴─┐ ┌──────┐ » + # q_2: ┤ H ├┤ P(2π) ├────────────────────────┤ X ├┤ P(0) ├┤ X ├┤ X ├─┤ P(0) ├─» + # └───┘└───────┘ └───┘└──────┘└───┘└───┘ └──────┘ » + # « » + # «q_0: ─────────────────────■─────────────────■────■────────────■───────────────» + # « ┌───┐ ┌──────┐┌─┴─┐┌───────────┐┌─┴─┐ │ │ » + # «q_1: ──■──┤ H ├─┤ P(2) ├┤ X ├┤ P(12.599) ├┤ X ├──┼────────────┼────■──────────» + # « ┌─┴─┐├───┤┌┴──────┤└───┘└───────────┘└───┘┌─┴─┐┌──────┐┌─┴─┐┌─┴─┐┌──────┐» + # «q_2: ┤ X ├┤ H ├┤ P(2π) ├───────────────────────┤ X ├┤ P(0) ├┤ X ├┤ X ├┤ P(0) ├» + # « └───┘└───┘└───────┘ └───┘└──────┘└───┘└───┘└──────┘» + # « + # «q_0: ───── + # « + # «q_1: ──■── + # « ┌─┴─┐ + # «q_2: ┤ X ├ + # « └───┘ + ref = QuantumCircuit(3) + for _ in range(2): + ref.h([0, 1, 2]) + for i in range(3): + ref.p(2 * times[i], i) + zz_evolution(ref, 0, 1) + zz_evolution(ref, 0, 2) + zz_evolution(ref, 1, 2) + + self.assertTrue(Operator(encoding).equiv(ref)) + + @combine(entanglement=["linear", "reverse_linear", "pairwise"]) + def test_zz_entanglement(self, entanglement): + """Test the ZZ feature map works with pairwise, linear and reverse_linear entanglement.""" + num_qubits = 5 + encoding = zz_feature_map(num_qubits, entanglement=entanglement, reps=1) + ops = encoding.count_ops() + expected_ops = {"h": num_qubits, "p": 2 * num_qubits - 1, "cx": 2 * (num_qubits - 1)} + self.assertEqual(ops, expected_ops) + + def test_pauli_alpha(self): + """Test Pauli rotation factor (getter, setter).""" + alpha = 1.234 + + # this is needed as the outcoming Rust circuit has no qreg + encoding = QuantumCircuit(1) + encoding.compose(pauli_feature_map(1, alpha=alpha, paulis=["z"], reps=1), inplace=True) + + ref = QuantumCircuit(1) + ref.h(0) + ref.p(alpha * encoding.parameters[0], 0) + + self.assertEqual(ref, encoding) + + def test_zzfeaturemap_raises_if_too_small(self): + """Test the ``ZZFeatureMap`` raises an error if the number of qubits is smaller than 2.""" + with self.assertRaises(QiskitError): + _ = zz_feature_map(1) + + def test_dict_entanglement(self): + """Test passing the entanglement as dictionary.""" + entanglement = {1: [(0,), (2,)], 2: [(1, 2)], 3: [(0, 1, 2)]} + circuit = QuantumCircuit(3) + circuit.compose( + pauli_feature_map(3, reps=1, paulis=["z", "xx", "yyy"], entanglement=entanglement), + inplace=True, + ) + x = circuit.parameters + + ref = QuantumCircuit(3) + ref.h(ref.qubits) + + ref.p(2 * x[0], 0) + ref.p(2 * x[2], 2) + + ref.h([1, 2]) + ref.cx(1, 2) + ref.p(2 * np.prod([np.pi - xi for xi in [x[1], x[2]]]), 2) + ref.cx(1, 2) + ref.h([1, 2]) + + ref.rx(np.pi / 2, range(3)) + ref.cx(0, 1) + ref.cx(1, 2) + ref.p(2 * np.prod([np.pi - xi for xi in x]), 2) + ref.cx(1, 2) + ref.cx(0, 1) + ref.rx(-np.pi / 2, range(3)) + + self.assertEqual(ref, circuit) + + def test_invalid_entanglement(self): + """Test if a ValueError is raised when an invalid entanglement is passed""" + n_qubits = 3 + entanglement = { + 1: [(0, 1), (2,)], + 2: [(0, 1), (1, 2)], + 3: [(0, 1, 2)], + } + + with self.assertRaises(QiskitError): + _ = pauli_feature_map( + n_qubits, reps=2, paulis=["Z", "ZZ", "ZZZ"], entanglement=entanglement + ) + + def test_entanglement_not_specified(self): + """Test if an error is raised when entanglement is not explicitly specified for + all n-qubit pauli blocks""" + n_qubits = 3 + entanglement = { + 1: [(0, 1), (2,)], + 3: [(0, 1, 2)], + } + with self.assertRaises(QiskitError): + _ = pauli_feature_map( + n_qubits, reps=2, paulis=["Z", "ZZ", "ZZZ"], entanglement=entanglement + ) + + def test_parameter_prefix(self): + """Test the Parameter prefix""" + encoding_pauli = pauli_feature_map( + feature_dimension=2, reps=2, paulis=["ZY"], parameter_prefix="p" + ) + encoding_z = z_feature_map(feature_dimension=2, reps=2, parameter_prefix="q") + encoding_zz = zz_feature_map(feature_dimension=2, reps=2, parameter_prefix="r") + x = ParameterVector("x", 2) + y = Parameter("y") + + self.assertEqual( + str(encoding_pauli.parameters), + "ParameterView([ParameterVectorElement(p[0]), ParameterVectorElement(p[1])])", + ) + self.assertEqual( + str(encoding_z.parameters), + "ParameterView([ParameterVectorElement(q[0]), ParameterVectorElement(q[1])])", + ) + self.assertEqual( + str(encoding_zz.parameters), + "ParameterView([ParameterVectorElement(r[0]), ParameterVectorElement(r[1])])", + ) + + encoding_pauli_param_x = encoding_pauli.assign_parameters(x) + encoding_z_param_x = encoding_z.assign_parameters(x) + encoding_zz_param_x = encoding_zz.assign_parameters(x) + + self.assertEqual( + str(encoding_pauli_param_x.parameters), + "ParameterView([ParameterVectorElement(x[0]), ParameterVectorElement(x[1])])", + ) + self.assertEqual( + str(encoding_z_param_x.parameters), + "ParameterView([ParameterVectorElement(x[0]), ParameterVectorElement(x[1])])", + ) + self.assertEqual( + str(encoding_zz_param_x.parameters), + "ParameterView([ParameterVectorElement(x[0]), ParameterVectorElement(x[1])])", + ) + + encoding_pauli_param_y = encoding_pauli.assign_parameters([1, y]) + encoding_z_param_y = encoding_z.assign_parameters([1, y]) + encoding_zz_param_y = encoding_zz.assign_parameters([1, y]) + + self.assertEqual(str(encoding_pauli_param_y.parameters), "ParameterView([Parameter(y)])") + self.assertEqual(str(encoding_z_param_y.parameters), "ParameterView([Parameter(y)])") + self.assertEqual(str(encoding_zz_param_y.parameters), "ParameterView([Parameter(y)])") + + def test_custom_data_mapping(self): + """Test passing a custom data mapping function.""" + + def my_mapping(x): + return 42 if len(x) == 1 else np.sum(x) + + encoding = QuantumCircuit(2) + encoding.compose(zz_feature_map(2, reps=1, data_map_func=my_mapping), inplace=True) + + params = encoding.parameters + ref = QuantumCircuit(2) + ref.h(range(2)) + ref.p(2 * 42, range(2)) + ref.cx(0, 1) + ref.p(2 * (params[0] + params[1]), 1) + ref.cx(0, 1) + + self.assertEqual(ref, encoding) + + if __name__ == "__main__": unittest.main() From b083de6cdefac86c47171f4b8e4ef2913979bebc Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 12 Sep 2024 11:00:04 -0400 Subject: [PATCH 2/5] Revert version number back to 1.3.0 after beta pre-release (#13142) Now that the 1.3.0b1 pre-release has been published this commit reverts the version number back to 1.3.0 to differentiate development builds from the published pre-release. --- Cargo.lock | 269 ++++++++++++++++++++++++--------------------- docs/conf.py | 2 +- qiskit/VERSION.txt | 2 +- 3 files changed, 146 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d00620630e79..d73b21266dab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" @@ -94,9 +94,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -129,13 +129,13 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -184,24 +184,24 @@ checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" [[package]] name = "cov-mark" -version = "2.0.0-pre.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d48d8f76bd9331f19fe2aaf3821a9f9fb32c3963e1e3d6ce82a8c09cef7444a" +checksum = "0570650661aa447e7335f1d5e4f499d8e58796e617bedc9267d971e51c8b49d4" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -227,9 +227,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -281,20 +281,20 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -323,7 +323,7 @@ checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -334,7 +334,7 @@ checksum = "5322a90066ddae2b705096eb9e10c465c0498ae93bf9bdd6437415327c88e3bb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -533,9 +533,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -580,11 +580,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "indexmap" @@ -605,9 +611,9 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", @@ -655,9 +661,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" @@ -677,9 +683,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matrixcompare" @@ -699,9 +705,9 @@ checksum = "b0bdabb30db18805d5290b3da7ceaccbddba795620b86c02145d688e04900a73" [[package]] name = "matrixmultiply" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" dependencies = [ "autocfg", "rawpointer", @@ -709,9 +715,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" @@ -969,9 +975,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -987,20 +993,20 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pest" -version = "2.7.10" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" dependencies = [ "memchr", "thiserror", @@ -1009,9 +1015,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.10" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" dependencies = [ "pest", "pest_generator", @@ -1019,22 +1025,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.10" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] name = "pest_meta" -version = "2.7.10" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" dependencies = [ "once_cell", "pest", @@ -1053,21 +1059,24 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "priority-queue" -version = "2.0.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70c501afe3a2e25c9bd219aa56ec1e04cdb3fcdd763055be268778c13fa82c1f" +checksum = "560bcab673ff7f6ca9e270c17bf3affd8a05e3bd9207f123b0d45076fd8197e8" dependencies = [ "autocfg", "equivalent", @@ -1128,7 +1137,7 @@ checksum = "d315b3197b780e4873bc0e11251cb56a33f65a6032a3d39b8d1405c255513766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1196,7 +1205,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1205,11 +1214,11 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1296,9 +1305,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1426,11 +1435,11 @@ checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -1483,9 +1492,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rustworkx-core" @@ -1530,22 +1539,22 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.200" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1567,9 +1576,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smol_str" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" dependencies = [ "serde", ] @@ -1587,9 +1596,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1602,7 +1611,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "byteorder", "enum-as-inner", "libc", @@ -1612,9 +1621,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.14" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "text-size" @@ -1639,7 +1648,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1662,27 +1671,27 @@ checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "unindent" @@ -1692,9 +1701,9 @@ checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -1730,11 +1739,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1773,7 +1782,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -1793,18 +1811,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1815,9 +1833,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -1827,9 +1845,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -1839,15 +1857,15 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -1857,9 +1875,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -1869,9 +1887,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -1881,9 +1899,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -1893,9 +1911,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "xshell" @@ -1920,20 +1938,21 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] diff --git a/docs/conf.py b/docs/conf.py index 493dd58de83c..a84a1ff691bf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,7 +32,7 @@ # The short X.Y version version = "1.3" # The full version, including alpha/beta/rc tags -release = "1.3.0b1" +release = "1.3.0" language = "en" diff --git a/qiskit/VERSION.txt b/qiskit/VERSION.txt index 94bfba8ed4ed..f0bb29e76388 100644 --- a/qiskit/VERSION.txt +++ b/qiskit/VERSION.txt @@ -1 +1 @@ -1.3.0b1 +1.3.0 From 4c88a71237ccfc528e8a0b3704a53a3375cab8be Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 12 Sep 2024 15:28:26 -0400 Subject: [PATCH 3/5] Oxidize dag_to_circuit() (#13126) * Oxidize dag_to_circuit() This commit migrates the dag_to_circuit() converter function implementation to rust. The core of this is having a DAGCircuit to CircuitData converter function in rust. To do this efficiently we just copy the BitDatas and Interners to the new circuit data object being created and then just iterate over all the packed instructions in the circuit. To facilitate this a new constructor for CircuitData is added that takes an iterator of PackedInstructions and the BitDatas and Interners and builds a new circuit data from that. * Apply suggestions from code review Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * Update crates/circuit/src/converters.rs Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * Update crates/circuit/src/converters.rs Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * Use immutable access methods instead of relying on public fields * Add docstring to the new CircuitData constructor method --------- Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> --- crates/circuit/src/circuit_data.rs | 57 +++++++++++++++++++++++++++++ crates/circuit/src/converters.rs | 51 +++++++++++++++++++++++++- crates/circuit/src/dag_circuit.rs | 4 +- qiskit/converters/dag_to_circuit.py | 6 ++- 4 files changed, 113 insertions(+), 5 deletions(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index da49f9edc605..b367a6877c11 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -166,6 +166,63 @@ impl CircuitData { Ok(res) } + /// A constructor for CircuitData from an iterator of PackedInstruction objects + /// + /// This is tpically useful when iterating over a CircuitData or DAGCircuit + /// to construct a new CircuitData from the iterator of PackedInstructions. As + /// such it requires that you have `BitData` and `Interner` objects to run. If + /// you just wish to build a circuit data from an iterator of instructions + /// the `from_packed_operations` or `from_standard_gates` constructor methods + /// are a better choice + /// + /// # Args + /// + /// * py: A GIL handle this is needed to instantiate Qubits in Python space + /// * qubits: The BitData to use for the new circuit's qubits + /// * clbits: The BitData to use for the new circuit's clbits + /// * qargs_interner: The interner for Qubit objects in the circuit. This must + /// contain all the Interned indices stored in the + /// PackedInstructions from `instructions` + /// * cargs_interner: The interner for Clbit objects in the circuit. This must + /// contain all the Interned indices stored in the + /// PackedInstructions from `instructions` + /// * Instructions: An iterator with items of type: `PyResult` + /// that contais the instructions to insert in iterator order to the new + /// CircuitData. This returns a `PyResult` to facilitate the case where + /// you need to make a python copy (such as with `PackedOperation::py_deepcopy()`) + /// of the operation while iterating for constructing the new `CircuitData`. An + /// example of this use case is in `qiskit_circuit::converters::dag_to_circuit`. + /// * global_phase: The global phase value to use for the new circuit. + pub fn from_packed_instructions( + py: Python, + qubits: BitData, + clbits: BitData, + qargs_interner: Interner<[Qubit]>, + cargs_interner: Interner<[Clbit]>, + instructions: I, + global_phase: Param, + ) -> PyResult + where + I: IntoIterator>, + { + let instruction_iter = instructions.into_iter(); + let mut res = CircuitData { + data: Vec::with_capacity(instruction_iter.size_hint().0), + qargs_interner, + cargs_interner, + qubits, + clbits, + param_table: ParameterTable::new(), + global_phase, + }; + + for inst in instruction_iter { + res.data.push(inst?); + res.track_instruction_parameters(py, res.data.len() - 1)?; + } + Ok(res) + } + /// An alternate constructor to build a new `CircuitData` from an iterator /// of standard gates. This can be used to build a circuit from a sequence /// of standard gates, such as for a `StandardGate` definition or circuit diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs index ab9d4bce0475..37ba83ae5173 100644 --- a/crates/circuit/src/converters.rs +++ b/crates/circuit/src/converters.rs @@ -10,6 +10,9 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +#[cfg(feature = "cache_pygates")] +use std::cell::OnceCell; + use ::pyo3::prelude::*; use hashbrown::HashMap; use pyo3::{ @@ -17,7 +20,9 @@ use pyo3::{ types::{PyDict, PyList}, }; -use crate::{circuit_data::CircuitData, dag_circuit::DAGCircuit}; +use crate::circuit_data::CircuitData; +use crate::dag_circuit::{DAGCircuit, NodeType}; +use crate::packed_instruction::PackedInstruction; /// An extractable representation of a QuantumCircuit reserved only for /// conversion purposes. @@ -85,7 +90,51 @@ pub fn circuit_to_dag( ) } +#[pyfunction(signature = (dag, copy_operations = true))] +pub fn dag_to_circuit( + py: Python, + dag: &DAGCircuit, + copy_operations: bool, +) -> PyResult { + CircuitData::from_packed_instructions( + py, + dag.qubits().clone(), + dag.clbits().clone(), + dag.qargs_interner().clone(), + dag.cargs_interner().clone(), + dag.topological_op_nodes()?.map(|node_index| { + let NodeType::Operation(ref instr) = dag.dag()[node_index] else { + unreachable!( + "The received node from topological_op_nodes() is not an Operation node." + ) + }; + if copy_operations { + let op = instr.op.py_deepcopy(py, None)?; + Ok(PackedInstruction { + op, + qubits: instr.qubits, + clbits: instr.clbits, + params: Some(Box::new( + instr + .params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(), + )), + extra_attrs: instr.extra_attrs.clone(), + #[cfg(feature = "cache_pygates")] + py_op: OnceCell::new(), + }) + } else { + Ok(instr.clone()) + } + }), + dag.get_global_phase(), + ) +} + pub fn converters(m: &Bound) -> PyResult<()> { m.add_function(wrap_pyfunction!(circuit_to_dag, m)?)?; + m.add_function(wrap_pyfunction!(dag_to_circuit, m)?)?; Ok(()) } diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index d872806c5315..0e944789d307 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -782,7 +782,7 @@ impl DAGCircuit { /// Return the global phase of the circuit. #[getter] - fn get_global_phase(&self) -> Param { + pub fn get_global_phase(&self) -> Param { self.global_phase.clone() } @@ -6375,7 +6375,7 @@ impl DAGCircuit { params: (!new_op.params.is_empty()).then(|| Box::new(new_op.params)), extra_attrs: new_op.extra_attrs, #[cfg(feature = "cache_pygates")] - py_op: py_op, + py_op, }; let new_index = self.dag.add_node(NodeType::Operation(inst)); self.dag.add_edge(source, new_index, weight.clone()); diff --git a/qiskit/converters/dag_to_circuit.py b/qiskit/converters/dag_to_circuit.py index 47adee456380..84ea6ef0fd24 100644 --- a/qiskit/converters/dag_to_circuit.py +++ b/qiskit/converters/dag_to_circuit.py @@ -13,6 +13,7 @@ """Helper function for converting a dag to a circuit.""" from qiskit.circuit import QuantumCircuit +from qiskit._accelerate.converters import dag_to_circuit as dag_to_circuit_rs def dag_to_circuit(dag, copy_operations=True): @@ -54,6 +55,8 @@ def dag_to_circuit(dag, copy_operations=True): """ name = dag.name or None + + circuit_data = dag_to_circuit_rs(dag, copy_operations) circuit = QuantumCircuit( dag.qubits, dag.clbits, @@ -69,8 +72,7 @@ def dag_to_circuit(dag, copy_operations=True): circuit.metadata = dag.metadata circuit.calibrations = dag.calibrations - for node in dag.topological_op_nodes(): - circuit._append(node._to_circuit_instruction(deepcopy=copy_operations)) + circuit._data = circuit_data circuit.duration = dag.duration circuit.unit = dag.unit From c655acb51bf41d70e6c22cdf176f3cad6334198d Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Thu, 12 Sep 2024 16:23:26 -0400 Subject: [PATCH 4/5] Move `Option>` into `ExtraInstructionAttributes`. (#13127) * Move Option> into ExtraInstructionAttributes. Previously, CircuitInstruction and PackedInstruction held the ExtraInstructionAttributes struct within an Option>. By putting the Option> inside ExtraInstructionAttributes, we can use the struct itself to manage its memory and provide access to the attributes behind a nicer interface. The size of ExtraInstructionAttributes should be the same size as the old Option>, so this should not have memory implications. * Address review comments. - Use tuple struct. - Use 'Attributes' over 'Attrs'. - Add doc comment for internal 'ExtraAttributes' struct. - Add doc comments for methods. - Add setters for unit and duration. * Fix performance regression from unnecessary dict creation. --- crates/accelerate/src/commutation_analysis.rs | 4 +- crates/accelerate/src/commutation_checker.rs | 20 +- crates/circuit/src/circuit_data.rs | 17 +- crates/circuit/src/circuit_instruction.rs | 205 +++++++++++++----- crates/circuit/src/dag_circuit.rs | 111 ++-------- crates/circuit/src/dag_node.rs | 43 +--- crates/circuit/src/operations.rs | 18 +- crates/circuit/src/packed_instruction.rs | 12 +- 8 files changed, 209 insertions(+), 221 deletions(-) diff --git a/crates/accelerate/src/commutation_analysis.rs b/crates/accelerate/src/commutation_analysis.rs index e8b47658b9f4..a29c648a5f81 100644 --- a/crates/accelerate/src/commutation_analysis.rs +++ b/crates/accelerate/src/commutation_analysis.rs @@ -95,12 +95,12 @@ pub(crate) fn analyze_commutations_inner( py, &op1, params1, - packed_inst0.extra_attrs.as_deref(), + &packed_inst0.extra_attrs, qargs1, cargs1, &op2, params2, - packed_inst1.extra_attrs.as_deref(), + &packed_inst1.extra_attrs, qargs2, cargs2, MAX_NUM_QUBITS, diff --git a/crates/accelerate/src/commutation_checker.rs b/crates/accelerate/src/commutation_checker.rs index c7fbd03a2afe..b6e61fcf3f6d 100644 --- a/crates/accelerate/src/commutation_checker.rs +++ b/crates/accelerate/src/commutation_checker.rs @@ -121,12 +121,12 @@ impl CommutationChecker { py, &op1.instruction.operation.view(), &op1.instruction.params, - op1.instruction.extra_attrs.as_deref(), + &op1.instruction.extra_attrs, &qargs1, &cargs1, &op2.instruction.operation.view(), &op2.instruction.params, - op2.instruction.extra_attrs.as_deref(), + &op2.instruction.extra_attrs, &qargs2, &cargs2, max_num_qubits, @@ -162,12 +162,12 @@ impl CommutationChecker { py, &op1.operation.view(), &op1.params, - op1.extra_attrs.as_deref(), + &op1.extra_attrs, &qargs1, &cargs1, &op2.operation.view(), &op2.params, - op2.extra_attrs.as_deref(), + &op2.extra_attrs, &qargs2, &cargs2, max_num_qubits, @@ -232,12 +232,12 @@ impl CommutationChecker { py: Python, op1: &OperationRef, params1: &[Param], - attrs1: Option<&ExtraInstructionAttributes>, + attrs1: &ExtraInstructionAttributes, qargs1: &[Qubit], cargs1: &[Clbit], op2: &OperationRef, params2: &[Param], - attrs2: Option<&ExtraInstructionAttributes>, + attrs2: &ExtraInstructionAttributes, qargs2: &[Qubit], cargs2: &[Clbit], max_num_qubits: u32, @@ -494,20 +494,20 @@ impl CommutationChecker { fn commutation_precheck( op1: &OperationRef, params1: &[Param], - attrs1: Option<&ExtraInstructionAttributes>, + attrs1: &ExtraInstructionAttributes, qargs1: &[Qubit], cargs1: &[Clbit], op2: &OperationRef, params2: &[Param], - attrs2: Option<&ExtraInstructionAttributes>, + attrs2: &ExtraInstructionAttributes, qargs2: &[Qubit], cargs2: &[Clbit], max_num_qubits: u32, ) -> Option { if op1.control_flow() || op2.control_flow() - || attrs1.is_some_and(|attr| attr.condition.is_some()) - || attrs2.is_some_and(|attr| attr.condition.is_some()) + || attrs1.condition().is_some() + || attrs2.condition().is_some() { return Some(false); } diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index b367a6877c11..0308bd8dfb8b 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -14,7 +14,9 @@ use std::cell::OnceCell; use crate::bit_data::BitData; -use crate::circuit_instruction::{CircuitInstruction, OperationFromPython}; +use crate::circuit_instruction::{ + CircuitInstruction, ExtraInstructionAttributes, OperationFromPython, +}; use crate::imports::{ANNOTATED_OPERATION, CLBIT, QUANTUM_CIRCUIT, QUBIT}; use crate::interner::{Interned, Interner}; use crate::operations::{Operation, OperationRef, Param, StandardGate}; @@ -157,7 +159,7 @@ impl CircuitData { qubits, clbits, params, - extra_attrs: None, + extra_attrs: ExtraInstructionAttributes::default(), #[cfg(feature = "cache_pygates")] py_op: OnceCell::new(), }); @@ -266,7 +268,7 @@ impl CircuitData { qubits, clbits: no_clbit_index, params, - extra_attrs: None, + extra_attrs: ExtraInstructionAttributes::default(), #[cfg(feature = "cache_pygates")] py_op: OnceCell::new(), }); @@ -324,7 +326,7 @@ impl CircuitData { qubits, clbits: no_clbit_index, params, - extra_attrs: None, + extra_attrs: ExtraInstructionAttributes::default(), #[cfg(feature = "cache_pygates")] py_op: OnceCell::new(), }); @@ -683,12 +685,7 @@ impl CircuitData { #[pyo3(signature = (func))] pub fn map_nonstandard_ops(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { for inst in self.data.iter_mut() { - if inst.op.try_standard_gate().is_some() - && !inst - .extra_attrs - .as_ref() - .is_some_and(|attrs| attrs.condition.is_some()) - { + if inst.op.try_standard_gate().is_some() && inst.extra_attrs.condition().is_none() { continue; } let py_op = func.call1((inst.unpack_py_op(py)?,))?; diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 463f3352aa92..a56aee2c6137 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -30,18 +30,23 @@ use crate::operations::{ }; use crate::packed_instruction::PackedOperation; -/// These are extra mutable attributes for a circuit instruction's state. In general we don't -/// typically deal with this in rust space and the majority of the time they're not used in Python -/// space either. To save memory these are put in a separate struct and are stored inside a -/// `Box` on `CircuitInstruction` and `PackedInstruction`. +/// This is a private struct used to hold the actual attributes, which we store +/// on the heap using the [Box] within [ExtraInstructionAttributes]. #[derive(Debug, Clone)] -pub struct ExtraInstructionAttributes { - pub label: Option, - pub duration: Option, - pub unit: Option, - pub condition: Option, +struct ExtraAttributes { + label: Option, + duration: Option, + unit: Option, + condition: Option, } +/// Extra mutable attributes for a circuit instruction's state. In general we don't +/// typically deal with this in rust space and the majority of the time they're not used in Python +/// space either. To save memory, the attributes are stored inside a `Box` internally, so this +/// struct is no larger than that. +#[derive(Default, Debug, Clone)] +pub struct ExtraInstructionAttributes(Option>); + impl ExtraInstructionAttributes { /// Construct a new set of the extra attributes if any of the elements are not `None`, or return /// `None` if there is no need for an object. @@ -51,25 +56,32 @@ impl ExtraInstructionAttributes { duration: Option>, unit: Option, condition: Option>, - ) -> Option { - if label.is_some() || duration.is_some() || unit.is_some() || condition.is_some() { - Some(Self { - label, - duration, - unit, - condition, - }) - } else { - None - } + ) -> Self { + ExtraInstructionAttributes( + if label.is_some() || duration.is_some() || unit.is_some() || condition.is_some() { + Some(Box::new(ExtraAttributes { + label, + duration, + unit, + condition, + })) + } else { + None + }, + ) } - /// Get the Python-space version of the stored `unit`. This evalutes the Python-space default - /// (`"dt"`) value if we're storing a `None`. + /// Get the Python-space version of the stored `unit`. + /// This evaluates the Python-space default (`"dt"`) value if we're storing a `None`. pub fn py_unit(&self, py: Python) -> Py { - self.unit + self.0 .as_deref() - .map(|unit| <&str as IntoPy>>::into_py(unit, py)) + .and_then(|attrs| { + attrs + .unit + .as_deref() + .map(|unit| <&str as IntoPy>>::into_py(unit, py)) + }) .unwrap_or_else(|| Self::default_unit(py).clone().unbind()) } @@ -77,6 +89,106 @@ impl ExtraInstructionAttributes { pub fn default_unit(py: Python) -> &Bound { intern!(py, "dt") } + + /// Get the stored label attribute. + pub fn label(&self) -> Option<&str> { + self.0.as_deref().and_then(|attrs| attrs.label.as_deref()) + } + + /// Set the stored label attribute, or clear it if `label` is `None`. + pub fn set_label(&mut self, label: Option) { + if let Some(attrs) = &mut self.0 { + attrs.label = label; + self.shrink_if_empty(); + return; + } + if label.is_some() { + self.0 = Some(Box::new(ExtraAttributes { + label, + duration: None, + unit: None, + condition: None, + })) + } + } + + /// Get the stored duration attribute. + pub fn duration(&self) -> Option<&PyObject> { + self.0.as_deref().and_then(|attrs| attrs.duration.as_ref()) + } + + /// Set the stored duration attribute, or clear it if `duration` is `None`. + pub fn set_duration(&mut self, duration: Option) { + if let Some(attrs) = &mut self.0 { + attrs.duration = duration; + self.shrink_if_empty(); + return; + } + if duration.is_some() { + self.0 = Some(Box::new(ExtraAttributes { + label: None, + duration, + unit: None, + condition: None, + })) + } + } + + /// Get the unit attribute. + pub fn unit(&self) -> Option<&str> { + self.0.as_deref().and_then(|attrs| attrs.unit.as_deref()) + } + + /// Set the stored unit attribute, or clear it if `unit` is `None`. + pub fn set_unit(&mut self, unit: Option) { + if let Some(attrs) = &mut self.0 { + attrs.unit = unit; + self.shrink_if_empty(); + return; + } + if unit.is_some() { + self.0 = Some(Box::new(ExtraAttributes { + label: None, + duration: None, + unit, + condition: None, + })) + } + } + + /// Get the condition attribute. + pub fn condition(&self) -> Option<&PyObject> { + self.0.as_deref().and_then(|attrs| attrs.condition.as_ref()) + } + + /// Set the stored condition attribute, or clear it if `condition` is `None`. + pub fn set_condition(&mut self, condition: Option) { + if let Some(attrs) = &mut self.0 { + attrs.condition = condition; + self.shrink_if_empty(); + return; + } + if condition.is_some() { + self.0 = Some(Box::new(ExtraAttributes { + label: None, + duration: None, + unit: None, + condition, + })) + } + } + + fn shrink_if_empty(&mut self) { + if let Some(attrs) = &self.0 { + if attrs.label.is_none() + && attrs.duration.is_none() + && attrs.unit.is_none() + && attrs.condition.is_none() + { + self.0 = None; + } + } + } } /// A single instruction in a :class:`.QuantumCircuit`, comprised of the :attr:`operation` and @@ -122,7 +234,7 @@ pub struct CircuitInstruction { #[pyo3(get)] pub clbits: Py, pub params: SmallVec<[Param; 3]>, - pub extra_attrs: Option>, + pub extra_attrs: ExtraInstructionAttributes, #[cfg(feature = "cache_pygates")] pub py_op: OnceCell>, } @@ -146,9 +258,7 @@ impl CircuitInstruction { } pub fn condition(&self) -> Option<&PyObject> { - self.extra_attrs - .as_ref() - .and_then(|args| args.condition.as_ref()) + self.extra_attrs.condition() } } @@ -189,14 +299,7 @@ impl CircuitInstruction { qubits: as_tuple(py, qubits)?.unbind(), clbits: PyTuple::empty_bound(py).unbind(), params, - extra_attrs: label.map(|label| { - Box::new(ExtraInstructionAttributes { - label: Some(label), - duration: None, - unit: None, - condition: None, - }) - }), + extra_attrs: ExtraInstructionAttributes::new(label, None, None, None), #[cfg(feature = "cache_pygates")] py_op: OnceCell::new(), }) @@ -227,7 +330,7 @@ impl CircuitInstruction { let out = match self.operation.view() { OperationRef::Standard(standard) => standard - .create_py_op(py, Some(&self.params), self.extra_attrs.as_deref())? + .create_py_op(py, Some(&self.params), &self.extra_attrs)? .into_any(), OperationRef::Gate(gate) => gate.gate.clone_ref(py), OperationRef::Instruction(instruction) => instruction.instruction.clone_ref(py), @@ -261,37 +364,24 @@ impl CircuitInstruction { #[getter] fn label(&self) -> Option<&str> { - self.extra_attrs - .as_ref() - .and_then(|attrs| attrs.label.as_deref()) + self.extra_attrs.label() } #[getter] fn get_condition(&self, py: Python) -> Option { - self.extra_attrs - .as_ref() - .and_then(|attrs| attrs.condition.as_ref().map(|x| x.clone_ref(py))) + self.extra_attrs.condition().map(|x| x.clone_ref(py)) } #[getter] fn duration(&self, py: Python) -> Option { - self.extra_attrs - .as_ref() - .and_then(|attrs| attrs.duration.as_ref().map(|x| x.clone_ref(py))) + self.extra_attrs.duration().map(|x| x.clone_ref(py)) } #[getter] fn unit(&self, py: Python) -> Py { // Python space uses `"dt"` as the default, whereas we simply don't store the extra // attributes at all if they're none. - self.extra_attrs - .as_ref() - .map(|attrs| attrs.py_unit(py)) - .unwrap_or_else(|| { - ExtraInstructionAttributes::default_unit(py) - .clone() - .unbind() - }) + self.extra_attrs.py_unit(py) } /// Is the :class:`.Operation` contained in this instruction a Qiskit standard gate? @@ -524,7 +614,7 @@ impl CircuitInstruction { pub struct OperationFromPython { pub operation: PackedOperation, pub params: SmallVec<[Param; 3]>, - pub extra_attrs: Option>, + pub extra_attrs: ExtraInstructionAttributes, } impl<'py> FromPyObject<'py> for OperationFromPython { @@ -558,8 +648,7 @@ impl<'py> FromPyObject<'py> for OperationFromPython { ob.getattr(intern!(py, "duration"))?.extract()?, unit, ob.getattr(intern!(py, "condition"))?.extract()?, - ) - .map(Box::from)) + )) }; 'standard: { @@ -640,7 +729,7 @@ impl<'py> FromPyObject<'py> for OperationFromPython { return Ok(OperationFromPython { operation: PackedOperation::from_operation(operation), params, - extra_attrs: None, + extra_attrs: ExtraInstructionAttributes::default(), }); } Err(PyTypeError::new_err(format!("invalid input: {}", ob))) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 0e944789d307..5b218b2de818 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -2485,16 +2485,8 @@ def _format(operand): true }; let check_conditions = || -> PyResult { - if let Some(cond1) = inst1 - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.condition.as_ref()) - { - if let Some(cond2) = inst2 - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.condition.as_ref()) - { + if let Some(cond1) = inst1.extra_attrs.condition() { + if let Some(cond2) = inst2.extra_attrs.condition() { legacy_condition_eq .call1((cond1, cond2, &self_bit_indices, &other_bit_indices))? .extract::() @@ -2502,11 +2494,7 @@ def _format(operand): Ok(false) } } else { - Ok(inst2 - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.condition.as_ref()) - .is_none()) + Ok(inst2.extra_attrs.condition().is_none()) } }; @@ -3067,11 +3055,7 @@ def _format(operand): let node_map = if propagate_condition && !node.op.control_flow() { // Nested until https://github.com/rust-lang/rust/issues/53667 is fixed in a stable // release - if let Some(condition) = node - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.condition.as_ref()) - { + if let Some(condition) = node.extra_attrs.condition() { let mut in_dag = input_dag.copy_empty_like(py, "alike")?; // The remapping of `condition` below is still using the old code that assumes a 2-tuple. // This is because this remapping code only makes sense in the case of non-control-flow @@ -3164,12 +3148,7 @@ def _format(operand): for in_node_index in input_dag.topological_op_nodes()? { let in_node = &input_dag.dag[in_node_index]; if let NodeType::Operation(inst) = in_node { - if inst - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.condition.as_ref()) - .is_some() - { + if inst.extra_attrs.condition().is_some() { return Err(DAGCircuitError::new_err( "cannot propagate a condition to an element that already has one", )); @@ -3189,16 +3168,9 @@ def _format(operand): } let mut new_inst = inst.clone(); if new_condition.is_truthy()? { - if let Some(ref mut attrs) = new_inst.extra_attrs { - attrs.condition = Some(new_condition.as_any().clone().unbind()); - } else { - new_inst.extra_attrs = Some(Box::new(ExtraInstructionAttributes { - condition: Some(new_condition.as_any().clone().unbind()), - label: None, - duration: None, - unit: None, - })); - } + new_inst + .extra_attrs + .set_condition(Some(new_condition.as_any().clone().unbind())); #[cfg(feature = "cache_pygates")] { new_inst.py_op.take(); @@ -3283,13 +3255,7 @@ def _format(operand): let raw_target = old_op.instruction.getattr(py, "target")?; let target = raw_target.bind(py); let kwargs = PyDict::new_bound(py); - kwargs.set_item( - "label", - old_inst - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.label.as_ref()), - )?; + kwargs.set_item("label", old_inst.extra_attrs.label())?; let new_op = imports::SWITCH_CASE_OP.get_bound(py).call( ( variable_mapper.map_target(target)?, @@ -3318,11 +3284,7 @@ def _format(operand): } } } - if let Some(condition) = old_inst - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.condition.as_ref()) - { + if let Some(condition) = old_inst.extra_attrs.condition() { if old_inst.op.name() != "switch_case" { let new_condition: Option = variable_mapper .map_condition(condition.bind(py), false)? @@ -3332,18 +3294,7 @@ def _format(operand): if let NodeType::Operation(ref mut new_inst) = &mut self.dag[*new_node_index] { - match &mut new_inst.extra_attrs { - Some(attrs) => attrs.condition.clone_from(&new_condition), - None => { - new_inst.extra_attrs = - Some(Box::new(ExtraInstructionAttributes { - label: None, - condition: new_condition.clone(), - unit: None, - duration: None, - })) - } - } + new_inst.extra_attrs.set_condition(new_condition.clone()); #[cfg(feature = "cache_pygates")] { new_inst.py_op.take(); @@ -3440,14 +3391,8 @@ def _format(operand): .map(|x| Wire::Clbit(*x)), ) .collect(); - let (additional_clbits, additional_vars) = self.additional_wires( - py, - new_op.operation.view(), - new_op - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.condition.as_ref()), - )?; + let (additional_clbits, additional_vars) = + self.additional_wires(py, new_op.operation.view(), new_op.extra_attrs.condition())?; new_wires.extend(additional_clbits.iter().map(|x| Wire::Clbit(*x))); new_wires.extend(additional_vars.iter().map(|x| Wire::Var(x.clone_ref(py)))); @@ -3470,12 +3415,7 @@ def _format(operand): && !(node.instruction.operation.control_flow() || new_op.operation.control_flow()) { // if new_op has a condition, the condition can't be propagated from the old node - if new_op - .extra_attrs - .as_ref() - .and_then(|extra| extra.condition.as_ref()) - .is_some() - { + if new_op.extra_attrs.condition().is_some() { return Err(DAGCircuitError::new_err( "Cannot propagate a condition to an operation that already has one.", )); @@ -3486,17 +3426,8 @@ def _format(operand): "Cannot add a condition on a generic Operation.", )); } - if let Some(ref mut extra) = extra_attrs { - extra.condition = Some(old_condition.clone_ref(py)); - } else { - extra_attrs = ExtraInstructionAttributes::new( - None, - None, - None, - Some(old_condition.clone_ref(py)), - ) - .map(Box::new) - } + extra_attrs.set_condition(Some(old_condition.clone_ref(py))); + let binding = self .control_flow_module .condition_resources(old_condition.bind(py))?; @@ -5029,11 +4960,9 @@ impl DAGCircuit { let filter_fn = move |node_index: NodeIndex| -> Result { let node = &self.dag[node_index]; match node { - NodeType::Operation(inst) => Ok(namelist.contains(inst.op.name()) - && match &inst.extra_attrs { - None => true, - Some(attrs) => attrs.condition.is_none(), - }), + NodeType::Operation(inst) => { + Ok(namelist.contains(inst.op.name()) && inst.extra_attrs.condition().is_none()) + } _ => Ok(false), } }; @@ -6276,7 +6205,7 @@ impl DAGCircuit { clbits: old_node.clbits, params: (!new_gate.1.is_empty()) .then(|| Box::new(new_gate.1.iter().map(|x| Param::Float(*x)).collect())), - extra_attrs: None, + extra_attrs: ExtraInstructionAttributes::default(), #[cfg(feature = "cache_pygates")] py_op: OnceCell::new(), } diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index ccae7a8c5d82..4d82cf31e813 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -368,34 +368,28 @@ impl DAGOpNode { #[getter] fn label(&self) -> Option<&str> { - self.instruction - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.label.as_deref()) + self.instruction.extra_attrs.label() } #[getter] fn condition(&self, py: Python) -> Option { self.instruction .extra_attrs - .as_ref() - .and_then(|attrs| attrs.condition.as_ref().map(|x| x.clone_ref(py))) + .condition() + .map(|x| x.clone_ref(py)) } #[getter] fn duration(&self, py: Python) -> Option { self.instruction .extra_attrs - .as_ref() - .and_then(|attrs| attrs.duration.as_ref().map(|x| x.clone_ref(py))) + .duration() + .map(|x| x.clone_ref(py)) } #[getter] fn unit(&self) -> Option<&str> { - self.instruction - .extra_attrs - .as_ref() - .and_then(|attrs| attrs.unit.as_deref()) + self.instruction.extra_attrs.unit() } /// Is the :class:`.Operation` contained in this node a Qiskit standard gate? @@ -426,30 +420,7 @@ impl DAGOpNode { #[setter] fn set_label(&mut self, val: Option) { - match self.instruction.extra_attrs.as_mut() { - Some(attrs) => attrs.label = val, - None => { - if val.is_some() { - self.instruction.extra_attrs = Some(Box::new( - crate::circuit_instruction::ExtraInstructionAttributes { - label: val, - duration: None, - unit: None, - condition: None, - }, - )) - } - } - }; - if let Some(attrs) = &self.instruction.extra_attrs { - if attrs.label.is_none() - && attrs.duration.is_none() - && attrs.unit.is_none() - && attrs.condition.is_none() - { - self.instruction.extra_attrs = None; - } - } + self.instruction.extra_attrs.set_label(val); } #[getter] diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 2a341d687f6c..ad76e9d44008 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -425,22 +425,28 @@ impl StandardGate { &self, py: Python, params: Option<&[Param]>, - extra_attrs: Option<&ExtraInstructionAttributes>, + extra_attrs: &ExtraInstructionAttributes, ) -> PyResult> { let gate_class = get_std_gate_class(py, *self)?; let args = match params.unwrap_or(&[]) { &[] => PyTuple::empty_bound(py), params => PyTuple::new_bound(py, params), }; - if let Some(extra) = extra_attrs { + let (label, unit, duration, condition) = ( + extra_attrs.label(), + extra_attrs.unit(), + extra_attrs.duration(), + extra_attrs.condition(), + ); + if label.is_some() || unit.is_some() || duration.is_some() || condition.is_some() { let kwargs = [ - ("label", extra.label.to_object(py)), - ("unit", extra.py_unit(py).into_any()), - ("duration", extra.duration.to_object(py)), + ("label", label.to_object(py)), + ("unit", extra_attrs.py_unit(py).into_any()), + ("duration", duration.to_object(py)), ] .into_py_dict_bound(py); let mut out = gate_class.call_bound(py, args, Some(&kwargs))?; - if let Some(ref condition) = extra.condition { + if let Some(condition) = condition { out = out.call_method0(py, "to_mutable")?; out.setattr(py, "condition", condition)?; } diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index df8f9801314a..82c678031a15 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -497,7 +497,7 @@ pub struct PackedInstruction { /// The index under which the interner has stored `clbits`. pub clbits: Interned<[Clbit]>, pub params: Option>>, - pub extra_attrs: Option>, + pub extra_attrs: ExtraInstructionAttributes, #[cfg(feature = "cache_pygates")] /// This is hidden in a `OnceCell` because it's just an on-demand cache; we don't create this @@ -548,16 +548,12 @@ impl PackedInstruction { #[inline] pub fn condition(&self) -> Option<&Py> { - self.extra_attrs - .as_ref() - .and_then(|extra| extra.condition.as_ref()) + self.extra_attrs.condition() } #[inline] pub fn label(&self) -> Option<&str> { - self.extra_attrs - .as_ref() - .and_then(|extra| extra.label.as_deref()) + self.extra_attrs.label() } /// Build a reference to the Python-space operation object (the `Gate`, etc) packed into this @@ -573,7 +569,7 @@ impl PackedInstruction { OperationRef::Standard(standard) => standard.create_py_op( py, self.params.as_deref().map(SmallVec::as_slice), - self.extra_attrs.as_deref(), + &self.extra_attrs, ), OperationRef::Gate(gate) => Ok(gate.gate.clone_ref(py)), OperationRef::Instruction(instruction) => Ok(instruction.instruction.clone_ref(py)), From 653b5b66f5d856290acf26d40a9dac29ccfa21d5 Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Fri, 13 Sep 2024 17:35:03 +0300 Subject: [PATCH 5/5] Clean two_qubit_decompose code (#13093) * add pyfunction for decompose_two_qubit_product_gate * replace python code by rust for decompose_two_qubit_product_gate * remove unused code from two_qubit_decompose.py * updates following review comments * port trace_to_fid to rust * port Ud to rust * fix lint errors * fix lint errors * format * remove PyResult following review --- crates/accelerate/src/two_qubit_decompose.rs | 73 ++++++++++++++++++- .../two_qubit/two_qubit_decompose.py | 67 +---------------- test/python/synthesis/test_synthesis.py | 2 +- test/randomized/test_synthesis.py | 2 +- 4 files changed, 78 insertions(+), 66 deletions(-) diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index 92ad4724682f..76b92acd3faf 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -32,7 +32,7 @@ use ndarray::linalg::kron; use ndarray::prelude::*; use ndarray::Zip; use numpy::{IntoPyArray, ToPyArray}; -use numpy::{PyReadonlyArray1, PyReadonlyArray2}; +use numpy::{PyArray2, PyArrayLike2, PyReadonlyArray1, PyReadonlyArray2}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; @@ -154,6 +154,15 @@ impl TraceToFidelity for c64 { } } +#[pyfunction] +#[pyo3(name = "trace_to_fid")] +/// Average gate fidelity is :math:`Fbar = (d + |Tr (Utarget \\cdot U^dag)|^2) / d(d+1)` +/// M. Horodecki, P. Horodecki and R. Horodecki, PRA 60, 1888 (1999) +fn py_trace_to_fid(trace: Complex64) -> PyResult { + let fid = trace.trace_to_fid(); + Ok(fid) +} + fn decompose_two_qubit_product_gate( special_unitary: ArrayView2, ) -> PyResult<(Array2, Array2, f64)> { @@ -182,9 +191,31 @@ fn decompose_two_qubit_product_gate( } l.mapv_inplace(|x| x / det_l.sqrt()); let phase = det_l.arg() / 2.; + Ok((l, r, phase)) } +#[pyfunction] +#[pyo3(name = "decompose_two_qubit_product_gate")] +/// Decompose :math:`U = U_l \otimes U_r` where :math:`U \in SU(4)`, +/// and :math:`U_l,~U_r \in SU(2)`. +/// Args: +/// special_unitary_matrix: special unitary matrix to decompose +/// Raises: +/// QiskitError: if decomposition isn't possible. +fn py_decompose_two_qubit_product_gate( + py: Python, + special_unitary: PyArrayLike2, +) -> PyResult<(PyObject, PyObject, f64)> { + let view = special_unitary.as_array(); + let (l, r, phase) = decompose_two_qubit_product_gate(view)?; + Ok(( + l.into_pyarray_bound(py).unbind().into(), + r.into_pyarray_bound(py).unbind().into(), + phase, + )) +} + fn __weyl_coordinates(unitary: MatRef) -> [f64; 3] { let uscaled = scale(C1 / unitary.determinant().powf(0.25)) * unitary; let uup = transform_from_magic_basis(uscaled); @@ -301,6 +332,43 @@ fn rz_matrix(theta: f64) -> Array2 { array![[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] } +/// Generates the array :math:`e^{(i a XX + i b YY + i c ZZ)}` +fn ud(a: f64, b: f64, c: f64) -> Array2 { + array![ + [ + (IM * c).exp() * (a - b).cos(), + C_ZERO, + C_ZERO, + IM * (IM * c).exp() * (a - b).sin() + ], + [ + C_ZERO, + (M_IM * c).exp() * (a + b).cos(), + IM * (M_IM * c).exp() * (a + b).sin(), + C_ZERO + ], + [ + C_ZERO, + IM * (M_IM * c).exp() * (a + b).sin(), + (M_IM * c).exp() * (a + b).cos(), + C_ZERO + ], + [ + IM * (IM * c).exp() * (a - b).sin(), + C_ZERO, + C_ZERO, + (IM * c).exp() * (a - b).cos() + ] + ] +} + +#[pyfunction] +#[pyo3(name = "Ud")] +fn py_ud(py: Python, a: f64, b: f64, c: f64) -> Py> { + let ud_mat = ud(a, b, c); + ud_mat.into_pyarray_bound(py).unbind() +} + fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2 { let identity = aview2(&ONE_QUBIT_IDENTITY); let phase = c64(0., global_phase).exp(); @@ -2278,9 +2346,12 @@ pub fn local_equivalence(weyl: PyReadonlyArray1) -> PyResult<[f64; 3]> { pub fn two_qubit_decompose(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(_num_basis_gates))?; + m.add_wrapped(wrap_pyfunction!(py_decompose_two_qubit_product_gate))?; m.add_wrapped(wrap_pyfunction!(two_qubit_decompose_up_to_diagonal))?; m.add_wrapped(wrap_pyfunction!(two_qubit_local_invariants))?; m.add_wrapped(wrap_pyfunction!(local_equivalence))?; + m.add_wrapped(wrap_pyfunction!(py_trace_to_fid))?; + m.add_wrapped(wrap_pyfunction!(py_ud))?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py index 0e3427773387..d4c7702da35a 100644 --- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py +++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py @@ -24,8 +24,6 @@ arXiv:1811.12926 [quant-ph] (2018). """ from __future__ import annotations -import cmath -import math import io import base64 import warnings @@ -91,41 +89,18 @@ def decompose_two_qubit_product_gate(special_unitary_matrix: np.ndarray): QiskitError: if decomposition isn't possible. """ special_unitary_matrix = np.asarray(special_unitary_matrix, dtype=complex) - # extract the right component - R = special_unitary_matrix[:2, :2].copy() - detR = R[0, 0] * R[1, 1] - R[0, 1] * R[1, 0] - if abs(detR) < 0.1: - R = special_unitary_matrix[2:, :2].copy() - detR = R[0, 0] * R[1, 1] - R[0, 1] * R[1, 0] - if abs(detR) < 0.1: - raise QiskitError("decompose_two_qubit_product_gate: unable to decompose: detR < 0.1") - R /= np.sqrt(detR) - - # extract the left component - temp = np.kron(np.eye(2), R.T.conj()) - temp = special_unitary_matrix.dot(temp) - L = temp[::2, ::2] - detL = L[0, 0] * L[1, 1] - L[0, 1] * L[1, 0] - if abs(detL) < 0.9: - raise QiskitError("decompose_two_qubit_product_gate: unable to decompose: detL < 0.9") - L /= np.sqrt(detL) - phase = cmath.phase(detL) / 2 + (L, R, phase) = two_qubit_decompose.decompose_two_qubit_product_gate(special_unitary_matrix) temp = np.kron(L, R) deviation = abs(abs(temp.conj().T.dot(special_unitary_matrix).trace()) - 4) + if deviation > 1.0e-13: raise QiskitError( "decompose_two_qubit_product_gate: decomposition failed: " f"deviation too large: {deviation}" ) - return L, R, phase - - -_ipx = np.array([[0, 1j], [1j, 0]], dtype=complex) -_ipy = np.array([[0, 1], [-1, 0]], dtype=complex) -_ipz = np.array([[1j, 0], [0, -1j]], dtype=complex) -_id = np.array([[1, 0], [0, 1]], dtype=complex) + return (L, R, phase) class TwoQubitWeylDecomposition: @@ -239,7 +214,7 @@ def actual_fidelity(self, **kwargs) -> float: """Calculates the actual fidelity of the decomposed circuit to the input unitary.""" circ = self.circuit(**kwargs) trace = np.trace(Operator(circ).data.T.conj() @ self.unitary_matrix) - return trace_to_fid(trace) + return two_qubit_decompose.trace_to_fid(trace) def __repr__(self): """Represent with enough precision to allow copy-paste debugging of all corner cases""" @@ -460,40 +435,6 @@ def _weyl_gate(self, circ: QuantumCircuit, atol=1.0e-13): return circ -def Ud(a, b, c): - r"""Generates the array :math:`e^{(i a XX + i b YY + i c ZZ)}`""" - return np.array( - [ - [cmath.exp(1j * c) * math.cos(a - b), 0, 0, 1j * cmath.exp(1j * c) * math.sin(a - b)], - [0, cmath.exp(-1j * c) * math.cos(a + b), 1j * cmath.exp(-1j * c) * math.sin(a + b), 0], - [0, 1j * cmath.exp(-1j * c) * math.sin(a + b), cmath.exp(-1j * c) * math.cos(a + b), 0], - [1j * cmath.exp(1j * c) * math.sin(a - b), 0, 0, cmath.exp(1j * c) * math.cos(a - b)], - ], - dtype=complex, - ) - - -def trace_to_fid(trace): - r"""Average gate fidelity is - - .. math:: - - \bar{F} = \frac{d + |\mathrm{Tr} (U_\text{target} \cdot U^{\dag})|^2}{d(d+1)} - - M. Horodecki, P. Horodecki and R. Horodecki, PRA 60, 1888 (1999)""" - return (4 + abs(trace) ** 2) / 20 - - -def rz_array(theta): - """Return numpy array for Rz(theta). - - Rz(theta) = diag(exp(-i*theta/2),exp(i*theta/2)) - """ - return np.array( - [[cmath.exp(-1j * theta / 2.0), 0], [0, cmath.exp(1j * theta / 2.0)]], dtype=complex - ) - - class TwoQubitBasisDecomposer: """A class for decomposing 2-qubit unitaries into minimal number of uses of a 2-qubit basis gate. diff --git a/test/python/synthesis/test_synthesis.py b/test/python/synthesis/test_synthesis.py index b4fac3f427f0..b08240197211 100644 --- a/test/python/synthesis/test_synthesis.py +++ b/test/python/synthesis/test_synthesis.py @@ -59,11 +59,11 @@ two_qubit_cnot_decompose, TwoQubitBasisDecomposer, TwoQubitControlledUDecomposer, - Ud, decompose_two_qubit_product_gate, ) from qiskit._accelerate.two_qubit_decompose import two_qubit_decompose_up_to_diagonal from qiskit._accelerate.two_qubit_decompose import Specialization +from qiskit._accelerate.two_qubit_decompose import Ud from qiskit.synthesis.unitary import qsd from test import combine # pylint: disable=wrong-import-order from test import QiskitTestCase # pylint: disable=wrong-import-order diff --git a/test/randomized/test_synthesis.py b/test/randomized/test_synthesis.py index 9f0619a0c296..5e500b78dafc 100644 --- a/test/randomized/test_synthesis.py +++ b/test/randomized/test_synthesis.py @@ -23,8 +23,8 @@ from qiskit.synthesis.two_qubit.two_qubit_decompose import ( two_qubit_cnot_decompose, TwoQubitBasisDecomposer, - Ud, ) +from qiskit._accelerate.two_qubit_decompose import Ud class TestSynthesis(CheckDecompositions):