diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 95a0bda5d421..625ea9bc1f50 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.8' + python-version: '3.11' - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/randomized_tests.yml b/.github/workflows/randomized_tests.yml index f807656a84ce..1cc39893c5ec 100644 --- a/.github/workflows/randomized_tests.yml +++ b/.github/workflows/randomized_tests.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.8' + python-version: '3.11' - name: Install dependencies run: | python -m pip install -U pip setuptools wheel diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20e40dec9824..20c40dc38321 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - # Normally we test min and max version but we can't run python 3.8 or + # Normally we test min and max version but we can't run python # 3.9 on arm64 until actions/setup-python#808 is resolved python-version: ["3.10", "3.12"] steps: diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index d7c2b8c3f783..3314bae05d5b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -41,35 +41,6 @@ jobs: with: path: ./wheelhouse/*.whl name: wheels-${{ matrix.os }} - build_wheels_macos_arm_py38: - name: Build wheels on macOS arm - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [macos-12] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - name: Install Python - with: - python-version: '3.10' - - uses: dtolnay/rust-toolchain@stable - with: - components: llvm-tools-preview - - name: Build wheels - uses: pypa/cibuildwheel@v2.19.2 - env: - CIBW_BEFORE_ALL: rustup target add aarch64-apple-darwin - CIBW_BUILD: cp38-macosx_universal2 cp38-macosx_arm64 - CIBW_ARCHS_MACOS: arm64 universal2 - CIBW_ENVIRONMENT: >- - CARGO_BUILD_TARGET="aarch64-apple-darwin" - PYO3_CROSS_LIB_DIR="/Library/Frameworks/Python.framework/Versions/$(python -c 'import sys; print(str(sys.version_info[0])+"."+str(sys.version_info[1]))')/lib/python$(python -c 'import sys; print(str(sys.version_info[0])+"."+str(sys.version_info[1]))')" - - uses: actions/upload-artifact@v4 - with: - path: ./wheelhouse/*.whl - name: wheels-${{ matrix.os }}-arm build_wheels_32bit: name: Build wheels 32bit runs-on: ${{ matrix.os }} @@ -100,7 +71,7 @@ jobs: environment: release permissions: id-token: write - needs: ["build_wheels", "build_wheels_32bit", "build_wheels_macos_arm_py38"] + needs: ["build_wheels", "build_wheels_32bit"] steps: - uses: actions/download-artifact@v4 with: diff --git a/Cargo.toml b/Cargo.toml index 2f5fd8ce077b..15f34603c61e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ rayon = "1.10" # distributions). We only activate that feature when building the C extension module; we still need # it disabled for Rust-only tests to avoid linker errors with it not being loaded. See # https://pyo3.rs/main/features#extension-module for more. -pyo3 = { version = "0.21.2", features = ["abi3-py38"] } +pyo3 = { version = "0.21.2", features = ["abi3-py39"] } # These are our own crates. qiskit-accelerate = { path = "crates/accelerate" } diff --git a/asv.conf.json b/asv.conf.json index 70dd3b760e2c..a75bc0c59c3a 100644 --- a/asv.conf.json +++ b/asv.conf.json @@ -17,7 +17,7 @@ "dvcs": "git", "environment_type": "virtualenv", "show_commit_url": "http://github.com/Qiskit/qiskit/commit/", - "pythons": ["3.8", "3.9", "3.10", "3.11", "3.12"], + "pythons": ["3.9", "3.10", "3.11", "3.12"], "benchmark_dir": "test/benchmarks", "env_dir": ".asv/env", "results_dir": ".asv/results" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c3154abf412c..49df19808498 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -37,12 +37,12 @@ parameters: - name: "supportedPythonVersions" displayName: "All supported versions of Python" type: object - default: ["3.8", "3.9", "3.10", "3.11", "3.12"] + default: ["3.9", "3.10", "3.11", "3.12"] - name: "minimumPythonVersion" displayName: "Minimum supported version of Python" type: string - default: "3.8" + default: "3.9" - name: "maximumPythonVersion" displayName: "Maximum supported version of Python" diff --git a/crates/accelerate/src/commutation_analysis.rs b/crates/accelerate/src/commutation_analysis.rs index 08fa1dda5ec9..2da8cfce931e 100644 --- a/crates/accelerate/src/commutation_analysis.rs +++ b/crates/accelerate/src/commutation_analysis.rs @@ -50,7 +50,7 @@ const MAX_NUM_QUBITS: u32 = 3; /// commutation_set = {0: [[0], [2, 3], [4], [1]]} /// node_indices = {(0, 0): 0, (1, 0): 3, (2, 0): 1, (3, 0): 1, (4, 0): 2} /// -fn analyze_commutations_inner( +pub(crate) fn analyze_commutations_inner( py: Python, dag: &mut DAGCircuit, commutation_checker: &mut CommutationChecker, diff --git a/crates/accelerate/src/commutation_cancellation.rs b/crates/accelerate/src/commutation_cancellation.rs new file mode 100644 index 000000000000..dc2d4436d83a --- /dev/null +++ b/crates/accelerate/src/commutation_cancellation.rs @@ -0,0 +1,280 @@ +// 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 std::f64::consts::PI; + +use hashbrown::{HashMap, HashSet}; +use pyo3::exceptions::PyRuntimeError; +use pyo3::prelude::*; +use pyo3::{pyfunction, pymodule, wrap_pyfunction, Bound, PyResult, Python}; +use rustworkx_core::petgraph::stable_graph::NodeIndex; +use smallvec::{smallvec, SmallVec}; + +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, Wire}; +use qiskit_circuit::operations::StandardGate::{ + CXGate, CYGate, CZGate, HGate, PhaseGate, RXGate, RZGate, SGate, TGate, U1Gate, XGate, YGate, + ZGate, +}; +use qiskit_circuit::operations::{Operation, Param, StandardGate}; +use qiskit_circuit::Qubit; + +use crate::commutation_analysis::analyze_commutations_inner; +use crate::commutation_checker::CommutationChecker; +use crate::{euler_one_qubit_decomposer, QiskitError}; + +const _CUTOFF_PRECISION: f64 = 1e-5; +static ROTATION_GATES: [&str; 4] = ["p", "u1", "rz", "rx"]; +static HALF_TURNS: [&str; 2] = ["z", "x"]; +static QUARTER_TURNS: [&str; 1] = ["s"]; +static EIGHTH_TURNS: [&str; 1] = ["t"]; + +static VAR_Z_MAP: [(&str, StandardGate); 3] = [("rz", RZGate), ("p", PhaseGate), ("u1", U1Gate)]; +static Z_ROTATIONS: [StandardGate; 6] = [PhaseGate, ZGate, U1Gate, RZGate, TGate, SGate]; +static X_ROTATIONS: [StandardGate; 2] = [XGate, RXGate]; +static SUPPORTED_GATES: [StandardGate; 5] = [CXGate, CYGate, CZGate, HGate, YGate]; + +#[derive(Hash, Eq, PartialEq, Debug)] +enum GateOrRotation { + Gate(StandardGate), + ZRotation, + XRotation, +} +#[derive(Hash, Eq, PartialEq, Debug)] +struct CancellationSetKey { + gate: GateOrRotation, + qubits: SmallVec<[Qubit; 2]>, + com_set_index: usize, + second_index: Option, +} + +#[pyfunction] +#[pyo3(signature = (dag, commutation_checker, basis_gates=None))] +pub(crate) fn cancel_commutations( + py: Python, + dag: &mut DAGCircuit, + commutation_checker: &mut CommutationChecker, + basis_gates: Option>, +) -> PyResult<()> { + let basis: HashSet = if let Some(basis) = basis_gates { + basis + } else { + HashSet::new() + }; + let z_var_gate = dag + .get_op_counts() + .keys() + .find_map(|g| { + VAR_Z_MAP + .iter() + .find(|(key, _)| *key == g.as_str()) + .map(|(_, gate)| gate) + }) + .or_else(|| { + basis.iter().find_map(|g| { + VAR_Z_MAP + .iter() + .find(|(key, _)| *key == g.as_str()) + .map(|(_, gate)| gate) + }) + }); + // Fallback to the first matching key from basis if there is no match in dag.op_names + + // Gate sets to be cancelled + /* Traverse each qubit to generate the cancel dictionaries + Cancel dictionaries: + - For 1-qubit gates the key is (gate_type, qubit_id, commutation_set_id), + the value is the list of gates that share the same gate type, qubit, commutation set. + - For 2qbit gates the key: (gate_type, first_qbit, sec_qbit, first commutation_set_id, + sec_commutation_set_id), the value is the list gates that share the same gate type, + qubits and commutation sets. + */ + let (commutation_set, node_indices) = analyze_commutations_inner(py, dag, commutation_checker)?; + let mut cancellation_sets: HashMap> = HashMap::new(); + + (0..dag.num_qubits() as u32).for_each(|qubit| { + let wire = Qubit(qubit); + if let Some(wire_commutation_set) = commutation_set.get(&Wire::Qubit(wire)) { + for (com_set_idx, com_set) in wire_commutation_set.iter().enumerate() { + if let Some(&nd) = com_set.first() { + if !matches!(dag.dag[nd], NodeType::Operation(_)) { + continue; + } + } else { + continue; + } + for node in com_set.iter() { + let instr = match &dag.dag[*node] { + NodeType::Operation(instr) => instr, + _ => panic!("Unexpected type in commutation set."), + }; + let num_qargs = dag.get_qargs(instr.qubits).len(); + // no support for cancellation of parameterized gates + if instr.is_parameterized() { + continue; + } + if let Some(op_gate) = instr.op.try_standard_gate() { + if num_qargs == 1 && SUPPORTED_GATES.contains(&op_gate) { + cancellation_sets + .entry(CancellationSetKey { + gate: GateOrRotation::Gate(op_gate), + qubits: smallvec![wire], + com_set_index: com_set_idx, + second_index: None, + }) + .or_insert_with(Vec::new) + .push(*node); + } + + if num_qargs == 1 && Z_ROTATIONS.contains(&op_gate) { + cancellation_sets + .entry(CancellationSetKey { + gate: GateOrRotation::ZRotation, + qubits: smallvec![wire], + com_set_index: com_set_idx, + second_index: None, + }) + .or_insert_with(Vec::new) + .push(*node); + } + if num_qargs == 1 && X_ROTATIONS.contains(&op_gate) { + cancellation_sets + .entry(CancellationSetKey { + gate: GateOrRotation::XRotation, + qubits: smallvec![wire], + com_set_index: com_set_idx, + second_index: None, + }) + .or_insert_with(Vec::new) + .push(*node); + } + // Don't deal with Y rotation, because Y rotation doesn't commute with + // CNOT, so it should be dealt with by optimized1qgate pass + if num_qargs == 2 && dag.get_qargs(instr.qubits)[0] == wire { + let second_qarg = dag.get_qargs(instr.qubits)[1]; + cancellation_sets + .entry(CancellationSetKey { + gate: GateOrRotation::Gate(op_gate), + qubits: smallvec![wire, second_qarg], + com_set_index: com_set_idx, + second_index: node_indices + .get(&(*node, Wire::Qubit(second_qarg))) + .copied(), + }) + .or_insert_with(Vec::new) + .push(*node); + } + } + } + } + } + }); + + for (cancel_key, cancel_set) in &cancellation_sets { + if cancel_set.len() > 1 { + if let GateOrRotation::Gate(g) = cancel_key.gate { + if SUPPORTED_GATES.contains(&g) { + for &c_node in &cancel_set[0..(cancel_set.len() / 2) * 2] { + dag.remove_op_node(c_node); + } + } + continue; + } + if matches!(cancel_key.gate, GateOrRotation::ZRotation) && z_var_gate.is_none() { + continue; + } + if matches!( + cancel_key.gate, + GateOrRotation::ZRotation | GateOrRotation::XRotation + ) { + let mut total_angle: f64 = 0.0; + let mut total_phase: f64 = 0.0; + for current_node in cancel_set { + let node_op = match &dag.dag[*current_node] { + NodeType::Operation(instr) => instr, + _ => panic!("Unexpected type in commutation set run."), + }; + let node_op_name = node_op.op.name(); + + let node_angle = if ROTATION_GATES.contains(&node_op_name) { + match node_op.params_view().first() { + Some(Param::Float(f)) => Ok(*f), + _ => return Err(QiskitError::new_err(format!( + "Rotational gate with parameter expression encountered in cancellation {:?}", + node_op.op + ))) + } + } else if HALF_TURNS.contains(&node_op_name) { + Ok(PI) + } else if QUARTER_TURNS.contains(&node_op_name) { + Ok(PI / 2.0) + } else if EIGHTH_TURNS.contains(&node_op_name) { + Ok(PI / 4.0) + } else { + Err(PyRuntimeError::new_err(format!( + "Angle for operation {} is not defined", + node_op_name + ))) + }; + total_angle += node_angle?; + + let Param::Float(new_phase) = node_op + .op + .definition(node_op.params_view()) + .unwrap() + .global_phase() + .clone() + else { + unreachable!() + }; + total_phase += new_phase + } + + let new_op = match cancel_key.gate { + GateOrRotation::ZRotation => z_var_gate.unwrap(), + GateOrRotation::XRotation => &RXGate, + _ => unreachable!(), + }; + + let gate_angle = euler_one_qubit_decomposer::mod_2pi(total_angle, 0.); + + let new_op_phase: f64 = if gate_angle.abs() > _CUTOFF_PRECISION { + dag.insert_1q_on_incoming_qubit((*new_op, &[total_angle]), cancel_set[0]); + let Param::Float(new_phase) = new_op + .definition(&[Param::Float(total_angle)]) + .unwrap() + .global_phase() + .clone() + else { + unreachable!(); + }; + new_phase + } else { + 0.0 + }; + + dag.add_global_phase(py, &Param::Float(total_phase - new_op_phase))?; + + for node in cancel_set { + dag.remove_op_node(*node); + } + } + } + } + + Ok(()) +} + +#[pymodule] +pub fn commutation_cancellation(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(cancel_commutations))?; + Ok(()) +} diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index a3cb11ea45a2..4c0a8539cf34 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -924,7 +924,7 @@ pub fn det_one_qubit(mat: ArrayView2) -> Complex64 { /// Wrap angle into interval [-π,π). If within atol of the endpoint, clamp to -π #[inline] -fn mod_2pi(angle: f64, atol: f64) -> f64 { +pub(crate) fn mod_2pi(angle: f64, atol: f64) -> f64 { // f64::rem_euclid() isn't exactly the same as Python's % operator, but because // the RHS here is a constant and positive it is effectively equivalent for // this case diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 9e391e0a9030..9111f932e270 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -17,6 +17,7 @@ use pyo3::import_exception; pub mod check_map; pub mod circuit_library; pub mod commutation_analysis; +pub mod commutation_cancellation; pub mod commutation_checker; pub mod convert_2q_block_matrix; pub mod dense_layout; diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs new file mode 100644 index 000000000000..ab9d4bce0475 --- /dev/null +++ b/crates/circuit/src/converters.rs @@ -0,0 +1,91 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023, 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 ::pyo3::prelude::*; +use hashbrown::HashMap; +use pyo3::{ + intern, + types::{PyDict, PyList}, +}; + +use crate::{circuit_data::CircuitData, dag_circuit::DAGCircuit}; + +/// An extractable representation of a QuantumCircuit reserved only for +/// conversion purposes. +#[derive(Debug, Clone)] +pub struct QuantumCircuitData<'py> { + pub data: CircuitData, + pub name: Option>, + pub calibrations: Option>>, + pub metadata: Option>, + pub qregs: Option>, + pub cregs: Option>, + pub input_vars: Vec>, + pub captured_vars: Vec>, + pub declared_vars: Vec>, +} + +impl<'py> FromPyObject<'py> for QuantumCircuitData<'py> { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + let py = ob.py(); + let circuit_data = ob.getattr("_data")?; + let data_borrowed = circuit_data.extract::()?; + Ok(QuantumCircuitData { + data: data_borrowed, + name: ob.getattr(intern!(py, "name")).ok(), + calibrations: ob.getattr(intern!(py, "calibrations"))?.extract().ok(), + metadata: ob.getattr(intern!(py, "metadata")).ok(), + qregs: ob + .getattr(intern!(py, "qregs")) + .map(|ob| ob.downcast_into())? + .ok(), + cregs: ob + .getattr(intern!(py, "cregs")) + .map(|ob| ob.downcast_into())? + .ok(), + input_vars: ob + .call_method0(intern!(py, "iter_input_vars"))? + .iter()? + .collect::>>()?, + captured_vars: ob + .call_method0(intern!(py, "iter_captured_vars"))? + .iter()? + .collect::>>()?, + declared_vars: ob + .call_method0(intern!(py, "iter_declared_vars"))? + .iter()? + .collect::>>()?, + }) + } +} + +#[pyfunction(signature = (quantum_circuit, copy_operations = true, qubit_order = None, clbit_order = None))] +pub fn circuit_to_dag( + py: Python, + quantum_circuit: QuantumCircuitData, + copy_operations: bool, + qubit_order: Option>>, + clbit_order: Option>>, +) -> PyResult { + DAGCircuit::from_circuit( + py, + quantum_circuit, + copy_operations, + qubit_order, + clbit_order, + ) +} + +pub fn converters(m: &Bound) -> PyResult<()> { + m.add_function(wrap_pyfunction!(circuit_to_dag, m)?)?; + Ok(()) +} diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 0b5a43c1eb4b..197ad57278a2 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -15,9 +15,11 @@ use std::hash::{Hash, Hasher}; use ahash::RandomState; use crate::bit_data::BitData; +use crate::circuit_data::CircuitData; use crate::circuit_instruction::{ CircuitInstruction, ExtraInstructionAttributes, OperationFromPython, }; +use crate::converters::QuantumCircuitData; use crate::dag_node::{DAGInNode, DAGNode, DAGOpNode, DAGOutNode}; use crate::dot_utils::build_dot; use crate::error::DAGCircuitError; @@ -6454,6 +6456,16 @@ impl DAGCircuit { } } + /// Get an immutable reference to the op counts for this DAGCircuit + /// + /// This differs from count_ops() in that it doesn't handle control flow recursion at all + /// and it returns a reference instead of an owned copy. If you don't need to work with + /// control flow or ownership of the counts this is a more efficient alternative to + /// `DAGCircuit::count_ops(py, false)` + pub fn get_op_counts(&self) -> &IndexMap { + &self.op_names + } + /// Extends the DAG with valid instances of [PackedInstruction] pub fn extend(&mut self, py: Python, iter: I) -> PyResult> where @@ -6562,7 +6574,12 @@ impl DAGCircuit { predecessor_node }; + // Because `DAGCircuit::additional_wires` can return repeated instances of vars, + // we need to make sure to skip those to avoid cycles. vars_last_nodes.set_item(var, new_node.index())?; + if var_last_node == new_node { + continue; + } self.dag .add_edge(var_last_node, new_node, Wire::Var(var.clone_ref(py))); } @@ -6590,6 +6607,207 @@ impl DAGCircuit { Ok(new_nodes) } + + /// Alternative constructor to build an instance of [DAGCircuit] from a `QuantumCircuit`. + pub(crate) fn from_circuit( + py: Python, + qc: QuantumCircuitData, + copy_op: bool, + qubit_order: Option>>, + clbit_order: Option>>, + ) -> PyResult { + // Extract necessary attributes + let qc_data = qc.data; + let num_qubits = qc_data.num_qubits(); + let num_clbits = qc_data.num_clbits(); + let num_ops = qc_data.__len__(); + let num_vars = qc.declared_vars.len() + qc.input_vars.len() + qc.captured_vars.len(); + + // Build DAGCircuit with capacity + let mut new_dag = DAGCircuit::with_capacity( + py, + num_qubits, + num_clbits, + Some(num_vars), + Some(num_ops), + None, + )?; + + // Assign other necessary data + new_dag.name = qc.name.map(|ob| ob.unbind()); + + // Avoid manually acquiring the GIL. + new_dag.global_phase = match qc_data.global_phase() { + Param::ParameterExpression(exp) => Param::ParameterExpression(exp.clone_ref(py)), + Param::Float(float) => Param::Float(*float), + _ => unreachable!("Incorrect parameter assigned for global phase"), + }; + + if let Some(calibrations) = qc.calibrations { + new_dag.calibrations = calibrations; + } + + new_dag.metadata = qc.metadata.map(|meta| meta.unbind()); + + // Add the qubits depending on order. + let qubit_map: Option> = if let Some(qubit_ordering) = qubit_order { + let mut ordered_vec = Vec::from_iter((0..num_qubits as u32).map(Qubit)); + qubit_ordering + .into_iter() + .try_for_each(|qubit| -> PyResult<()> { + if new_dag.qubits.find(&qubit).is_some() { + return Err(DAGCircuitError::new_err(format!( + "duplicate qubits {}", + &qubit + ))); + } + let qubit_index = qc_data.qubits().find(&qubit).unwrap(); + ordered_vec[qubit_index.0 as usize] = + new_dag.add_qubit_unchecked(py, &qubit)?; + Ok(()) + })?; + Some(ordered_vec) + } else { + qc_data + .qubits() + .bits() + .iter() + .try_for_each(|qubit| -> PyResult<_> { + new_dag.add_qubit_unchecked(py, qubit.bind(py))?; + Ok(()) + })?; + None + }; + + // Add the clbits depending on order. + let clbit_map: Option> = if let Some(clbit_ordering) = clbit_order { + let mut ordered_vec = Vec::from_iter((0..num_clbits as u32).map(Clbit)); + clbit_ordering + .into_iter() + .try_for_each(|clbit| -> PyResult<()> { + if new_dag.clbits.find(&clbit).is_some() { + return Err(DAGCircuitError::new_err(format!( + "duplicate clbits {}", + &clbit + ))); + }; + let clbit_index = qc_data.clbits().find(&clbit).unwrap(); + ordered_vec[clbit_index.0 as usize] = + new_dag.add_clbit_unchecked(py, &clbit)?; + Ok(()) + })?; + Some(ordered_vec) + } else { + qc_data + .clbits() + .bits() + .iter() + .try_for_each(|clbit| -> PyResult<()> { + new_dag.add_clbit_unchecked(py, clbit.bind(py))?; + Ok(()) + })?; + None + }; + + // Add all of the new vars. + for var in &qc.declared_vars { + new_dag.add_var(py, var, DAGVarType::Declare)?; + } + + for var in &qc.input_vars { + new_dag.add_var(py, var, DAGVarType::Input)?; + } + + for var in &qc.captured_vars { + new_dag.add_var(py, var, DAGVarType::Capture)?; + } + + // Add all the registers + if let Some(qregs) = qc.qregs { + for qreg in qregs.iter() { + new_dag.add_qreg(py, &qreg)?; + } + } + + if let Some(cregs) = qc.cregs { + for creg in cregs.iter() { + new_dag.add_creg(py, &creg)?; + } + } + + // Pre-process and re-intern all indices again. + let instructions: Vec = qc_data + .iter() + .map(|instr| -> PyResult { + // Re-map the qubits + let new_qargs = if let Some(qubit_mapping) = &qubit_map { + let qargs = qc_data + .get_qargs(instr.qubits) + .iter() + .map(|bit| qubit_mapping[bit.0 as usize]) + .collect(); + new_dag.qargs_interner.insert_owned(qargs) + } else { + new_dag + .qargs_interner + .insert(qc_data.get_qargs(instr.qubits)) + }; + // Remap the clbits + let new_cargs = if let Some(clbit_mapping) = &clbit_map { + let qargs = qc_data + .get_cargs(instr.clbits) + .iter() + .map(|bit| clbit_mapping[bit.0 as usize]) + .collect(); + new_dag.cargs_interner.insert_owned(qargs) + } else { + new_dag + .cargs_interner + .insert(qc_data.get_cargs(instr.clbits)) + }; + // Copy the operations + + Ok(PackedInstruction { + op: if copy_op { + instr.op.py_deepcopy(py, None)? + } else { + instr.op.clone() + }, + qubits: new_qargs, + clbits: new_cargs, + params: instr.params.clone(), + extra_attrs: instr.extra_attrs.clone(), + #[cfg(feature = "cache_pygates")] + py_op: OnceCell::new(), + }) + }) + .collect::>>()?; + + // Finally add all the instructions back + new_dag.extend(py, instructions)?; + + Ok(new_dag) + } + + /// Builds a [DAGCircuit] based on an instance of [CircuitData]. + pub fn from_circuit_data( + py: Python, + circuit_data: CircuitData, + copy_op: bool, + ) -> PyResult { + let circ = QuantumCircuitData { + data: circuit_data, + name: None, + calibrations: None, + metadata: None, + qregs: None, + cregs: None, + input_vars: Vec::new(), + captured_vars: Vec::new(), + declared_vars: Vec::new(), + }; + Self::from_circuit(py, circ, copy_op, None, None) + } } /// Add to global phase. Global phase can only be Float or ParameterExpression so this diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 5106ba030288..dcff558ade64 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -13,6 +13,7 @@ pub mod bit_data; pub mod circuit_data; pub mod circuit_instruction; +pub mod converters; pub mod dag_circuit; pub mod dag_node; mod dot_utils; diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index 03bd0202dae4..6033c7c47e49 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -14,12 +14,13 @@ use pyo3::prelude::*; use qiskit_accelerate::{ check_map::check_map_mod, circuit_library::circuit_library, - commutation_analysis::commutation_analysis, commutation_checker::commutation_checker, - convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, - error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, - filter_op_nodes::filter_op_nodes_mod, gate_direction::gate_direction, - inverse_cancellation::inverse_cancellation_mod, isometry::isometry, nlayout::nlayout, - optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval, + commutation_analysis::commutation_analysis, commutation_cancellation::commutation_cancellation, + commutation_checker::commutation_checker, convert_2q_block_matrix::convert_2q_block_matrix, + dense_layout::dense_layout, error_map::error_map, + euler_one_qubit_decomposer::euler_one_qubit_decomposer, filter_op_nodes::filter_op_nodes_mod, + gate_direction::gate_direction, inverse_cancellation::inverse_cancellation_mod, + isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, + pauli_exp_val::pauli_expval, remove_diagonal_gates_before_measure::remove_diagonal_gates_before_measure, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, split_2q_unitaries::split_2q_unitaries_mod, star_prerouting::star_prerouting, @@ -42,6 +43,7 @@ where #[pymodule] fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, qiskit_circuit::circuit, "circuit")?; + add_submodule(m, qiskit_circuit::converters::converters, "converters")?; add_submodule(m, qiskit_qasm2::qasm2, "qasm2")?; add_submodule(m, qiskit_qasm3::qasm3, "qasm3")?; add_submodule(m, circuit_library, "circuit_library")?; @@ -77,5 +79,6 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, gate_direction, "gate_direction")?; add_submodule(m, commutation_checker, "commutation_checker")?; add_submodule(m, commutation_analysis, "commutation_analysis")?; + add_submodule(m, commutation_cancellation, "commutation_cancellation")?; Ok(()) } diff --git a/pyproject.toml b/pyproject.toml index 9a16fc6e0ac3..689bf473e2f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "qiskit" description = "An open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives." -requires-python = ">=3.8" +requires-python = ">=3.9" license = {text = "Apache 2.0"} authors = [ { name = "Qiskit Development Team", email = "qiskit@us.ibm.com" }, @@ -27,7 +27,6 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -140,12 +139,12 @@ include = ["qiskit", "qiskit.*"] [tool.black] line-length = 100 -target-version = ['py38', 'py39', 'py310', 'py311'] +target-version = ['py39', 'py310', 'py311'] [tool.cibuildwheel] manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" -skip = "pp* cp36-* cp37-* *musllinux* *win32 *i686 cp38-macosx_arm64" +skip = "pp* cp36-* cp37-* cp38-* *musllinux* *win32 *i686 cp38-macosx_arm64" test-skip = "*win32 *linux_i686" test-command = "python {project}/examples/python/stochastic_swap.py" # We need to use pre-built versions of Numpy and Scipy in the tests; they have a @@ -197,7 +196,7 @@ extension-pkg-allow-list = [ "tweedledum", ] load-plugins = ["pylint.extensions.docparams", "pylint.extensions.docstyle"] -py-version = "3.8" # update it when bumping minimum supported python version +py-version = "3.9" # update it when bumping minimum supported python version [tool.pylint.basic] good-names = ["a", "b", "i", "j", "k", "d", "n", "m", "ex", "v", "w", "x", "y", "z", "Run", "_", "logger", "q", "c", "r", "qr", "cr", "qc", "nd", "pi", "op", "b", "ar", "br", "p", "cp", "ax", "dt", "__unittest", "iSwapGate", "mu"] diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 29e2b8ba5c11..25137d7a5918 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -43,14 +43,6 @@ " Qiskit unfortunately cannot enforce this requirement during environment resolution." " See https://qisk.it/packaging-1-0 for more detail." ) -if sys.version_info < (3, 9): - warnings.warn( - "Using Qiskit with Python 3.8 is deprecated as of the 1.1.0 release. " - "Support for running Qiskit with Python 3.8 will be removed in the " - "1.3.0 release, which coincides with when Python 3.8 goes end of life.", - DeprecationWarning, - ) - from . import _accelerate import qiskit._numpy_compat @@ -61,6 +53,7 @@ # and not have to rely on attribute access. No action needed for top-level extension packages. sys.modules["qiskit._accelerate.circuit"] = _accelerate.circuit sys.modules["qiskit._accelerate.circuit_library"] = _accelerate.circuit_library +sys.modules["qiskit._accelerate.converters"] = _accelerate.converters sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = _accelerate.convert_2q_block_matrix sys.modules["qiskit._accelerate.dense_layout"] = _accelerate.dense_layout sys.modules["qiskit._accelerate.error_map"] = _accelerate.error_map @@ -91,6 +84,7 @@ sys.modules["qiskit._accelerate.synthesis.clifford"] = _accelerate.synthesis.clifford sys.modules["qiskit._accelerate.commutation_checker"] = _accelerate.commutation_checker sys.modules["qiskit._accelerate.commutation_analysis"] = _accelerate.commutation_analysis +sys.modules["qiskit._accelerate.commutation_cancellation"] = _accelerate.commutation_cancellation sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase sys.modules["qiskit._accelerate.split_2q_unitaries"] = _accelerate.split_2q_unitaries sys.modules["qiskit._accelerate.gate_direction"] = _accelerate.gate_direction diff --git a/qiskit/converters/circuit_to_dag.py b/qiskit/converters/circuit_to_dag.py index a330b8cbd682..5be9a721bafa 100644 --- a/qiskit/converters/circuit_to_dag.py +++ b/qiskit/converters/circuit_to_dag.py @@ -12,8 +12,8 @@ """Helper function for converting a circuit to a dag""" -from qiskit.dagcircuit.dagcircuit import DAGCircuit -from qiskit.dagcircuit.dagnode import DAGOpNode +from qiskit.circuit.library.blueprintcircuit import BlueprintCircuit +from qiskit._accelerate.converters import circuit_to_dag as core_circuit_to_dag def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_order=None): @@ -56,46 +56,22 @@ def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_ord circ.rz(0.5, q[1]).c_if(c, 2) dag = circuit_to_dag(circ) """ - dagcircuit = DAGCircuit() - dagcircuit.name = circuit.name - dagcircuit.global_phase = circuit.global_phase - dagcircuit.calibrations = circuit.calibrations - dagcircuit.metadata = circuit.metadata - - if qubit_order is None: - qubits = circuit.qubits - elif len(qubit_order) != circuit.num_qubits or set(qubit_order) != set(circuit.qubits): + # If we have an instance of BluePrintCircuit, make sure it is built by calling ._build() + if isinstance(circuit, BlueprintCircuit): + if not circuit._is_built: + circuit._build() + + if qubit_order is not None and ( + len(qubit_order) != circuit.num_qubits or set(qubit_order) != set(circuit.qubits) + ): raise ValueError("'qubit_order' does not contain exactly the same qubits as the circuit") - else: - qubits = qubit_order - if clbit_order is None: - clbits = circuit.clbits - elif len(clbit_order) != circuit.num_clbits or set(clbit_order) != set(circuit.clbits): + if clbit_order is not None and ( + len(clbit_order) != circuit.num_clbits or set(clbit_order) != set(circuit.clbits) + ): raise ValueError("'clbit_order' does not contain exactly the same clbits as the circuit") - else: - clbits = clbit_order - - dagcircuit.add_qubits(qubits) - dagcircuit.add_clbits(clbits) - - for var in circuit.iter_input_vars(): - dagcircuit.add_input_var(var) - for var in circuit.iter_captured_vars(): - dagcircuit.add_captured_var(var) - for var in circuit.iter_declared_vars(): - dagcircuit.add_declared_var(var) - - for register in circuit.qregs: - dagcircuit.add_qreg(register) - - for register in circuit.cregs: - dagcircuit.add_creg(register) - for instruction in circuit.data: - dagcircuit._apply_op_node_back( - DAGOpNode.from_instruction(instruction, deepcopy=copy_operations) - ) + dagcircuit = core_circuit_to_dag(circuit, copy_operations, qubit_order, clbit_order) dagcircuit.duration = circuit.duration dagcircuit.unit = circuit.unit diff --git a/qiskit/transpiler/passes/optimization/commutative_cancellation.py b/qiskit/transpiler/passes/optimization/commutative_cancellation.py index 836fa112fd84..130ff0609354 100644 --- a/qiskit/transpiler/passes/optimization/commutative_cancellation.py +++ b/qiskit/transpiler/passes/optimization/commutative_cancellation.py @@ -11,23 +11,16 @@ # that they have been altered from the originals. """Cancel the redundant (self-adjoint) gates through commutation relations.""" - -from collections import defaultdict -import numpy as np - -from qiskit.circuit.quantumregister import QuantumRegister -from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passes.optimization.commutation_analysis import CommutationAnalysis -from qiskit.dagcircuit import DAGCircuit, DAGInNode, DAGOutNode -from qiskit.circuit.commutation_library import CommutationChecker, StandardGateCommutations +from qiskit.circuit.commutation_library import StandardGateCommutations + from qiskit.circuit.library.standard_gates.u1 import U1Gate -from qiskit.circuit.library.standard_gates.rx import RXGate from qiskit.circuit.library.standard_gates.p import PhaseGate from qiskit.circuit.library.standard_gates.rz import RZGate -from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES +from qiskit._accelerate import commutation_cancellation +from qiskit._accelerate.commutation_checker import CommutationChecker +from qiskit.transpiler.passes.utils.control_flow import trivial_recurse _CUTOFF_PRECISION = 1e-5 @@ -59,6 +52,7 @@ def __init__(self, basis_gates=None, target=None): self.basis = set(basis_gates) else: self.basis = set() + self.target = target if target is not None: self.basis = set(target.operation_names) @@ -70,12 +64,11 @@ def __init__(self, basis_gates=None, target=None): # build a commutation checker restricted to the gates we cancel -- the others we # do not have to investigate, which allows to save time - commutation_checker = CommutationChecker( + self._commutation_checker = CommutationChecker( StandardGateCommutations, gates=self._gates | self._z_rotations | self._x_rotations ) - self.requires.append(CommutationAnalysis(_commutation_checker=commutation_checker)) - + @trivial_recurse def run(self, dag): """Run the CommutativeCancellation pass on `dag`. @@ -85,141 +78,5 @@ def run(self, dag): Returns: DAGCircuit: the optimized DAG. """ - var_z_gate = None - z_var_gates = [gate for gate in dag.count_ops().keys() if gate in self._var_z_map] - if z_var_gates: - # prioritize z gates in circuit - var_z_gate = self._var_z_map[next(iter(z_var_gates))] - else: - z_var_gates = [gate for gate in self.basis if gate in self._var_z_map] - if z_var_gates: - var_z_gate = self._var_z_map[next(iter(z_var_gates))] - - # Gate sets to be cancelled - cancellation_sets = defaultdict(lambda: []) - - # Traverse each qubit to generate the cancel dictionaries - # Cancel dictionaries: - # - For 1-qubit gates the key is (gate_type, qubit_id, commutation_set_id), - # the value is the list of gates that share the same gate type, qubit, commutation set. - # - For 2qbit gates the key: (gate_type, first_qbit, sec_qbit, first commutation_set_id, - # sec_commutation_set_id), the value is the list gates that share the same gate type, - # qubits and commutation sets. - for wire in dag.qubits: - wire_commutation_set = self.property_set["commutation_set"][wire] - - for com_set_idx, com_set in enumerate(wire_commutation_set): - if isinstance(com_set[0], (DAGInNode, DAGOutNode)): - continue - for node in com_set: - num_qargs = len(node.qargs) - if any(isinstance(p, ParameterExpression) for p in node.params): - continue # no support for cancellation of parameterized gates - if num_qargs == 1 and node.name in self._gates: - cancellation_sets[(node.name, wire, com_set_idx)].append(node) - if num_qargs == 1 and node.name in self._z_rotations: - cancellation_sets[("z_rotation", wire, com_set_idx)].append(node) - if num_qargs == 1 and node.name in ["rx", "x"]: - cancellation_sets[("x_rotation", wire, com_set_idx)].append(node) - # Don't deal with Y rotation, because Y rotation doesn't commute with CNOT, so - # it should be dealt with by optimized1qgate pass - elif num_qargs == 2 and node.qargs[0] == wire: - second_qarg = node.qargs[1] - q2_key = ( - node.name, - wire, - second_qarg, - com_set_idx, - self.property_set["commutation_set"][(node, second_qarg)], - ) - cancellation_sets[q2_key].append(node) - - for cancel_set_key in cancellation_sets: - if cancel_set_key[0] == "z_rotation" and var_z_gate is None: - continue - set_len = len(cancellation_sets[cancel_set_key]) - if set_len > 1 and cancel_set_key[0] in self._gates: - gates_to_cancel = cancellation_sets[cancel_set_key] - for c_node in gates_to_cancel[: (set_len // 2) * 2]: - dag.remove_op_node(c_node) - - elif set_len > 1 and cancel_set_key[0] in ["z_rotation", "x_rotation"]: - run = cancellation_sets[cancel_set_key] - run_qarg = run[0].qargs[0] - total_angle = 0.0 # lambda - total_phase = 0.0 - for current_node in run: - if ( - current_node.condition is not None - or len(current_node.qargs) != 1 - or current_node.qargs[0] != run_qarg - ): - raise RuntimeError("internal error") - - if current_node.name in ["p", "u1", "rz", "rx"]: - current_angle = float(current_node.params[0]) - elif current_node.name in ["z", "x"]: - current_angle = np.pi - elif current_node.name == "t": - current_angle = np.pi / 4 - elif current_node.name == "s": - current_angle = np.pi / 2 - else: - raise RuntimeError( - f"Angle for operation {current_node.name } is not defined" - ) - - # Compose gates - total_angle = current_angle + total_angle - if current_node.definition: - total_phase += current_node.definition.global_phase - - # Replace the data of the first node in the run - if cancel_set_key[0] == "z_rotation": - new_op = var_z_gate(total_angle) - elif cancel_set_key[0] == "x_rotation": - new_op = RXGate(total_angle) - else: - raise RuntimeError("impossible case") - - new_op_phase = 0 - if np.mod(total_angle, (2 * np.pi)) > _CUTOFF_PRECISION: - new_qarg = QuantumRegister(1, "q") - new_dag = DAGCircuit() - new_dag.add_qreg(new_qarg) - new_dag.apply_operation_back(new_op, [new_qarg[0]]) - dag.substitute_node_with_dag(run[0], new_dag) - if new_op.definition: - new_op_phase = new_op.definition.global_phase - - dag.global_phase = total_phase - new_op_phase - - # Delete the other nodes in the run - for current_node in run[1:]: - dag.remove_op_node(current_node) - - if np.mod(total_angle, (2 * np.pi)) < _CUTOFF_PRECISION: - dag.remove_op_node(run[0]) - - dag = self._handle_control_flow_ops(dag) - - return dag - - def _handle_control_flow_ops(self, dag): - """ - This is similar to transpiler/passes/utils/control_flow.py except that the - commutation analysis is redone for the control flow blocks. - """ - - pass_manager = PassManager([CommutationAnalysis(), self]) - for node in dag.op_nodes(): - if node.name not in CONTROL_FLOW_OP_NAMES: - continue - mapped_blocks = [] - for block in node.op.blocks: - new_circ = pass_manager.run(block) - mapped_blocks.append(new_circ) - dag.substitute_node( - node, node.op.replace_blocks(mapped_blocks), propagate_condition=False - ) + commutation_cancellation.cancel_commutations(dag, self._commutation_checker, self.basis) return dag diff --git a/releasenotes/notes/py3.9-min-now-c9781484a0eb288e.yaml b/releasenotes/notes/py3.9-min-now-c9781484a0eb288e.yaml new file mode 100644 index 000000000000..a58dc69f9e4a --- /dev/null +++ b/releasenotes/notes/py3.9-min-now-c9781484a0eb288e.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The minimum supported version of Python is now 3.9, this has been raised + from the previous minimum support version of 3.8. This change was necessary + because the upstream cPython project no longer supports Python 3.8. diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 04e71a0dd4d7..db8663ace558 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -1948,8 +1948,11 @@ def test_pre_v12_rejects_standalone_var(self, version): """Test that dumping to older QPY versions rejects standalone vars.""" a = expr.Var.new("a", types.Bool()) qc = QuantumCircuit(inputs=[a]) - with io.BytesIO() as fptr, self.assertRaisesRegex( - UnsupportedFeatureForVersion, "version 12 is required.*realtime variables" + with ( + io.BytesIO() as fptr, + self.assertRaisesRegex( + UnsupportedFeatureForVersion, "version 12 is required.*realtime variables" + ), ): dump(qc, fptr, version=version) @@ -1959,8 +1962,9 @@ def test_pre_v12_rejects_index(self, version): # Be sure to use a register, since standalone vars would be rejected for other reasons. qc = QuantumCircuit(ClassicalRegister(2, "cr")) qc.store(expr.index(qc.cregs[0], 0), False) - with io.BytesIO() as fptr, self.assertRaisesRegex( - UnsupportedFeatureForVersion, "version 12 is required.*Index" + with ( + io.BytesIO() as fptr, + self.assertRaisesRegex(UnsupportedFeatureForVersion, "version 12 is required.*Index"), ): dump(qc, fptr, version=version) diff --git a/test/python/circuit/test_control_flow_builders.py b/test/python/circuit/test_control_flow_builders.py index e41b5c1f9f31..7a3a0873ae77 100644 --- a/test/python/circuit/test_control_flow_builders.py +++ b/test/python/circuit/test_control_flow_builders.py @@ -3464,9 +3464,12 @@ def test_switch_rejects_entering_case_after_close(self): def test_switch_rejects_reentering_case(self): """It shouldn't be possible to enter a case within another case.""" circuit = QuantumCircuit(1, 1) - with circuit.switch(0) as case, case(0), self.assertRaisesRegex( - CircuitError, r"Cannot enter more than one case at once" - ), case(1): + with ( + circuit.switch(0) as case, + case(0), + self.assertRaisesRegex(CircuitError, r"Cannot enter more than one case at once"), + case(1), + ): pass @ddt.data("1", 1.0, None, (1, 2)) diff --git a/test/python/transpiler/test_commutative_cancellation.py b/test/python/transpiler/test_commutative_cancellation.py index 71bab61708cd..88f1d99bef31 100644 --- a/test/python/transpiler/test_commutative_cancellation.py +++ b/test/python/transpiler/test_commutative_cancellation.py @@ -13,6 +13,7 @@ """Gate cancellation pass testing""" import unittest + import numpy as np from qiskit import QuantumRegister, QuantumCircuit @@ -76,7 +77,7 @@ def test_all_gates(self): expected = QuantumCircuit(qr) expected.append(RZGate(2.0), [qr[0]]) expected.rx(1.0, qr[0]) - + expected.global_phase = 0.5 self.assertEqual(expected, new_circuit) def test_commutative_circuit1(self): @@ -433,7 +434,7 @@ def test_commutative_circuit3(self): expected.append(RZGate(np.pi * 17 / 12), [qr[2]]) expected.append(RZGate(np.pi * 2 / 3), [qr[3]]) expected.cx(qr[2], qr[1]) - + expected.global_phase = 3 * np.pi / 8 self.assertEqual( expected, new_circuit, msg=f"expected:\n{expected}\nnew_circuit:\n{new_circuit}" ) diff --git a/test/python/transpiler/test_passmanager.py b/test/python/transpiler/test_passmanager.py index ce4d28bf314e..60375a820655 100644 --- a/test/python/transpiler/test_passmanager.py +++ b/test/python/transpiler/test_passmanager.py @@ -27,7 +27,7 @@ DoWhileController, ) from qiskit.transpiler import PassManager, PropertySet, TransformationPass -from qiskit.transpiler.passes import CommutativeCancellation +from qiskit.transpiler.passes import RXCalibrationBuilder from qiskit.transpiler.passes import Optimize1qGates, BasisTranslator from qiskit.circuit.library.standard_gates.equivalence_library import ( StandardEquivalenceLibrary as std_eqlib, @@ -97,7 +97,6 @@ def test_callback_with_pass_requires(self): expected_end = QuantumCircuit(qr) expected_end.cx(qr[0], qr[2]) - expected_end_dag = circuit_to_dag(expected_end) calls = [] @@ -107,20 +106,19 @@ def callback(**kwargs): calls.append(out_dict) passmanager = PassManager() - passmanager.append(CommutativeCancellation(basis_gates=["u1", "u2", "u3", "cx"])) + passmanager.append(RXCalibrationBuilder()) passmanager.run(circuit, callback=callback) self.assertEqual(len(calls), 2) self.assertEqual(len(calls[0]), 5) self.assertEqual(calls[0]["count"], 0) - self.assertEqual(calls[0]["pass_"].name(), "CommutationAnalysis") + self.assertEqual(calls[0]["pass_"].name(), "NormalizeRXAngle") self.assertEqual(expected_start_dag, calls[0]["dag"]) self.assertIsInstance(calls[0]["time"], float) self.assertIsInstance(calls[0]["property_set"], PropertySet) self.assertEqual("MyCircuit", calls[0]["dag"].name) self.assertEqual(len(calls[1]), 5) self.assertEqual(calls[1]["count"], 1) - self.assertEqual(calls[1]["pass_"].name(), "CommutativeCancellation") - self.assertEqual(expected_end_dag, calls[1]["dag"]) + self.assertEqual(calls[1]["pass_"].name(), "RXCalibrationBuilder") self.assertIsInstance(calls[0]["time"], float) self.assertIsInstance(calls[0]["property_set"], PropertySet) self.assertEqual("MyCircuit", calls[1]["dag"].name) diff --git a/test/qpy_compat/process_version.sh b/test/qpy_compat/process_version.sh index 56743c613316..3fc34557b69e 100755 --- a/test/qpy_compat/process_version.sh +++ b/test/qpy_compat/process_version.sh @@ -45,9 +45,9 @@ if [[ ! -d qpy_$version ]] ; then echo "Building venv for qiskit-terra $version" python -m venv $version if [[ ${parts[0]} -eq 0 ]] ; then - ./$version/bin/pip install "qiskit-terra==$version" + ./$version/bin/pip install -c qpy_test_constraints.txt "qiskit-terra==$version" else - ./$version/bin/pip install "qiskit==$version" + ./$version/bin/pip install -c qpy_test_constraints.txt "qiskit==$version" fi mkdir qpy_$version pushd qpy_$version diff --git a/test/qpy_compat/qpy_test_constraints.txt b/test/qpy_compat/qpy_test_constraints.txt new file mode 100644 index 000000000000..03f798b3c01a --- /dev/null +++ b/test/qpy_compat/qpy_test_constraints.txt @@ -0,0 +1,2 @@ +numpy===1.24.4 +scipy===1.10.1 diff --git a/test/qpy_compat/run_tests.sh b/test/qpy_compat/run_tests.sh index 4fc6bc5b91fd..3810773ec0f3 100755 --- a/test/qpy_compat/run_tests.sh +++ b/test/qpy_compat/run_tests.sh @@ -20,7 +20,7 @@ export PYTHONHASHSEED=$(python -S -c "import random; print(random.randint(1, 429 echo "PYTHONHASHSEED=$PYTHONHASHSEED" python -m venv qiskit_venv -qiskit_venv/bin/pip install ../.. +qiskit_venv/bin/pip install -c ../../constraints.txt ../.. parallel bash ./process_version.sh ::: `git tag --sort=-creatordate` diff --git a/test/visual/mpl/graph/references/bloch_multivector.png b/test/visual/mpl/graph/references/bloch_multivector.png index 896e513ef368..e3036bc2ccb9 100644 Binary files a/test/visual/mpl/graph/references/bloch_multivector.png and b/test/visual/mpl/graph/references/bloch_multivector.png differ diff --git a/test/visual/mpl/graph/references/bloch_multivector_figsize_improvements.png b/test/visual/mpl/graph/references/bloch_multivector_figsize_improvements.png index 5f5ba75d9428..c5a082e8039a 100644 Binary files a/test/visual/mpl/graph/references/bloch_multivector_figsize_improvements.png and b/test/visual/mpl/graph/references/bloch_multivector_figsize_improvements.png differ diff --git a/test/visual/mpl/graph/references/state_city.png b/test/visual/mpl/graph/references/state_city.png index a9af6d8b87f2..ecbdb3c3d2bc 100644 Binary files a/test/visual/mpl/graph/references/state_city.png and b/test/visual/mpl/graph/references/state_city.png differ diff --git a/tox.ini b/tox.ini index 89dc84d1758a..a0c5665695d2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 4.0 -envlist = py38, py39, py310, py311, py312, lint-incr +envlist = py39, py310, py311, py312, lint-incr isolated_build = true [testenv] @@ -15,7 +15,7 @@ setenv = QISKIT_SUPRESS_PACKAGING_WARNINGS=Y QISKIT_TEST_CAPTURE_STREAMS=1 QISKIT_PARALLEL=FALSE -passenv = +passenv = RUSTUP_TOOLCHAIN RAYON_NUM_THREADS OMP_NUM_THREADS