From d2754fb5bc4bfff5b2f2897eaddc71800e88b61a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 5 Feb 2025 12:52:27 -0500 Subject: [PATCH 1/2] Remove deprecated stochastic swap transpiler pass This commit removes the stochastic swap transpiler pass and its associated routing stage plugin. This pass has been superseded by sabre at this point and it was deprecated in the Qiskit 1.3.0 release. Part of #13699 --- crates/accelerate/src/edge_collections.rs | 68 - crates/accelerate/src/lib.rs | 2 - crates/accelerate/src/stochastic_swap.rs | 342 ---- crates/pyext/src/lib.rs | 1 - pyproject.toml | 1 - qiskit/__init__.py | 1 - qiskit/transpiler/passes/__init__.py | 2 - qiskit/transpiler/passes/routing/__init__.py | 1 - .../passes/routing/stochastic_swap.py | 532 ------ .../preset_passmanagers/builtin_plugins.py | 57 - ...move-stochastic-swap-88e2d6be4c0d5713.yaml | 42 + test/python/compiler/test_transpiler.py | 205 +- test/python/transpiler/test_mappers.py | 15 +- test/python/transpiler/test_sabre_layout.py | 25 +- test/python/transpiler/test_stage_plugin.py | 26 - .../python/transpiler/test_stochastic_swap.py | 1661 ----------------- 16 files changed, 55 insertions(+), 2926 deletions(-) delete mode 100644 crates/accelerate/src/edge_collections.rs delete mode 100644 crates/accelerate/src/stochastic_swap.rs delete mode 100644 qiskit/transpiler/passes/routing/stochastic_swap.py create mode 100644 releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml delete mode 100644 test/python/transpiler/test_stochastic_swap.py diff --git a/crates/accelerate/src/edge_collections.rs b/crates/accelerate/src/edge_collections.rs deleted file mode 100644 index 825016229c6c..000000000000 --- a/crates/accelerate/src/edge_collections.rs +++ /dev/null @@ -1,68 +0,0 @@ -// This code is part of Qiskit. -// -// (C) Copyright IBM 2022 -// -// This code is licensed under the Apache License, Version 2.0. You may -// obtain a copy of this license in the LICENSE.txt file in the root directory -// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -// -// Any modifications or derivative works of this code must retain this -// copyright notice, and modified files need to carry a notice indicating -// that they have been altered from the originals. - -use numpy::IntoPyArray; -use pyo3::prelude::*; - -use crate::nlayout::PhysicalQubit; - -/// A simple container that contains a vector representing edges in the -/// coupling map that are found to be optimal by the swap mapper. -#[pyclass(module = "qiskit._accelerate.stochastic_swap")] -#[derive(Clone, Debug)] -pub struct EdgeCollection { - pub edges: Vec, -} - -impl Default for EdgeCollection { - fn default() -> Self { - Self::new() - } -} - -#[pymethods] -impl EdgeCollection { - #[new] - #[pyo3(text_signature = "(/)")] - pub fn new() -> Self { - EdgeCollection { edges: Vec::new() } - } - - /// Add two edges, in order, to the collection. - /// - /// Args: - /// edge_start (int): The beginning edge. - /// edge_end (int): The end of the edge. - #[pyo3(text_signature = "(self, edge_start, edge_end, /)")] - pub fn add(&mut self, edge_start: PhysicalQubit, edge_end: PhysicalQubit) { - self.edges.push(edge_start); - self.edges.push(edge_end); - } - - /// Return the numpy array of edges - /// - /// The out array is the flattened edge list from the coupling graph. - /// For example, if the edge list were ``[(0, 1), (1, 2), (2, 3)]`` the - /// output array here would be ``[0, 1, 1, 2, 2, 3]``. - #[pyo3(text_signature = "(self, /)")] - pub fn edges(&self, py: Python) -> PyObject { - self.edges.clone().into_pyarray(py).into_any().unbind() - } - - fn __getstate__(&self) -> Vec { - self.edges.clone() - } - - fn __setstate__(&mut self, state: Vec) { - self.edges = state - } -} diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index e26689f4f0c3..4145c8c5e1fb 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -24,7 +24,6 @@ pub mod commutation_checker; pub mod consolidate_blocks; pub mod convert_2q_block_matrix; pub mod dense_layout; -pub mod edge_collections; pub mod elide_permutations; pub mod equivalence; pub mod error_map; @@ -47,7 +46,6 @@ pub mod sparse_observable; pub mod sparse_pauli_op; pub mod split_2q_unitaries; pub mod star_prerouting; -pub mod stochastic_swap; pub mod synthesis; pub mod target_transpiler; pub mod twirling; diff --git a/crates/accelerate/src/stochastic_swap.rs b/crates/accelerate/src/stochastic_swap.rs deleted file mode 100644 index a102d4525771..000000000000 --- a/crates/accelerate/src/stochastic_swap.rs +++ /dev/null @@ -1,342 +0,0 @@ -// This code is part of Qiskit. -// -// (C) Copyright IBM 2022 -// -// 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. - -// Needed to pass shared state between functions -// closures don't work because of recurssion -#![allow(clippy::too_many_arguments)] -#![allow(clippy::type_complexity)] - -use std::sync::RwLock; - -use hashbrown::HashSet; - -use ndarray::prelude::*; -use numpy::{PyReadonlyArray1, PyReadonlyArray2}; -use rayon::prelude::*; - -use pyo3::prelude::*; -use pyo3::wrap_pyfunction; - -use rand::prelude::*; -use rand_distr::{Distribution, Normal}; -use rand_pcg::Pcg64Mcg; - -use crate::edge_collections::EdgeCollection; -use crate::getenv_use_multiple_threads; -use crate::nlayout::{NLayout, PhysicalQubit, VirtualQubit}; - -#[inline] -fn compute_cost( - dist: &ArrayView2, - layout: &NLayout, - gates: &[VirtualQubit], - num_gates: usize, -) -> f64 { - (0..num_gates) - .map(|gate| { - dist[[ - gates[2 * gate].to_phys(layout).index(), - gates[2 * gate + 1].to_phys(layout).index(), - ]] - }) - .sum() -} - -/// Computes the symmetric random scaling (perturbation) matrix, -/// and places the values in the 'scale' array. -/// -/// Args: -/// scale (ndarray): An array of doubles where the values are to be stored. -/// cdist2 (ndarray): Array representing the coupling map distance squared. -/// rand (double *): Array of rands of length num_qubits*(num_qubits+1)//2. -/// num_qubits (int): Number of physical qubits. -#[inline] -fn compute_random_scaling( - scale: &mut Array2, - cdist2: &ArrayView2, - rand: &[f64], - num_qubits: usize, -) { - let mut idx: usize = 0; - for ii in 0..num_qubits { - for jj in 0..ii { - scale[[ii, jj]] = rand[idx] * cdist2[[ii, jj]]; - scale[[jj, ii]] = scale[[ii, jj]]; - idx += 1 - } - } -} - -fn swap_trial( - num_qubits: usize, - int_layout: &NLayout, - int_qubit_subset: &[VirtualQubit], - gates: &[VirtualQubit], - cdist: ArrayView2, - cdist2: ArrayView2, - edges: &[PhysicalQubit], - seed: u64, - trial_num: u64, - locked_best_possible: Option<&RwLock<&mut Option<(u64, f64, EdgeCollection, NLayout)>>>, -) -> Option<(f64, EdgeCollection, NLayout, usize)> { - if let Some(locked_best_possible) = locked_best_possible { - // Return fast if a depth == 1 solution was already found in another parallel - // trial. However for deterministic results in cases of multiple depth == 1 - // solutions still search for a solution if this trial number is less than - // the found solution (this mirrors the previous behavior of a serial loop). - let best_possible = locked_best_possible.read().unwrap(); - if best_possible.is_some() && best_possible.as_ref().unwrap().0 < trial_num { - return None; - } - } - let mut opt_edges = EdgeCollection::new(); - let mut trial_layout = int_layout.clone(); - let mut optimal_layout = int_layout.clone(); - - let num_gates: usize = gates.len() / 2; - let num_edges: usize = edges.len() / 2; - - let mut cost_reduced; - let mut depth_step: usize = 1; - let depth_max: usize = 2 * num_qubits + 1; - let mut min_cost: f64; - let mut new_cost: f64; - let mut dist: f64; - - let mut optimal_start = PhysicalQubit::new(u32::MAX); - let mut optimal_end = PhysicalQubit::new(u32::MAX); - let mut optimal_start_qubit = VirtualQubit::new(u32::MAX); - let mut optimal_end_qubit = VirtualQubit::new(u32::MAX); - - let mut scale = Array2::zeros((num_qubits, num_qubits)); - - let distribution = Normal::new(1.0, 1.0 / num_qubits as f64).unwrap(); - let mut rng: Pcg64Mcg = Pcg64Mcg::seed_from_u64(seed); - let rand_arr: Vec = distribution - .sample_iter(&mut rng) - .take(num_qubits * (num_qubits + 1) / 2) - .collect(); - - compute_random_scaling(&mut scale, &cdist2, &rand_arr, num_qubits); - - let input_qubit_set = int_qubit_subset.iter().copied().collect::>(); - - while depth_step < depth_max { - let mut qubit_set = input_qubit_set.clone(); - while !qubit_set.is_empty() { - min_cost = compute_cost(&scale.view(), &trial_layout, gates, num_gates); - // Try to decrease the objective function - cost_reduced = false; - for idx in 0..num_edges { - let start_edge = edges[2 * idx]; - let end_edge = edges[2 * idx + 1]; - let start_qubit = start_edge.to_virt(&trial_layout); - let end_qubit = end_edge.to_virt(&trial_layout); - if qubit_set.contains(&start_qubit) && qubit_set.contains(&end_qubit) { - // Try this edge to reduce cost - trial_layout.swap_physical(start_edge, end_edge); - // compute objective function - new_cost = compute_cost(&scale.view(), &trial_layout, gates, num_gates); - // record progress if we succeed - if new_cost < min_cost { - cost_reduced = true; - min_cost = new_cost; - optimal_layout = trial_layout.clone(); - optimal_start = start_edge; - optimal_end = end_edge; - optimal_start_qubit = start_qubit; - optimal_end_qubit = end_qubit; - } - trial_layout.swap_physical(start_edge, end_edge); - } - } - // After going over all edges - // Were there any good swap choices? - if cost_reduced { - qubit_set.remove(&optimal_start_qubit); - qubit_set.remove(&optimal_end_qubit); - trial_layout = optimal_layout.clone(); - opt_edges.add(optimal_start, optimal_end); - } else { - break; - } - } - // We have either run out of swap pairs to try or failed to improve - // the cost - - // Compute the coupling graph distance - dist = compute_cost(&cdist, &trial_layout, gates, num_gates); - // If all gates can be applied now we're finished. - // Otherwise we need to consider a deeper swap circuit - if dist as usize == num_gates { - break; - } - // increment the depth - depth_step += 1; - } - // Either we have succeeded at some depth d < d_max or failed - dist = compute_cost(&cdist, &trial_layout, gates, num_gates); - if let Some(locked_best_possible) = locked_best_possible { - if dist as usize == num_gates && depth_step == 1 { - let mut best_possible = locked_best_possible.write().unwrap(); - // In the case an ideal solution has already been found to preserve - // behavior consistent with the single threaded predecessor to this function - // we defer to the earlier trial - if best_possible.is_none() || best_possible.as_ref().unwrap().0 > trial_num { - **best_possible = Some((trial_num, dist, opt_edges, trial_layout)); - } - return None; - } - } - Some((dist, opt_edges, trial_layout, depth_step)) -} - -/// Run the random trials as part of the layer permutation used internally for -/// the stochastic swap algorithm. -/// -/// This function is multithreaded and will spawn a thread pool as part of its -/// execution. By default the number of threads will be equal to the number of -/// CPUs. You can tune the number of threads with the RAYON_NUM_THREADS -/// environment variable. For example, setting RAYON_NUM_THREADS=4 would limit -/// the thread pool to 4 threads. -/// -/// Args: -/// num_trials (int): The number of random trials to attempt -/// num_qubits (int): The number of qubits -/// int_layout (NLayout): The initial layout for the layer. The layout is a mapping -/// of virtual qubits to physical qubits in the coupling graph -/// int_qubit_subset (ndarray): A 1D array of qubit indices for the set of qubits in the -/// coupling map that we've chosen to map into. -/// int_gates (ndarray): A 1D array of qubit pairs that each 2 qubit gate operates on. -/// The pairs are flattened on the array so that each pair in the list of 2q gates -/// are adjacent in the array. For example, if the 2q interaction list was -/// ``[(0, 1), (2, 1), (3, 2)]``, the input here would be ``[0, 1, 2, 1, 3, 2]``. -/// cdist (ndarray): The distance matrix for the coupling graph of the target -/// backend -/// cdist2 (ndarray): The distance matrix squared for the coupling graph of the -/// target backend -/// edges (ndarray): A flattened 1d array of the edge list of the coupling graph. -/// The pairs are flattened on the array so that each node pair in the edge are -/// adjacent in the array. For example, if the edge list were ``[(0, 1), (1, 2), (2, 3)]`` -/// the input array here would be ``[0, 1, 1, 2, 2, 3]``. -/// seed (int): An optional seed for the rng used to generate the random perturbation -/// matrix used in each trial -/// Returns: -/// tuple: If a valid layout permutation is found a tuple of the form: -/// ``(edges, layout, depth)`` is returned. If a solution is not found the output -/// will be ``(None, None, max int)``. -#[pyfunction] -#[pyo3( - signature = (num_trials, num_qubits, int_layout, int_qubit_subset, int_gates, cdist, cdist2, edges, seed=None) -)] -pub fn swap_trials( - num_trials: u64, - num_qubits: usize, - int_layout: &NLayout, - int_qubit_subset: PyReadonlyArray1, - int_gates: PyReadonlyArray1, - cdist: PyReadonlyArray2, - cdist2: PyReadonlyArray2, - edges: PyReadonlyArray1, - seed: Option, -) -> PyResult<(Option, Option, usize)> { - let int_qubit_subset_arr = int_qubit_subset.as_slice()?; - let int_gates_arr = int_gates.as_slice()?; - let cdist_arr = cdist.as_array(); - let cdist2_arr = cdist2.as_array(); - let edges_arr = edges.as_slice()?; - let num_gates: usize = int_gates.len()? / 2; - let mut best_possible: Option<(u64, f64, EdgeCollection, NLayout)> = None; - let locked_best_possible: RwLock<&mut Option<(u64, f64, EdgeCollection, NLayout)>> = - RwLock::new(&mut best_possible); - let outer_rng: Pcg64Mcg = match seed { - Some(seed) => Pcg64Mcg::seed_from_u64(seed), - None => Pcg64Mcg::from_entropy(), - }; - let seed_vec: Vec = outer_rng - .sample_iter(&rand::distributions::Standard) - .take(num_trials as usize) - .collect(); - // Run in parallel only if we're not already in a multiprocessing context - // unless force threads is set. - let run_in_parallel = getenv_use_multiple_threads(); - - let mut best_depth = usize::MAX; - let mut best_edges: Option = None; - let mut best_layout: Option = None; - if run_in_parallel { - let result: Vec> = (0..num_trials) - .into_par_iter() - .map(|trial_num| { - swap_trial( - num_qubits, - int_layout, - int_qubit_subset_arr, - int_gates_arr, - cdist_arr, - cdist2_arr, - edges_arr, - seed_vec[trial_num as usize], - trial_num, - Some(&locked_best_possible), - ) - }) - .collect(); - match best_possible { - Some((_trial_num, _dist, edges, layout)) => { - best_edges = Some(edges); - best_layout = Some(layout); - best_depth = 1; - } - None => { - for (dist, edges, layout, depth) in result.into_iter().flatten() { - if dist as usize == num_gates && depth < best_depth { - best_edges = Some(edges); - best_layout = Some(layout); - best_depth = depth; - } - } - } - }; - } else { - for trial_num in 0..num_trials { - let (dist, edges, layout, depth) = swap_trial( - num_qubits, - int_layout, - int_qubit_subset_arr, - int_gates_arr, - cdist_arr, - cdist2_arr, - edges_arr, - seed_vec[trial_num as usize], - trial_num, - None, - ) - .unwrap(); - if dist as usize == num_gates && depth < best_depth { - best_edges = Some(edges); - best_layout = Some(layout); - best_depth = depth; - if depth == 1 { - return Ok((best_edges, best_layout, best_depth)); - } - } - } - } - Ok((best_edges, best_layout, best_depth)) -} - -pub fn stochastic_swap(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(swap_trials))?; - m.add_class::()?; - Ok(()) -} diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index d2a0db8d19fd..eb62505daaba 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -59,7 +59,6 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, ::qiskit_accelerate::sparse_pauli_op::sparse_pauli_op, "sparse_pauli_op")?; add_submodule(m, ::qiskit_accelerate::split_2q_unitaries::split_2q_unitaries_mod, "split_2q_unitaries")?; add_submodule(m, ::qiskit_accelerate::star_prerouting::star_prerouting, "star_prerouting")?; - add_submodule(m, ::qiskit_accelerate::stochastic_swap::stochastic_swap, "stochastic_swap")?; add_submodule(m, ::qiskit_accelerate::synthesis::synthesis, "synthesis")?; add_submodule(m, ::qiskit_accelerate::target_transpiler::target, "target")?; add_submodule(m, ::qiskit_accelerate::twirling::twirling, "twirling")?; diff --git a/pyproject.toml b/pyproject.toml index a89f191fe848..b5609b2baad9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ basic = "qiskit.transpiler.preset_passmanagers.builtin_plugins:BasicSwapPassMana lookahead = "qiskit.transpiler.preset_passmanagers.builtin_plugins:LookaheadSwapPassManager" none = "qiskit.transpiler.preset_passmanagers.builtin_plugins:NoneRoutingPassManager" sabre = "qiskit.transpiler.preset_passmanagers.builtin_plugins:SabreSwapPassManager" -stochastic = "qiskit.transpiler.preset_passmanagers.builtin_plugins:StochasticSwapPassManager" [project.entry-points."qiskit.transpiler.optimization"] default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:OptimizationPassManager" diff --git a/qiskit/__init__.py b/qiskit/__init__.py index c121c99112f8..7f65e9d93bb7 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -81,7 +81,6 @@ sys.modules["qiskit._accelerate.sparse_observable"] = _accelerate.sparse_observable sys.modules["qiskit._accelerate.sparse_pauli_op"] = _accelerate.sparse_pauli_op sys.modules["qiskit._accelerate.star_prerouting"] = _accelerate.star_prerouting -sys.modules["qiskit._accelerate.stochastic_swap"] = _accelerate.stochastic_swap sys.modules["qiskit._accelerate.elide_permutations"] = _accelerate.elide_permutations sys.modules["qiskit._accelerate.target"] = _accelerate.target sys.modules["qiskit._accelerate.two_qubit_decompose"] = _accelerate.two_qubit_decompose diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 8823e1ce7233..39d7406673ed 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -43,7 +43,6 @@ BasicSwap LookaheadSwap - StochasticSwap SabreSwap Commuting2qGateRouter StarPreRouting @@ -207,7 +206,6 @@ from .routing import BasicSwap from .routing import LayoutTransformation from .routing import LookaheadSwap -from .routing import StochasticSwap from .routing import SabreSwap from .routing import Commuting2qGateRouter from .routing import StarPreRouting diff --git a/qiskit/transpiler/passes/routing/__init__.py b/qiskit/transpiler/passes/routing/__init__.py index a1ac25fb4145..962fc4b087f0 100644 --- a/qiskit/transpiler/passes/routing/__init__.py +++ b/qiskit/transpiler/passes/routing/__init__.py @@ -15,7 +15,6 @@ from .basic_swap import BasicSwap from .layout_transformation import LayoutTransformation from .lookahead_swap import LookaheadSwap -from .stochastic_swap import StochasticSwap from .sabre_swap import SabreSwap from .commuting_2q_gate_routing.commuting_2q_gate_router import Commuting2qGateRouter from .commuting_2q_gate_routing.swap_strategy import SwapStrategy diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py deleted file mode 100644 index efbd7e37f626..000000000000 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ /dev/null @@ -1,532 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# 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. - -"""Map a DAGCircuit onto a ``coupling_map`` adding swap gates.""" - -import itertools -import logging -from math import inf -import numpy as np - -from qiskit.converters import dag_to_circuit, circuit_to_dag -from qiskit.circuit.classical import expr, types -from qiskit.circuit.quantumregister import QuantumRegister -from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.dagcircuit import DAGCircuit -from qiskit.circuit.library.standard_gates import SwapGate -from qiskit.transpiler.layout import Layout -from qiskit.transpiler.target import Target -from qiskit.circuit import ( - Clbit, - IfElseOp, - WhileLoopOp, - ForLoopOp, - SwitchCaseOp, - ControlFlowOp, - CASE_DEFAULT, -) -from qiskit._accelerate import stochastic_swap as stochastic_swap_rs -from qiskit._accelerate import nlayout -from qiskit.transpiler.passes.layout import disjoint_utils -from qiskit.utils import deprecate_func - -from .utils import get_swap_map_dag - -logger = logging.getLogger(__name__) - - -class StochasticSwap(TransformationPass): - """Map a DAGCircuit onto a `coupling_map` adding swap gates. - - Uses a randomized algorithm. - - Notes: - 1. Measurements may occur and be followed by swaps that result in repeated - measurement of the same qubit. Near-term experiments cannot implement - these circuits, so some care is required when using this mapper - with experimental backend targets. - - 2. We do not use the fact that the input state is zero to simplify - the circuit. - """ - - @deprecate_func( - since="1.3", - removal_timeline="in the 2.0 release", - additional_msg="The StochasticSwap transpilation pass is a suboptimal " - "routing algorithm and has been superseded by the SabreSwap pass.", - ) - def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_layout=None): - """StochasticSwap initializer. - - The coupling map is a connected graph - - If these are not satisfied, the behavior is undefined. - - Args: - coupling_map (Union[CouplingMap, Target]): Directed graph representing a coupling - map. - trials (int): maximum number of iterations to attempt - seed (int): seed for random number generator - fake_run (bool): if true, it will only pretend to do routing, i.e., no - swap is effectively added. - initial_layout (Layout): starting layout at beginning of pass. - """ - super().__init__() - - if isinstance(coupling_map, Target): - self.target = coupling_map - self.coupling_map = self.target.build_coupling_map() - else: - self.target = None - self.coupling_map = coupling_map - self.trials = trials - self.seed = seed - self.rng = None - self.fake_run = fake_run - self.qregs = None - self.initial_layout = initial_layout - self._int_to_qubit = None - - def run(self, dag): - """Run the StochasticSwap pass on `dag`. - - Args: - dag (DAGCircuit): DAG to map. - - Returns: - DAGCircuit: A mapped DAG. - - Raises: - TranspilerError: if the coupling map or the layout are not - compatible with the DAG, or if the coupling_map=None - """ - - if self.coupling_map is None: - raise TranspilerError("StochasticSwap cannot run with coupling_map=None") - - if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: - raise TranspilerError("StochasticSwap runs on physical circuits only") - - if len(dag.qubits) > len(self.coupling_map.physical_qubits): - raise TranspilerError("The layout does not match the amount of qubits in the DAG") - disjoint_utils.require_layout_isolated_to_component( - dag, self.coupling_map if self.target is None else self.target - ) - - self.rng = np.random.default_rng(self.seed) - - canonical_register = dag.qregs["q"] - if self.initial_layout is None: - self.initial_layout = Layout.generate_trivial_layout(canonical_register) - # Qubit indices are used to assign an integer to each virtual qubit during the routing: it's - # a mapping of {virtual: virtual}, for converting between Python and Rust forms. - self._int_to_qubit = tuple(dag.qubits) - - self.qregs = dag.qregs - logger.debug("StochasticSwap rng seeded with seed=%s", self.seed) - self.coupling_map.compute_distance_matrix() - new_dag = self._mapper(dag, self.coupling_map, trials=self.trials) - return new_dag - - def _layer_permutation(self, dag, layer_partition, layout, qubit_subset, coupling, trials): - """Find a swap circuit that implements a permutation for this layer. - - The goal is to swap qubits such that qubits in the same two-qubit gates - are adjacent. - - Based on S. Bravyi's algorithm. - - Args: - layer_partition (list): The layer_partition is a list of (qu)bit - lists and each qubit is a tuple (qreg, index). - layout (Layout): The layout is a Layout object mapping virtual - qubits in the input circuit to physical qubits in the coupling - graph. It reflects the current positions of the data. - qubit_subset (list): The qubit_subset is the set of qubits in - the coupling graph that we have chosen to map into, as tuples - (Register, index). - coupling (CouplingMap): Directed graph representing a coupling map. - This coupling map should be one that was provided to the - stochastic mapper. - trials (int): Number of attempts the randomized algorithm makes. - - Returns: - Tuple: success_flag, best_circuit, best_depth, best_layout - - If success_flag is True, then best_circuit contains a DAGCircuit with - the swap circuit, best_depth contains the depth of the swap circuit, - and best_layout contains the new positions of the data qubits after the - swap circuit has been applied. - - Raises: - TranspilerError: if anything went wrong. - """ - logger.debug("layer_permutation: layer_partition = %s", layer_partition) - logger.debug("layer_permutation: layout = %s", layout.get_virtual_bits()) - logger.debug("layer_permutation: qubit_subset = %s", qubit_subset) - logger.debug("layer_permutation: trials = %s", trials) - - # The input dag is on a flat canonical register - canonical_register = QuantumRegister(len(layout), "q") - - gates = [] # list of lists of tuples [[(register, index), ...], ...] - for gate_args in layer_partition: - if len(gate_args) > 2: - raise TranspilerError("Layer contains > 2-qubit gates") - if len(gate_args) == 2: - gates.append(tuple(gate_args)) - logger.debug("layer_permutation: gates = %s", gates) - - # Can we already apply the gates? If so, there is no work to do. - # Accessing via private attributes to avoid overhead from __getitem__ - # and to optimize performance of the distance matrix access - dist = sum(coupling._dist_matrix[layout._v2p[g[0]], layout._v2p[g[1]]] for g in gates) - logger.debug("layer_permutation: distance = %s", dist) - if dist == len(gates): - logger.debug("layer_permutation: nothing to do") - circ = DAGCircuit() - circ.add_qreg(canonical_register) - return True, circ, 0, layout - - # Begin loop over trials of randomized algorithm - num_qubits = len(layout) - best_depth = inf # initialize best depth - best_edges = None # best edges found - best_circuit = None # initialize best swap circuit - best_layout = None # initialize best final layout - - cdist2 = coupling._dist_matrix**2 - int_qubit_subset = np.fromiter( - (dag.find_bit(bit).index for bit in qubit_subset), - dtype=np.uint32, - count=len(qubit_subset), - ) - - int_gates = np.fromiter( - (dag.find_bit(bit).index for gate in gates for bit in gate), - dtype=np.uint32, - count=2 * len(gates), - ) - - layout_mapping = {dag.find_bit(k).index: v for k, v in layout.get_virtual_bits().items()} - int_layout = nlayout.NLayout(layout_mapping, num_qubits, coupling.size()) - - trial_circuit = DAGCircuit() # SWAP circuit for slice of swaps in this trial - trial_circuit.add_qubits(list(layout.get_virtual_bits())) - - edges = np.asarray(coupling.get_edges(), dtype=np.uint32).ravel() - cdist = coupling._dist_matrix - best_edges, best_layout, best_depth = stochastic_swap_rs.swap_trials( - trials, - num_qubits, - int_layout, - int_qubit_subset, - int_gates, - cdist, - cdist2, - edges, - seed=self.seed, - ) - # If we have no best circuit for this layer, all of the trials have failed - if best_layout is None: - logger.debug("layer_permutation: failed!") - return False, None, None, None - - edges = best_edges.edges() - for idx in range(len(edges) // 2): - swap_src = self._int_to_qubit[edges[2 * idx]] - swap_tgt = self._int_to_qubit[edges[2 * idx + 1]] - trial_circuit.apply_operation_back(SwapGate(), (swap_src, swap_tgt), (), check=False) - best_circuit = trial_circuit - - # Otherwise, we return our result for this layer - logger.debug("layer_permutation: success!") - layout_mapping = best_layout.layout_mapping() - - best_lay = Layout({best_circuit.qubits[k]: v for (k, v) in layout_mapping}) - return True, best_circuit, best_depth, best_lay - - def _layer_update(self, dag, layer, best_layout, best_depth, best_circuit): - """Add swaps followed by the now mapped layer from the original circuit. - - Args: - dag (DAGCircuit): The DAGCircuit object that the _mapper method is building - layer (DAGCircuit): A DAGCircuit layer from the original circuit - best_layout (Layout): layout returned from _layer_permutation - best_depth (int): depth returned from _layer_permutation - best_circuit (DAGCircuit): swap circuit returned from _layer_permutation - """ - logger.debug("layer_update: layout = %s", best_layout) - logger.debug("layer_update: self.initial_layout = %s", self.initial_layout) - - # Output any swaps - if best_depth > 0: - logger.debug("layer_update: there are swaps in this layer, depth %d", best_depth) - dag.compose(best_circuit, qubits=list(best_circuit.qubits), inline_captures=True) - else: - logger.debug("layer_update: there are no swaps in this layer") - # Output this layer - dag.compose( - layer["graph"], qubits=best_layout.reorder_bits(dag.qubits), inline_captures=True - ) - - def _mapper(self, circuit_graph, coupling_graph, trials=20): - """Map a DAGCircuit onto a CouplingMap using swap gates. - - Args: - circuit_graph (DAGCircuit): input DAG circuit - coupling_graph (CouplingMap): coupling graph to map onto - trials (int): number of trials. - - Returns: - DAGCircuit: object containing a circuit equivalent to - circuit_graph that respects couplings in coupling_graph - - Raises: - TranspilerError: if there was any error during the mapping - or with the parameters. - """ - # Schedule the input circuit by calling layers() - layerlist = list(circuit_graph.layers()) - logger.debug("schedule:") - for i, v in enumerate(layerlist): - logger.debug(" %d: %s", i, v["partition"]) - - qubit_subset = self.initial_layout.get_virtual_bits().keys() - - # Find swap circuit to precede each layer of input circuit - layout = self.initial_layout.copy() - - # Construct an empty DAGCircuit with the same set of - # qregs and cregs as the input circuit - dagcircuit_output = None - if not self.fake_run: - dagcircuit_output = circuit_graph.copy_empty_like() - - logger.debug("layout = %s", layout) - - # Iterate over layers - for i, layer in enumerate(layerlist): - # First try and compute a route for the entire layer in one go. - if not layer["graph"].op_nodes(op=ControlFlowOp): - success_flag, best_circuit, best_depth, best_layout = self._layer_permutation( - circuit_graph, layer["partition"], layout, qubit_subset, coupling_graph, trials - ) - - logger.debug("mapper: layer %d", i) - logger.debug("mapper: success_flag=%s,best_depth=%s", success_flag, str(best_depth)) - if success_flag: - layout = best_layout - - # Update the DAG - if not self.fake_run: - self._layer_update( - dagcircuit_output, layer, best_layout, best_depth, best_circuit - ) - continue - - # If we're here, we need to go through every gate in the layer serially. - logger.debug("mapper: failed, layer %d, retrying sequentially", i) - # Go through each gate in the layer - for j, serial_layer in enumerate(layer["graph"].serial_layers()): - layer_dag = serial_layer["graph"] - # layer_dag has only one operation - op_node = layer_dag.op_nodes()[0] - if isinstance(op_node.op, ControlFlowOp): - layout = self._controlflow_layer_update( - dagcircuit_output, layer_dag, layout, circuit_graph - ) - else: - (success_flag, best_circuit, best_depth, best_layout) = self._layer_permutation( - circuit_graph, - serial_layer["partition"], - layout, - qubit_subset, - coupling_graph, - trials, - ) - logger.debug("mapper: layer %d, sublayer %d", i, j) - logger.debug( - "mapper: success_flag=%s,best_depth=%s,", success_flag, str(best_depth) - ) - - # Give up if we fail again - if not success_flag: - raise TranspilerError(f"swap mapper failed: layer {i}, sublayer {j}") - - # Update the record of qubit positions - # for each inner iteration - layout = best_layout - # Update the DAG - if not self.fake_run: - self._layer_update( - dagcircuit_output, - serial_layer, - best_layout, - best_depth, - best_circuit, - ) - - # This is the final edgemap. We might use it to correctly replace - # any measurements that needed to be removed earlier. - logger.debug("mapper: self.initial_layout = %s", self.initial_layout) - logger.debug("mapper: layout = %s", layout) - if self.property_set["final_layout"] is None: - self.property_set["final_layout"] = layout - else: - self.property_set["final_layout"] = layout.compose( - self.property_set["final_layout"], circuit_graph.qubits - ) - - if self.fake_run: - return circuit_graph - return dagcircuit_output - - def _controlflow_layer_update(self, dagcircuit_output, layer_dag, current_layout, root_dag): - """ - Updates the new dagcircuit with a routed control flow operation. - - Args: - dagcircuit_output (DAGCircuit): dagcircuit that is being built with routed operations. - layer_dag (DAGCircuit): layer to route containing a single controlflow operation. - current_layout (Layout): current layout coming into this layer. - root_dag (DAGCircuit): root dag of pass - - Returns: - Layout: updated layout after this layer has been routed. - - Raises: - TranspilerError: if layer_dag does not contain a recognized ControlFlowOp. - - """ - node = layer_dag.op_nodes()[0] - if not isinstance(node.op, (IfElseOp, ForLoopOp, WhileLoopOp, SwitchCaseOp)): - raise TranspilerError(f"unsupported control flow operation: {node}") - # For each block, expand it up be the full width of the containing DAG so we can be certain - # that it is routable, then route it within that. When we recombine later, we'll reduce all - # these blocks down to remove any qubits that are idle. - block_dags = [] - block_layouts = [] - for block in node.op.blocks: - inner_pass = self._recursive_pass(current_layout) - block_dags.append(inner_pass.run(_dag_from_block(block, node, root_dag))) - block_layouts.append(inner_pass.property_set["final_layout"].copy()) - - # Determine what layout we need to go towards. For some blocks (such as `for`), we must - # guarantee that the final layout is the same as the initial or the loop won't work. - if _controlflow_exhaustive_acyclic(node.op): - # We heuristically just choose to use the layout of whatever the deepest block is, to - # avoid extending the total depth by too much. - final_layout = max( - zip(block_layouts, block_dags), key=lambda x: x[1].depth(recurse=True) - )[0] - else: - final_layout = current_layout - if self.fake_run: - return final_layout - - # Add swaps to the end of each block to make sure they all have the same layout at the end. - # Adding these swaps can cause fewer wires to be idle than we expect (if we have to swap - # across unused qubits), so we track that at this point too. - idle_qubits = set(root_dag.qubits) - for layout, updated_dag_block in zip(block_layouts, block_dags): - swap_dag, swap_qubits = get_swap_map_dag( - root_dag, self.coupling_map, layout, final_layout, seed=self._new_seed() - ) - if swap_dag.size(recurse=False): - updated_dag_block.compose(swap_dag, qubits=swap_qubits, inline_captures=True) - idle_qubits &= set(updated_dag_block.idle_wires()) - - # Now for each block, expand it to be full width over all active wires (all blocks of a - # control-flow operation need to have equal input wires), and convert it to circuit form. - block_circuits = [] - for updated_dag_block in block_dags: - updated_dag_block.remove_qubits(*idle_qubits) - block_circuits.append(dag_to_circuit(updated_dag_block)) - - new_op = node.op.replace_blocks(block_circuits) - new_qargs = block_circuits[0].qubits - dagcircuit_output.apply_operation_back(new_op, new_qargs, node.cargs, check=False) - return final_layout - - def _new_seed(self): - """Get a seed for a new RNG instance.""" - return self.rng.integers(0x7FFF_FFFF_FFFF_FFFF) - - def _recursive_pass(self, initial_layout): - """Get a new instance of this class to handle a recursive call for a control-flow block. - - Each pass starts with its own new seed, determined deterministically from our own.""" - return self.__class__( - self.coupling_map, - # This doesn't cause an exponential explosion of the trials because we only generate a - # recursive pass instance for control-flow operations, while the trial multiplicity is - # only for non-control-flow layers. - trials=self.trials, - seed=self._new_seed(), - fake_run=self.fake_run, - initial_layout=initial_layout, - ) - - -def _controlflow_exhaustive_acyclic(operation: ControlFlowOp): - """Return True if the entire control-flow operation represents a block that is guaranteed to be - entered, and does not cycle back to the initial layout.""" - if isinstance(operation, IfElseOp): - return len(operation.blocks) == 2 - if isinstance(operation, SwitchCaseOp): - cases = operation.cases() - if isinstance(operation.target, expr.Expr): - type_ = operation.target.type - if type_.kind is types.Bool: - max_matches = 2 - elif type_.kind is types.Uint: - max_matches = 1 << type_.width - else: - raise RuntimeError(f"unhandled target type: '{type_}'") - else: - max_matches = 2 if isinstance(operation.target, Clbit) else 1 << len(operation.target) - return CASE_DEFAULT in cases or len(cases) == max_matches - return False - - -def _dag_from_block(block, node, root_dag): - """Get a :class:`DAGCircuit` that represents the :class:`.QuantumCircuit` ``block`` embedded - within the ``root_dag`` for full-width routing purposes. This means that all the qubits are in - the output DAG, but only the necessary clbits and classical registers are.""" - out = DAGCircuit() - # The pass already ensured that `root_dag` has only a single quantum register with everything. - for qreg in root_dag.qregs.values(): - out.add_qreg(qreg) - # For clbits, we need to take more care. Nested control-flow might need registers to exist for - # conditions on inner blocks. `DAGCircuit.substitute_node_with_dag` handles this register - # mapping when required, so we use that with a dummy block that pretends to act on all variables - # in the DAG. - out.add_clbits(node.cargs) - for var in block.iter_input_vars(): - out.add_input_var(var) - for var in block.iter_captured_vars(): - out.add_captured_var(var) - for var in block.iter_declared_vars(): - out.add_declared_var(var) - - dummy = out.apply_operation_back( - IfElseOp(expr.lift(True), block.copy_empty_like(vars_mode="captures")), - node.qargs, - node.cargs, - check=False, - ) - wire_map = dict(itertools.chain(zip(block.qubits, node.qargs), zip(block.clbits, node.cargs))) - out.substitute_node_with_dag(dummy, circuit_to_dag(block), wires=wire_map) - return out diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 1e5ca4519864..d921c3233abc 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -20,7 +20,6 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.passes import BasicSwap from qiskit.transpiler.passes import LookaheadSwap -from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap from qiskit.transpiler.passes import Error from qiskit.transpiler.passes import SetLayout @@ -296,62 +295,6 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana raise TranspilerError(f"Invalid optimization level specified: {optimization_level}") -class StochasticSwapPassManager(PassManagerStagePlugin): - """Plugin class for routing stage with :class:`~.StochasticSwap`""" - - def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager: - """Build routing stage PassManager.""" - seed_transpiler = pass_manager_config.seed_transpiler - target = pass_manager_config.target - coupling_map = pass_manager_config.coupling_map - coupling_map_routing = target - if coupling_map_routing is None: - coupling_map_routing = coupling_map - backend_properties = pass_manager_config.backend_properties - vf2_call_limit, vf2_max_trials = common.get_vf2_limits( - optimization_level, - pass_manager_config.layout_method, - pass_manager_config.initial_layout, - ) - if optimization_level == 3: - routing_pass = StochasticSwap(coupling_map_routing, trials=200, seed=seed_transpiler) - else: - routing_pass = StochasticSwap(coupling_map_routing, trials=20, seed=seed_transpiler) - - if optimization_level == 0: - return common.generate_routing_passmanager( - routing_pass, - target, - coupling_map=coupling_map, - seed_transpiler=-1, - use_barrier_before_measurement=True, - ) - if optimization_level == 1: - return common.generate_routing_passmanager( - routing_pass, - target, - coupling_map, - vf2_call_limit=vf2_call_limit, - vf2_max_trials=vf2_max_trials, - backend_properties=backend_properties, - seed_transpiler=-1, - check_trivial=True, - use_barrier_before_measurement=True, - ) - if optimization_level in {2, 3}: - return common.generate_routing_passmanager( - routing_pass, - target, - coupling_map=coupling_map, - vf2_call_limit=vf2_call_limit, - vf2_max_trials=vf2_max_trials, - backend_properties=backend_properties, - seed_transpiler=-1, - use_barrier_before_measurement=True, - ) - raise TranspilerError(f"Invalid optimization level specified: {optimization_level}") - - class LookaheadSwapPassManager(PassManagerStagePlugin): """Plugin class for routing stage with :class:`~.LookaheadSwap`""" diff --git a/releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml b/releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml new file mode 100644 index 000000000000..7b1b19cc27e6 --- /dev/null +++ b/releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml @@ -0,0 +1,42 @@ +--- +upgrade_transpiler: + - | + The deprecated ``StochasticSwap`` transpiler pass, and it's associated + built-in routing stage plugin `"stochastic"` have been removed. These + were marked as deprecated in the Qiskit 1.3.0 release. The pass has + been superseded by the :class:`.SabreSwap` which should be used instead + as it offers better performance and output quality. For example if the + pass was previously invoked via the transpile function such as with:: + + from qiskit import transpile + from qiskit.circuit import QuantumCircuit + from qiskit.transpiler import CouplingMap + from qiskit.providers.fake_provider import GenericBackendV2 + + + qc = QuantumCircuit(4) + qc.h(0) + qc.cx(0, range(1, 4)) + qc.measure_all() + + cmap = CouplingMap.from_heavy_hex(3) + backend = GenericBackendV2(num_qubits=cmap.size(), coupling_map=cmap) + + tqc = transpile( + qc, + routing_method="stochastic", + layout_method="dense", + seed_transpiler=12342, + target=backend.target + ) + + this should be replaced with:: + + + tqc = transpile( + qc, + routing_method="sabre", + layout_method="dense", + seed_transpiler=12342, + target=backend.target + ) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 851c6817d82f..b88afdddd3bd 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -956,10 +956,7 @@ def test_move_measurements(self): circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) lay = [0, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6] - with self.assertWarns(DeprecationWarning): - out = transpile( - circ, initial_layout=lay, coupling_map=cmap, routing_method="stochastic" - ) + out = transpile(circ, initial_layout=lay, coupling_map=cmap, routing_method="sabre") out_dag = circuit_to_dag(out) meas_nodes = out_dag.named_nodes("measure") for meas_node in meas_nodes: @@ -3637,32 +3634,6 @@ def test_basic_connected_circuit_dense_layout(self, routing_method): continue self.assertIn(qubits, self.backend.target[op_name]) - @data("stochastic") - def test_basic_connected_circuit_dense_layout_stochastic(self, routing_method): - """Test basic connected circuit on disjoint backend for deprecated stochastic swap""" - # TODO: Remove when StochasticSwap is removed - qc = QuantumCircuit(5) - qc.h(0) - qc.cx(0, 1) - qc.cx(0, 2) - qc.cx(0, 3) - qc.cx(0, 4) - qc.measure_all() - with self.assertWarns(DeprecationWarning): - tqc = transpile( - qc, - self.backend, - layout_method="dense", - routing_method=routing_method, - seed_transpiler=42, - ) - for inst in tqc.data: - qubits = tuple(tqc.find_bit(x).index for x in inst.qubits) - op_name = inst.operation.name - if op_name == "barrier": - continue - self.assertIn(qubits, self.backend.target[op_name]) - # Lookahead swap skipped for performance @data("sabre", "basic") def test_triple_circuit_dense_layout(self, routing_method): @@ -3713,57 +3684,6 @@ def test_triple_circuit_dense_layout(self, routing_method): continue self.assertIn(qubits, self.backend.target[op_name]) - @data("stochastic") - def test_triple_circuit_dense_layout_stochastic(self, routing_method): - """Test a split circuit with one circuit component per chip for deprecated StochasticSwap.""" - # TODO: Remove when StochasticSwap is removed - qc = QuantumCircuit(30) - qc.h(0) - qc.h(10) - qc.h(20) - qc.cx(0, 1) - qc.cx(0, 2) - qc.cx(0, 3) - qc.cx(0, 4) - qc.cx(0, 5) - qc.cx(0, 6) - qc.cx(0, 7) - qc.cx(0, 8) - qc.cx(0, 9) - qc.ecr(10, 11) - qc.ecr(10, 12) - qc.ecr(10, 13) - qc.ecr(10, 14) - qc.ecr(10, 15) - qc.ecr(10, 16) - qc.ecr(10, 17) - qc.ecr(10, 18) - qc.ecr(10, 19) - qc.cy(20, 21) - qc.cy(20, 22) - qc.cy(20, 23) - qc.cy(20, 24) - qc.cy(20, 25) - qc.cy(20, 26) - qc.cy(20, 27) - qc.cy(20, 28) - qc.cy(20, 29) - qc.measure_all() - with self.assertWarns(DeprecationWarning): - tqc = transpile( - qc, - self.backend, - layout_method="dense", - routing_method=routing_method, - seed_transpiler=42, - ) - for inst in tqc.data: - qubits = tuple(tqc.find_bit(x).index for x in inst.qubits) - op_name = inst.operation.name - if op_name == "barrier": - continue - self.assertIn(qubits, self.backend.target[op_name]) - @data("sabre", "basic", "lookahead") def test_triple_circuit_invalid_layout(self, routing_method): """Test a split circuit with one circuit component per chip.""" @@ -3826,64 +3746,6 @@ def test_triple_circuit_invalid_layout(self, routing_method): seed_transpiler=42, ) - @data("stochastic") - def test_triple_circuit_invalid_layout_stochastic(self, routing_method): - """Test a split circuit with one circuit component per chip for deprecated ``StochasticSwap``""" - # TODO: Remove when StochasticSwap is removed - qc = QuantumCircuit(30) - qc.h(0) - qc.h(10) - qc.h(20) - qc.cx(0, 1) - qc.cx(0, 2) - qc.cx(0, 3) - qc.cx(0, 4) - qc.cx(0, 5) - qc.cx(0, 6) - qc.cx(0, 7) - qc.cx(0, 8) - qc.cx(0, 9) - qc.ecr(10, 11) - qc.ecr(10, 12) - qc.ecr(10, 13) - qc.ecr(10, 14) - qc.ecr(10, 15) - qc.ecr(10, 16) - qc.ecr(10, 17) - qc.ecr(10, 18) - qc.ecr(10, 19) - qc.cy(20, 21) - qc.cy(20, 22) - qc.cy(20, 23) - qc.cy(20, 24) - qc.cy(20, 25) - qc.cy(20, 26) - qc.cy(20, 27) - qc.cy(20, 28) - qc.cy(20, 29) - qc.measure_all() - with self.assertRaises(TranspilerError): - if routing_method == "stochastic": - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The StochasticSwap transpilation pass is a suboptimal", - ): - transpile( - qc, - self.backend, - layout_method="trivial", - routing_method=routing_method, - seed_transpiler=42, - ) - else: - transpile( - qc, - self.backend, - layout_method="trivial", - routing_method=routing_method, - seed_transpiler=42, - ) - # Lookahead swap skipped for performance reasons, stochastic moved to new test due to deprecation @data("sabre", "basic") def test_six_component_circuit_dense_layout(self, routing_method): @@ -3946,71 +3808,6 @@ def test_six_component_circuit_dense_layout(self, routing_method): continue self.assertIn(qubits, self.backend.target[op_name]) - # Lookahead swap skipped for performance reasons - @data("stochastic") - def test_six_component_circuit_dense_layout_stochastic(self, routing_method): - """Test input circuit with more than 1 component per backend component - for deprecated ``StochasticSwap``.""" - # TODO: Remove when StochasticSwap is removed - qc = QuantumCircuit(42) - qc.h(0) - qc.h(10) - qc.h(20) - qc.cx(0, 1) - qc.cx(0, 2) - qc.cx(0, 3) - qc.cx(0, 4) - qc.cx(0, 5) - qc.cx(0, 6) - qc.cx(0, 7) - qc.cx(0, 8) - qc.cx(0, 9) - qc.ecr(10, 11) - qc.ecr(10, 12) - qc.ecr(10, 13) - qc.ecr(10, 14) - qc.ecr(10, 15) - qc.ecr(10, 16) - qc.ecr(10, 17) - qc.ecr(10, 18) - qc.ecr(10, 19) - qc.cy(20, 21) - qc.cy(20, 22) - qc.cy(20, 23) - qc.cy(20, 24) - qc.cy(20, 25) - qc.cy(20, 26) - qc.cy(20, 27) - qc.cy(20, 28) - qc.cy(20, 29) - qc.h(30) - qc.cx(30, 31) - qc.cx(30, 32) - qc.cx(30, 33) - qc.h(34) - qc.cx(34, 35) - qc.cx(34, 36) - qc.cx(34, 37) - qc.h(38) - qc.cx(38, 39) - qc.cx(39, 40) - qc.cx(39, 41) - qc.measure_all() - with self.assertWarns(DeprecationWarning): - tqc = transpile( - qc, - self.backend, - layout_method="dense", - routing_method=routing_method, - seed_transpiler=42, - ) - for inst in tqc.data: - qubits = tuple(tqc.find_bit(x).index for x in inst.qubits) - op_name = inst.operation.name - if op_name == "barrier": - continue - self.assertIn(qubits, self.backend.target[op_name]) - @data(0, 1, 2, 3) def test_transpile_target_with_qubits_without_ops(self, opt_level): """Test qubits without operations aren't ever used.""" diff --git a/test/python/transpiler/test_mappers.py b/test/python/transpiler/test_mappers.py index c3b0644c2473..748d589c97ab 100644 --- a/test/python/transpiler/test_mappers.py +++ b/test/python/transpiler/test_mappers.py @@ -77,7 +77,7 @@ def test_a_common_test(self): from qiskit.providers.basic_provider import BasicSimulator from qiskit.qasm2 import dump from qiskit.transpiler import PassManager -from qiskit.transpiler.passes import BasicSwap, LookaheadSwap, SabreSwap, StochasticSwap +from qiskit.transpiler.passes import BasicSwap, LookaheadSwap, SabreSwap from qiskit.transpiler.passes import SetLayout from qiskit.transpiler import CouplingMap, Layout from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -106,12 +106,6 @@ def create_passmanager(self, coupling_map, initial_layout=None): passmanager.append(SetLayout(Layout(initial_layout))) with warnings.catch_warnings(): - # TODO: remove this filter when StochasticSwap is removed - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=r".*StochasticSwap.*", - ) # pylint: disable=not-callable passmanager.append(self.pass_class(CouplingMap(coupling_map), **self.additional_args)) return passmanager @@ -289,13 +283,6 @@ class TestsLookaheadSwap(SwapperCommonTestCases, QiskitTestCase): pass_class = LookaheadSwap -class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase): - """Test SwapperCommonTestCases using StochasticSwap.""" - - pass_class = StochasticSwap - additional_args = {"seed": 0} - - class TestsSabreSwap(SwapperCommonTestCases, QiskitTestCase): """Test SwapperCommonTestCases using SabreSwap.""" diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index f8da34636cc7..bd149d2e13e0 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -20,7 +20,7 @@ from qiskit.circuit.classical import expr, types from qiskit.circuit.library import efficient_su2, quantum_volume from qiskit.transpiler import CouplingMap, AnalysisPass, PassManager -from qiskit.transpiler.passes import SabreLayout, DenseLayout, StochasticSwap, Unroll3qOrMore +from qiskit.transpiler.passes import SabreLayout, DenseLayout, Unroll3qOrMore, BasicSwap from qiskit.transpiler.exceptions import TranspilerError from qiskit.converters import circuit_to_dag from qiskit.compiler.transpiler import transpile @@ -259,15 +259,14 @@ def test_layout_many_search_trials(self): coupling_map=MUMBAI_CMAP, seed=42, ) - with self.assertWarns(DeprecationWarning): - res = transpile( - qc, - backend, - layout_method="sabre", - routing_method="stochastic", - seed_transpiler=12345, - optimization_level=1, - ) + res = transpile( + qc, + backend, + layout_method="sabre", + routing_method="basic", + seed_transpiler=12345, + optimization_level=1, + ) self.assertIsInstance(res, QuantumCircuit) layout = res._layout.initial_layout self.assertEqual( @@ -308,12 +307,10 @@ def test_support_var_with_explicit_routing_pass(self): cm = CouplingMap.from_line(8) with self.assertWarns(DeprecationWarning): - pass_ = SabreLayout( - cm, seed=0, routing_pass=StochasticSwap(cm, trials=1, seed=0, fake_run=True) - ) + pass_ = SabreLayout(cm, seed=0, routing_pass=BasicSwap(cm, fake_run=True)) _ = pass_(qc) layout = pass_.property_set["layout"] - self.assertEqual([layout[q] for q in qc.qubits], [2, 3, 4, 1, 5]) + self.assertEqual([layout[q] for q in qc.qubits], [3, 4, 2, 5, 1]) @slow_test def test_release_valve_routes_multiple(self): diff --git a/test/python/transpiler/test_stage_plugin.py b/test/python/transpiler/test_stage_plugin.py index f278096b8419..3caf95d82939 100644 --- a/test/python/transpiler/test_stage_plugin.py +++ b/test/python/transpiler/test_stage_plugin.py @@ -41,7 +41,6 @@ def test_list_stage_plugins(self): self.assertIn("basic", routing_passes) self.assertIn("sabre", routing_passes) self.assertIn("lookahead", routing_passes) - self.assertIn("stochastic", routing_passes) self.assertIsInstance(list_stage_plugins("init"), list) self.assertIsInstance(list_stage_plugins("layout"), list) self.assertIsInstance(list_stage_plugins("translation"), list) @@ -123,31 +122,6 @@ def test_routing_plugins(self, optimization_level, routing_method): counts = backend.run(tqc, shots=1000).result().get_counts() self.assertDictAlmostEqual(counts, {"0000": 500, "1111": 500}, delta=100) - @combine( - optimization_level=list(range(4)), - routing_method=["stochastic"], - ) - def test_routing_plugin_stochastic(self, optimization_level, routing_method): - """Test stoc routing plugins (excluding error).""" - # Note remove once StochasticSwap gets removed - qc = QuantumCircuit(4) - qc.h(0) - qc.cx(0, 1) - qc.cx(0, 2) - qc.cx(0, 3) - qc.measure_all() - with self.assertWarns(DeprecationWarning): - tqc = transpile( - qc, - basis_gates=["cx", "sx", "x", "rz"], - coupling_map=CouplingMap.from_line(4), - optimization_level=optimization_level, - routing_method=routing_method, - ) - backend = BasicSimulator() - counts = backend.run(tqc, shots=1000).result().get_counts() - self.assertDictAlmostEqual(counts, {"0000": 500, "1111": 500}, delta=100) - @combine( optimization_level=list(range(4)), ) diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py deleted file mode 100644 index 7e4dfe63de3c..000000000000 --- a/test/python/transpiler/test_stochastic_swap.py +++ /dev/null @@ -1,1661 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 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. - -"""Test the Stochastic Swap pass""" - -import unittest -import warnings - -import numpy.random - -from ddt import ddt, data -from qiskit.transpiler.passes import StochasticSwap -from qiskit.transpiler import CouplingMap, PassManager, Layout -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit -from qiskit.transpiler.passes.utils import CheckMap -from qiskit.circuit.random import random_circuit -from qiskit.providers.fake_provider import GenericBackendV2 -from qiskit.compiler.transpiler import transpile -from qiskit.circuit import ControlFlowOp, Clbit, CASE_DEFAULT -from qiskit.circuit.classical import expr, types -from test import QiskitTestCase # pylint: disable=wrong-import-order -from test.utils._canonical import canonicalize_control_flow # pylint: disable=wrong-import-order - -from ..legacy_cmaps import MUMBAI_CMAP, RUESCHLIKON_CMAP - - -@ddt -class TestStochasticSwap(QiskitTestCase): - """ - Tests the StochasticSwap pass. - All of the tests use a fixed seed since the results - may depend on it. - """ - - def test_trivial_case(self): - """ - q0:--(+)-[H]-(+)- - | | - q1:---.-------|-- - | - q2:-----------.-- - Coupling map: [1]--[0]--[2] - """ - coupling = CouplingMap([[0, 1], [0, 2]]) - - qr = QuantumRegister(3, "q") - circuit = QuantumCircuit(qr) - circuit.cx(qr[0], qr[1]) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[2]) - - dag = circuit_to_dag(circuit) - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 13) - after = pass_.run(dag) - - self.assertEqual(dag, after) - - def test_trivial_in_same_layer(self): - """ - q0:--(+)-- - | - q1:---.--- - q2:--(+)-- - | - q3:---.--- - Coupling map: [0]--[1]--[2]--[3] - """ - coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) - - qr = QuantumRegister(4, "q") - circuit = QuantumCircuit(qr) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[0], qr[1]) - - dag = circuit_to_dag(circuit) - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 13) - after = pass_.run(dag) - - self.assertEqual(dag, after) - - def test_permute_wires_1(self): - """ - q0:-------- - - q1:---.---- - | - q2:--(+)--- - Coupling map: [1]--[0]--[2] - q0:--x-(+)- - | | - q1:--|--.-- - | - q2:--x----- - """ - coupling = CouplingMap([[0, 1], [0, 2]]) - - qr = QuantumRegister(3, "q") - circuit = QuantumCircuit(qr) - circuit.cx(qr[1], qr[2]) - dag = circuit_to_dag(circuit) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 11) - after = pass_.run(dag) - - expected = QuantumCircuit(qr) - expected.swap(qr[0], qr[2]) - expected.cx(qr[1], qr[0]) - - self.assertEqual(circuit_to_dag(expected), after) - - def test_permute_wires_2(self): - """ - qr0:---.---[H]-- - | - qr1:---|-------- - | - qr2:--(+)------- - Coupling map: [0]--[1]--[2] - qr0:----.---[H]- - | - qr1:-x-(+)------ - | - qr2:-x---------- - """ - coupling = CouplingMap([[1, 0], [1, 2]]) - - qr = QuantumRegister(3, "q") - circuit = QuantumCircuit(qr) - circuit.cx(qr[0], qr[2]) - circuit.h(qr[0]) - dag = circuit_to_dag(circuit) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 11) - after = pass_.run(dag) - - expected = QuantumCircuit(qr) - expected.swap(qr[1], qr[2]) - expected.cx(qr[0], qr[1]) - expected.h(qr[0]) - - self.assertEqual(expected, dag_to_circuit(after)) - - def test_permute_wires_3(self): - """ - qr0:--(+)---.-- - | | - qr1:---|----|-- - | | - qr2:---|----|-- - | | - qr3:---.---(+)- - Coupling map: [0]--[1]--[2]--[3] - qr0:-x------------ - | - qr1:-x--(+)---.--- - | | - qr2:-x---.---(+)-- - | - qr3:-x------------ - """ - coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) - - qr = QuantumRegister(4, "q") - circuit = QuantumCircuit(qr) - circuit.cx(qr[0], qr[3]) - circuit.cx(qr[3], qr[0]) - dag = circuit_to_dag(circuit) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 13) - after = pass_.run(dag) - - expected = QuantumCircuit(qr) - expected.swap(qr[0], qr[1]) - expected.swap(qr[2], qr[3]) - expected.cx(qr[1], qr[2]) - expected.cx(qr[2], qr[1]) - - self.assertEqual(circuit_to_dag(expected), after) - - def test_permute_wires_4(self): - """No qubit label permutation occurs if the first - layer has only single-qubit gates. This is suboptimal - but seems to be the current behavior. - qr0:------(+)-- - | - qr1:-------|--- - | - qr2:-------|--- - | - qr3:--[H]--.--- - Coupling map: [0]--[1]--[2]--[3] - qr0:------X--------- - | - qr1:------X-(+)----- - | - qr2:------X--.------ - | - qr3:-[H]--X--------- - """ - coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) - - qr = QuantumRegister(4, "q") - circuit = QuantumCircuit(qr) - circuit.h(qr[3]) - circuit.cx(qr[3], qr[0]) - dag = circuit_to_dag(circuit) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 13) - after = pass_.run(dag) - - expected = QuantumCircuit(qr) - expected.h(qr[3]) - expected.swap(qr[2], qr[3]) - expected.swap(qr[0], qr[1]) - expected.cx(qr[2], qr[1]) - - self.assertEqual(circuit_to_dag(expected), after) - - def test_permute_wires_5(self): - """This is the same case as permute_wires_4 - except the single qubit gate is after the two-qubit - gate, so the layout is adjusted. - qr0:--(+)------ - | - qr1:---|------- - | - qr2:---|------- - | - qr3:---.--[H]-- - Coupling map: [0]--[1]--[2]--[3] - qr0:-x----------- - | - qr1:-x--(+)------ - | - qr2:-x---.--[H]-- - | - qr3:-x----------- - """ - coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) - - qr = QuantumRegister(4, "q") - circuit = QuantumCircuit(qr) - circuit.cx(qr[3], qr[0]) - circuit.h(qr[3]) - dag = circuit_to_dag(circuit) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 13) - after = pass_.run(dag) - - expected = QuantumCircuit(qr) - expected.swap(qr[0], qr[1]) - expected.swap(qr[2], qr[3]) - expected.cx(qr[2], qr[1]) - expected.h(qr[2]) - - self.assertEqual(circuit_to_dag(expected), after) - - def test_all_single_qubit(self): - """Test all trivial layers.""" - coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) - qr = QuantumRegister(4, "q") - cr = ClassicalRegister(4, "c") - circ = QuantumCircuit(qr, cr) - circ.h(qr) - circ.z(qr) - circ.s(qr) - circ.t(qr) - circ.tdg(qr) - circ.measure(qr[0], cr[0]) # intentional duplicate - circ.measure(qr[0], cr[0]) - circ.measure(qr[1], cr[1]) - circ.measure(qr[2], cr[2]) - circ.measure(qr[3], cr[3]) - dag = circuit_to_dag(circ) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 13) - after = pass_.run(dag) - self.assertEqual(dag, after) - - def test_overoptimization_case(self): - """Check mapper overoptimization. - The mapper should not change the semantics of the input. - An overoptimization introduced issue #81: - https://github.com/Qiskit/qiskit-terra/issues/81 - """ - coupling = CouplingMap([[0, 2], [1, 2], [2, 3]]) - qr = QuantumRegister(4, "q") - cr = ClassicalRegister(4, "c") - circuit = QuantumCircuit(qr, cr) - circuit.x(qr[0]) - circuit.y(qr[1]) - circuit.z(qr[2]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.s(qr[1]) - circuit.t(qr[2]) - circuit.h(qr[3]) - circuit.cx(qr[1], qr[2]) - circuit.measure(qr[0], cr[0]) - circuit.measure(qr[1], cr[1]) - circuit.measure(qr[2], cr[2]) - circuit.measure(qr[3], cr[3]) - dag = circuit_to_dag(circuit) - # ┌───┐ ┌─┐ - # q_0: | 0 >┤ X ├────────────■───────────────────────────┤M├───────── - # └───┘┌───┐ ┌─┴─┐ ┌───┐ └╥┘┌─┐ - # q_1: | 0 >─────┤ Y ├─────┤ X ├─────┤ S ├────────────■───╫─┤M├────── - # └───┘┌───┐└───┘ └───┘┌───┐ ┌─┴─┐ ║ └╥┘┌─┐ - # q_2: | 0 >──────────┤ Z ├───────■───────┤ T ├─────┤ X ├─╫──╫─┤M├─── - # └───┘ ┌─┴─┐ └───┘┌───┐└───┘ ║ ║ └╥┘┌─┐ - # q_3: | 0 >────────────────────┤ X ├──────────┤ H ├──────╫──╫──╫─┤M├ - # └───┘ └───┘ ║ ║ ║ └╥┘ - # c_0: 0 ══════════════════════════════════════════════╩══╬══╬══╬═ - # ║ ║ ║ - # c_1: 0 ═════════════════════════════════════════════════╩══╬══╬═ - # ║ ║ - # c_2: 0 ════════════════════════════════════════════════════╩══╬═ - # ║ - # c_3: 0 ═══════════════════════════════════════════════════════╩═ - # - expected = QuantumCircuit(qr, cr) - expected.z(qr[2]) - expected.y(qr[1]) - expected.x(qr[0]) - expected.swap(qr[0], qr[2]) - expected.cx(qr[2], qr[1]) - expected.swap(qr[0], qr[2]) - expected.cx(qr[2], qr[3]) - expected.s(qr[1]) - expected.t(qr[2]) - expected.h(qr[3]) - expected.measure(qr[0], cr[0]) - expected.cx(qr[1], qr[2]) - expected.measure(qr[3], cr[3]) - expected.measure(qr[1], cr[1]) - expected.measure(qr[2], cr[2]) - expected_dag = circuit_to_dag(expected) - # ┌───┐ ┌─┐ - # q_0: ┤ X ├─X───────X──────┤M├──────────────── - # ├───┤ │ ┌───┐ │ ┌───┐└╥┘ ┌─┐ - # q_1: ┤ Y ├─┼─┤ X ├─┼─┤ S ├─╫────────■──┤M├─── - # ├───┤ │ └─┬─┘ │ └───┘ ║ ┌───┐┌─┴─┐└╥┘┌─┐ - # q_2: ┤ Z ├─X───■───X───■───╫─┤ T ├┤ X ├─╫─┤M├ - # └───┘ ┌─┴─┐ ║ ├───┤└┬─┬┘ ║ └╥┘ - # q_3: ────────────────┤ X ├─╫─┤ H ├─┤M├──╫──╫─ - # └───┘ ║ └───┘ └╥┘ ║ ║ - # c: 4/══════════════════════╩════════╩═══╩══╩═ - # 0 3 1 2 - - # - # Layout -- - # {qr[0]: 0, - # qr[1]: 1, - # qr[2]: 2, - # qr[3]: 3} - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 19) - after = pass_.run(dag) - - self.assertEqual(expected_dag, after) - - def test_already_mapped(self): - """Circuit not remapped if matches topology. - See: https://github.com/Qiskit/qiskit-terra/issues/342 - """ - coupling = CouplingMap(RUESCHLIKON_CMAP) - qr = QuantumRegister(16, "q") - cr = ClassicalRegister(16, "c") - circ = QuantumCircuit(qr, cr) - circ.cx(qr[3], qr[14]) - circ.cx(qr[5], qr[4]) - circ.h(qr[9]) - circ.cx(qr[9], qr[8]) - circ.x(qr[11]) - circ.cx(qr[3], qr[4]) - circ.cx(qr[12], qr[11]) - circ.cx(qr[13], qr[4]) - for j in range(16): - circ.measure(qr[j], cr[j]) - - dag = circuit_to_dag(circ) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 13) - after = pass_.run(dag) - self.assertEqual(circuit_to_dag(circ), after) - - def test_congestion(self): - """Test code path that falls back to serial layers.""" - coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) - qr = QuantumRegister(4, "q") - cr = ClassicalRegister(4, "c") - circ = QuantumCircuit(qr, cr) - circ.cx(qr[1], qr[2]) - circ.cx(qr[0], qr[3]) - circ.measure(qr[0], cr[0]) - circ.h(qr) - circ.cx(qr[0], qr[1]) - circ.cx(qr[2], qr[3]) - circ.measure(qr[0], cr[0]) - circ.measure(qr[1], cr[1]) - circ.measure(qr[2], cr[2]) - circ.measure(qr[3], cr[3]) - dag = circuit_to_dag(circ) - # Input: - # ┌─┐┌───┐ ┌─┐ - # q_0: |0>─────────────────■──────────────────┤M├┤ H ├──■─────┤M├ - # ┌───┐ │ └╥┘└───┘┌─┴─┐┌─┐└╥┘ - # q_1: |0>──■───────┤ H ├──┼───────────────────╫──────┤ X ├┤M├─╫─ - # ┌─┴─┐┌───┐└───┘ │ ┌─┐ ║ └───┘└╥┘ ║ - # q_2: |0>┤ X ├┤ H ├───────┼─────────■─────┤M├─╫────────────╫──╫─ - # └───┘└───┘ ┌─┴─┐┌───┐┌─┴─┐┌─┐└╥┘ ║ ║ ║ - # q_3: |0>───────────────┤ X ├┤ H ├┤ X ├┤M├─╫──╫────────────╫──╫─ - # └───┘└───┘└───┘└╥┘ ║ ║ ║ ║ - # c_0: 0 ═══════════════════════════════╬══╬══╩════════════╬══╩═ - # ║ ║ ║ - # c_1: 0 ═══════════════════════════════╬══╬═══════════════╩════ - # ║ ║ - # c_2: 0 ═══════════════════════════════╬══╩════════════════════ - # ║ - # c_3: 0 ═══════════════════════════════╩═══════════════════════ - # - # Expected output (with seed 999): - # ┌───┐ ┌─┐ - # q_0: ───────X──┤ H ├─────────────────X──────┤M├────── - # │ └───┘ ┌─┐ ┌───┐ │ ┌───┐└╥┘ ┌─┐ - # q_1: ──■────X────■───────┤M├─X─┤ X ├─X─┤ X ├─╫────┤M├ - # ┌─┴─┐┌───┐ │ └╥┘ │ └─┬─┘┌─┐└─┬─┘ ║ └╥┘ - # q_2: ┤ X ├┤ H ├──┼────────╫──┼───■──┤M├──┼───╫─────╫─ - # └───┘└───┘┌─┴─┐┌───┐ ║ │ ┌───┐└╥┘ │ ║ ┌─┐ ║ - # q_3: ──────────┤ X ├┤ H ├─╫──X─┤ H ├─╫───■───╫─┤M├─╫─ - # └───┘└───┘ ║ └───┘ ║ ║ └╥┘ ║ - # c: 4/═════════════════════╩══════════╩═══════╩══╩══╩═ - # 0 2 3 0 1 - # - # Target coupling graph: - # 2 - # | - # 0 - 1 - 3 - - expected = QuantumCircuit(qr, cr) - expected.cx(qr[1], qr[2]) - expected.h(qr[2]) - expected.swap(qr[0], qr[1]) - expected.h(qr[0]) - expected.cx(qr[1], qr[3]) - expected.h(qr[3]) - expected.measure(qr[1], cr[0]) - expected.swap(qr[1], qr[3]) - expected.cx(qr[2], qr[1]) - expected.h(qr[3]) - expected.swap(qr[0], qr[1]) - expected.measure(qr[2], cr[2]) - expected.cx(qr[3], qr[1]) - expected.measure(qr[0], cr[3]) - expected.measure(qr[3], cr[0]) - expected.measure(qr[1], cr[1]) - expected_dag = circuit_to_dag(expected) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 999) - after = pass_.run(dag) - self.assertEqual(expected_dag, after) - - def test_only_output_cx_and_swaps_in_coupling_map(self): - """Test that output DAG contains only 2q gates from the the coupling map.""" - - coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) - qr = QuantumRegister(4, "q") - cr = ClassicalRegister(4, "c") - circuit = QuantumCircuit(qr, cr) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[0], qr[2]) - circuit.cx(qr[0], qr[3]) - circuit.measure(qr, cr) - dag = circuit_to_dag(circuit) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, 20, 5) - after = pass_.run(dag) - - valid_couplings = [{qr[a], qr[b]} for (a, b) in coupling.get_edges()] - - for _2q_gate in after.two_qubit_ops(): - self.assertIn(set(_2q_gate.qargs), valid_couplings) - - def test_len_cm_vs_dag(self): - """Test error if the coupling map is smaller than the dag.""" - - coupling = CouplingMap([[0, 1], [1, 2]]) - qr = QuantumRegister(4, "q") - cr = ClassicalRegister(4, "c") - circuit = QuantumCircuit(qr, cr) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[0], qr[2]) - circuit.cx(qr[0], qr[3]) - circuit.measure(qr, cr) - dag = circuit_to_dag(circuit) - - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling) - with self.assertRaises(TranspilerError): - _ = pass_.run(dag) - - def test_single_gates_omitted(self): - """Test if single qubit gates are omitted.""" - - coupling_map = [[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]] - - # q_0: ──■────────────────── - # │ - # q_1: ──┼─────────■──────── - # │ ┌─┴─┐ - # q_2: ──┼───────┤ X ├────── - # │ ┌────┴───┴─────┐ - # q_3: ──┼──┤ U(1,1.5,0.7) ├ - # ┌─┴─┐└──────────────┘ - # q_4: ┤ X ├──────────────── - # └───┘ - qr = QuantumRegister(5, "q") - cr = ClassicalRegister(5, "c") - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[4]) - circuit.cx(qr[1], qr[2]) - circuit.u(1, 1.5, 0.7, qr[3]) - - # q_0: ─────────────────X────── - # │ - # q_1: ───────■─────────X───■── - # ┌─┴─┐ │ - # q_2: ─────┤ X ├───────────┼── - # ┌────┴───┴─────┐ ┌─┴─┐ - # q_3: ┤ U(1,1.5,0.7) ├─X─┤ X ├ - # └──────────────┘ │ └───┘ - # q_4: ─────────────────X────── - expected = QuantumCircuit(qr, cr) - expected.cx(qr[1], qr[2]) - expected.u(1, 1.5, 0.7, qr[3]) - expected.swap(qr[0], qr[1]) - expected.swap(qr[3], qr[4]) - expected.cx(qr[1], qr[3]) - - expected_dag = circuit_to_dag(expected) - - with self.assertWarns(DeprecationWarning): - stochastic = StochasticSwap(CouplingMap(coupling_map), seed=0) - after = PassManager(stochastic).run(circuit) - after = circuit_to_dag(after) - self.assertEqual(expected_dag, after) - - -@ddt -class TestStochasticSwapControlFlow(QiskitTestCase): - """Tests for control flow in stochastic swap.""" - - def test_pre_if_else_route(self): - """test swap with if else controlflow construct""" - num_qubits = 5 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap.from_line(num_qubits) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.cx(0, 2) - qc.measure(2, 2) - true_body = QuantumCircuit(qreg, creg[[2]]) - true_body.x(3) - false_body = QuantumCircuit(qreg, creg[[2]]) - false_body.x(4) - qc.if_else((creg[2], 0), true_body, false_body, qreg, creg[[2]]) - qc.barrier(qreg) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=82).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.swap(0, 1) - expected.cx(1, 2) - expected.measure(2, 2) - etrue_body = QuantumCircuit(qreg[[3, 4]], creg[[2]]) - etrue_body.x(0) - efalse_body = QuantumCircuit(qreg[[3, 4]], creg[[2]]) - efalse_body.x(1) - new_order = [1, 0, 2, 3, 4] - expected.if_else((creg[2], 0), etrue_body, efalse_body, qreg[[3, 4]], creg[[2]]) - expected.barrier(qreg) - expected.measure(qreg, creg[new_order]) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_pre_if_else_route_post_x(self): - """test swap with if else controlflow construct; pre-cx and post x""" - num_qubits = 5 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.cx(0, 2) - qc.measure(2, 2) - true_body = QuantumCircuit(qreg, creg[[0]]) - true_body.x(3) - false_body = QuantumCircuit(qreg, creg[[0]]) - false_body.x(4) - qc.if_else((creg[2], 0), true_body, false_body, qreg, creg[[0]]) - qc.x(1) - qc.barrier(qreg) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=431).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.swap(1, 2) - expected.cx(0, 1) - expected.measure(1, 2) - new_order = [0, 2, 1, 3, 4] - etrue_body = QuantumCircuit(qreg[[3, 4]], creg[[0]]) - etrue_body.x(0) - efalse_body = QuantumCircuit(qreg[[3, 4]], creg[[0]]) - efalse_body.x(1) - expected.if_else((creg[2], 0), etrue_body, efalse_body, qreg[[3, 4]], creg[[0]]) - expected.x(2) - expected.barrier(qreg) - expected.measure(qreg, creg[new_order]) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_post_if_else_route(self): - """test swap with if else controlflow construct; post cx""" - num_qubits = 5 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.measure(0, 0) - true_body = QuantumCircuit(qreg, creg[[0]]) - true_body.x(3) - false_body = QuantumCircuit(qreg, creg[[0]]) - false_body.x(4) - qc.barrier(qreg) - qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]]) - qc.barrier(qreg) - qc.cx(0, 2) - qc.barrier(qreg) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=6508).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.measure(0, 0) - etrue_body = QuantumCircuit(qreg[[3, 4]], creg[[0]]) - etrue_body.x(0) - efalse_body = QuantumCircuit(qreg[[3, 4]], creg[[0]]) - efalse_body.x(1) - expected.barrier(qreg) - expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[3, 4]], creg[[0]]) - expected.barrier(qreg) - expected.swap(0, 1) - expected.cx(1, 2) - expected.barrier(qreg) - expected.measure(qreg, creg[[1, 0, 2, 3, 4]]) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_pre_if_else2(self): - """test swap with if else controlflow construct; cx in if statement""" - num_qubits = 5 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.cx(0, 2) - qc.x(1) - qc.measure(0, 0) - true_body = QuantumCircuit(qreg, creg[[0]]) - true_body.x(0) - false_body = QuantumCircuit(qreg, creg[[0]]) - qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]]) - qc.barrier(qreg) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=38).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.x(1) - expected.swap(0, 1) - expected.cx(1, 2) - expected.measure(1, 0) - etrue_body = QuantumCircuit(qreg[[1]], creg[[0]]) - etrue_body.x(0) - efalse_body = QuantumCircuit(qreg[[1]], creg[[0]]) - new_order = [1, 0, 2, 3, 4] - expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[1]], creg[[0]]) - expected.barrier(qreg) - expected.measure(qreg, creg[new_order]) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_intra_if_else_route(self): - """test swap with if else controlflow construct""" - num_qubits = 5 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.x(1) - qc.measure(0, 0) - true_body = QuantumCircuit(qreg, creg[[0]]) - true_body.cx(0, 2) - false_body = QuantumCircuit(qreg, creg[[0]]) - false_body.cx(0, 4) - qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]]) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=8).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.x(1) - expected.measure(0, 0) - etrue_body = QuantumCircuit(qreg, creg[[0]]) - etrue_body.swap(0, 1) - etrue_body.cx(1, 2) - etrue_body.swap(1, 2) - etrue_body.swap(3, 4) - efalse_body = QuantumCircuit(qreg, creg[[0]]) - efalse_body.swap(0, 1) - efalse_body.swap(1, 2) - efalse_body.swap(3, 4) - efalse_body.cx(2, 3) - expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg[[0]]) - new_order = [1, 2, 0, 4, 3] - expected.measure(qreg, creg[new_order]) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_pre_intra_if_else(self): - """test swap with if else controlflow construct; cx in if statement""" - num_qubits = 5 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.cx(0, 2) - qc.x(1) - qc.measure(0, 0) - true_body = QuantumCircuit(qreg, creg[[0]]) - true_body.cx(0, 2) - false_body = QuantumCircuit(qreg, creg[[0]]) - false_body.cx(0, 4) - qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]]) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=2, trials=20).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - etrue_body = QuantumCircuit(qreg[[1, 2, 3, 4]], creg[[0]]) - efalse_body = QuantumCircuit(qreg[[1, 2, 3, 4]], creg[[0]]) - expected.h(0) - expected.x(1) - expected.swap(0, 1) - expected.cx(1, 2) - expected.measure(1, 0) - - etrue_body.cx(0, 1) - etrue_body.swap(2, 3) - etrue_body.swap(0, 1) - - efalse_body.swap(0, 1) - efalse_body.swap(2, 3) - efalse_body.cx(1, 2) - expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[1, 2, 3, 4]], creg[[0]]) - expected.measure(qreg, creg[[1, 2, 0, 4, 3]]) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_pre_intra_post_if_else(self): - """test swap with if else controlflow construct; cx before, in, and after if - statement""" - num_qubits = 5 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap.from_line(num_qubits) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.cx(0, 2) - qc.x(1) - qc.measure(0, 0) - true_body = QuantumCircuit(qreg, creg[[0]]) - true_body.cx(0, 2) - false_body = QuantumCircuit(qreg, creg[[0]]) - false_body.cx(0, 4) - qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]]) - qc.h(3) - qc.cx(3, 0) - qc.barrier() - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=1).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.x(1) - expected.swap(1, 2) - expected.cx(0, 1) - expected.measure(0, 0) - etrue_body = QuantumCircuit(qreg, creg[[0]]) - etrue_body.cx(0, 1) - etrue_body.swap(0, 1) - etrue_body.swap(4, 3) - etrue_body.swap(2, 3) - efalse_body = QuantumCircuit(qreg, creg[[0]]) - efalse_body.swap(0, 1) - efalse_body.swap(3, 4) - efalse_body.swap(2, 3) - efalse_body.cx(1, 2) - expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[0, 1, 2, 3, 4]], creg[[0]]) - expected.swap(1, 2) - expected.h(4) - expected.swap(3, 4) - expected.cx(3, 2) - expected.barrier() - expected.measure(qreg, creg[[2, 4, 0, 3, 1]]) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_if_expr(self): - """Test simple if conditional with an `Expr` condition.""" - coupling = CouplingMap.from_line(4) - - body = QuantumCircuit(4) - body.cx(0, 1) - body.cx(0, 2) - body.cx(0, 3) - qc = QuantumCircuit(4, 2) - qc.if_test(expr.logic_and(qc.clbits[0], qc.clbits[1]), body, [0, 1, 2, 3], []) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=58).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - def test_if_else_expr(self): - """Test simple if/else conditional with an `Expr` condition.""" - coupling = CouplingMap.from_line(4) - - true = QuantumCircuit(4) - true.cx(0, 1) - true.cx(0, 2) - true.cx(0, 3) - false = QuantumCircuit(4) - false.cx(3, 0) - false.cx(3, 1) - false.cx(3, 2) - qc = QuantumCircuit(4, 2) - qc.if_else(expr.logic_and(qc.clbits[0], qc.clbits[1]), true, false, [0, 1, 2, 3], []) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=58).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - def test_standalone_vars(self): - """Test that the routing works in the presence of stand-alone variables.""" - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Uint(8)) - c = expr.Var.new("c", types.Uint(8)) - qc = QuantumCircuit(5, inputs=[a]) - qc.add_var(b, 12) - qc.cx(0, 2) - qc.cx(1, 3) - qc.cx(3, 2) - qc.cx(3, 0) - qc.cx(4, 2) - qc.cx(4, 0) - qc.cx(1, 4) - qc.cx(3, 4) - with qc.if_test(a): - qc.store(a, False) - qc.add_var(c, 12) - qc.cx(0, 1) - with qc.if_test(a) as else_: - qc.store(a, False) - qc.add_var(c, 12) - qc.cx(0, 1) - with else_: - qc.cx(1, 2) - with qc.while_loop(a): - with qc.while_loop(a): - qc.add_var(c, 12) - qc.cx(1, 3) - qc.store(a, False) - with qc.switch(b) as case: - with case(0): - qc.add_var(c, 12) - qc.cx(3, 1) - with case(case.DEFAULT): - qc.cx(3, 1) - - cm = CouplingMap.from_line(5) - with self.assertWarns(DeprecationWarning): - pm = PassManager([StochasticSwap(cm, seed=0), CheckMap(cm)]) - _ = pm.run(qc) - self.assertTrue(pm.property_set["is_swap_mapped"]) - - def test_no_layout_change(self): - """test controlflow with no layout change needed""" - num_qubits = 5 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap.from_line(num_qubits) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.cx(0, 2) - qc.x(1) - qc.measure(0, 0) - true_body = QuantumCircuit(qreg, creg[[0]]) - true_body.x(2) - false_body = QuantumCircuit(qreg, creg[[0]]) - false_body.x(4) - qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]]) - qc.barrier(qreg) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=23).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.x(1) - expected.swap(1, 2) - expected.cx(0, 1) - expected.measure(0, 0) - etrue_body = QuantumCircuit(qreg[[1, 4]], creg[[0]]) - etrue_body.x(0) - efalse_body = QuantumCircuit(qreg[[1, 4]], creg[[0]]) - efalse_body.x(1) - expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[1, 4]], creg[[0]]) - expected.barrier(qreg) - expected.measure(qreg, creg[[0, 2, 1, 3, 4]]) - self.assertEqual(dag_to_circuit(cdag), expected) - - @data(1, 2, 3) - def test_for_loop(self, nloops): - """test stochastic swap with for_loop""" - # if the loop has only one iteration it isn't necessary for the pass - # to swap back to the starting layout. This test would check that - # optimization. - num_qubits = 3 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap.from_line(num_qubits) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.x(1) - for_body = QuantumCircuit(qreg) - for_body.cx(0, 2) - loop_parameter = None - qc.for_loop(range(nloops), loop_parameter, for_body, qreg, []) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=687).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.x(1) - efor_body = QuantumCircuit(qreg) - efor_body.swap(0, 1) - efor_body.cx(1, 2) - efor_body.swap(0, 1) - loop_parameter = None - expected.for_loop(range(nloops), loop_parameter, efor_body, qreg, []) - expected.measure(qreg, creg) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_while_loop(self): - """test while loop""" - num_qubits = 4 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(len(qreg)) - coupling = CouplingMap.from_line(num_qubits) - qc = QuantumCircuit(qreg, creg) - while_body = QuantumCircuit(qreg, creg) - while_body.reset(qreg[2:]) - while_body.h(qreg[2:]) - while_body.cx(0, 3) - while_body.measure(qreg[3], creg[3]) - qc.while_loop((creg, 0), while_body, qc.qubits, qc.clbits) - qc.barrier() - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=58).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - ewhile_body = QuantumCircuit(qreg, creg[:]) - ewhile_body.reset(qreg[2:]) - ewhile_body.h(qreg[2:]) - ewhile_body.swap(0, 1) - ewhile_body.swap(2, 3) - ewhile_body.cx(1, 2) - ewhile_body.measure(qreg[2], creg[3]) - ewhile_body.swap(1, 0) - ewhile_body.swap(3, 2) - expected.while_loop((creg, 0), ewhile_body, expected.qubits, expected.clbits) - expected.barrier() - expected.measure(qreg, creg) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_while_loop_expr(self): - """Test simple while loop with an `Expr` condition.""" - coupling = CouplingMap.from_line(4) - - body = QuantumCircuit(4) - body.cx(0, 1) - body.cx(0, 2) - body.cx(0, 3) - qc = QuantumCircuit(4, 2) - qc.while_loop(expr.logic_and(qc.clbits[0], qc.clbits[1]), body, [0, 1, 2, 3], []) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=58).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - def test_switch_single_case(self): - """Test routing of 'switch' with just a single case.""" - qreg = QuantumRegister(5, "q") - creg = ClassicalRegister(3, "c") - qc = QuantumCircuit(qreg, creg) - - case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.cx(2, 0) - qc.switch(creg, [(0, case0)], qreg[[0, 1, 2]], creg) - - coupling = CouplingMap.from_line(len(qreg)) - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, seed=58) - test = pass_(qc) - - check = CheckMap(coupling) - check(test) - self.assertTrue(check.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.swap(0, 1) - case0.cx(2, 1) - case0.swap(0, 1) - expected.switch(creg, [(0, case0)], qreg[[0, 1, 2]], creg[:]) - - self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) - - def test_switch_nonexhaustive(self): - """Test routing of 'switch' with several but nonexhaustive cases.""" - qreg = QuantumRegister(5, "q") - creg = ClassicalRegister(3, "c") - - qc = QuantumCircuit(qreg, creg) - case0 = QuantumCircuit(qreg, creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.cx(2, 0) - case1 = QuantumCircuit(qreg, creg[:]) - case1.cx(1, 2) - case1.cx(2, 3) - case1.cx(3, 1) - case2 = QuantumCircuit(qreg, creg[:]) - case2.cx(2, 3) - case2.cx(3, 4) - case2.cx(4, 2) - qc.switch(creg, [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg) - - coupling = CouplingMap.from_line(len(qreg)) - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, seed=58) - test = pass_(qc) - - check = CheckMap(coupling) - check(test) - self.assertTrue(check.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - case0 = QuantumCircuit(qreg, creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.swap(0, 1) - case0.cx(2, 1) - case0.swap(0, 1) - case1 = QuantumCircuit(qreg, creg[:]) - case1.cx(1, 2) - case1.cx(2, 3) - case1.swap(1, 2) - case1.cx(3, 2) - case1.swap(1, 2) - case2 = QuantumCircuit(qreg, creg[:]) - case2.cx(2, 3) - case2.cx(3, 4) - case2.swap(3, 4) - case2.cx(3, 2) - case2.swap(3, 4) - expected.switch(creg, [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg) - - self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) - - @data((0, 1, 2, 3), (CASE_DEFAULT,)) - def test_switch_exhaustive(self, labels): - """Test routing of 'switch' with exhaustive cases; we should not require restoring the - layout afterwards.""" - qreg = QuantumRegister(5, "q") - creg = ClassicalRegister(2, "c") - - qc = QuantumCircuit(qreg, creg) - case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.cx(2, 0) - qc.switch(creg, [(labels, case0)], qreg[[0, 1, 2]], creg) - - coupling = CouplingMap.from_line(len(qreg)) - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, seed=58) - test = pass_(qc) - - check = CheckMap(coupling) - check(test) - self.assertTrue(check.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.swap(0, 1) - case0.cx(2, 1) - expected.switch(creg, [(labels, case0)], qreg[[0, 1, 2]], creg) - - self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) - - def test_switch_nonexhaustive_expr(self): - """Test routing of 'switch' with an `Expr` target and several but nonexhaustive cases.""" - qreg = QuantumRegister(5, "q") - creg = ClassicalRegister(3, "c") - - qc = QuantumCircuit(qreg, creg) - case0 = QuantumCircuit(qreg, creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.cx(2, 0) - case1 = QuantumCircuit(qreg, creg[:]) - case1.cx(1, 2) - case1.cx(2, 3) - case1.cx(3, 1) - case2 = QuantumCircuit(qreg, creg[:]) - case2.cx(2, 3) - case2.cx(3, 4) - case2.cx(4, 2) - qc.switch(expr.bit_or(creg, 5), [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg) - - coupling = CouplingMap.from_line(len(qreg)) - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, seed=58) - test = pass_(qc) - - check = CheckMap(coupling) - check(test) - self.assertTrue(check.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - case0 = QuantumCircuit(qreg, creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.swap(0, 1) - case0.cx(2, 1) - case0.swap(0, 1) - case1 = QuantumCircuit(qreg, creg[:]) - case1.cx(1, 2) - case1.cx(2, 3) - case1.swap(1, 2) - case1.cx(3, 2) - case1.swap(1, 2) - case2 = QuantumCircuit(qreg, creg[:]) - case2.cx(2, 3) - case2.cx(3, 4) - case2.swap(3, 4) - case2.cx(3, 2) - case2.swap(3, 4) - expected.switch(expr.bit_or(creg, 5), [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg) - - self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) - - @data((0, 1, 2, 3), (CASE_DEFAULT,)) - def test_switch_exhaustive_expr(self, labels): - """Test routing of 'switch' with exhaustive cases on an `Expr` target; we should not require - restoring the layout afterwards.""" - qreg = QuantumRegister(5, "q") - creg = ClassicalRegister(2, "c") - - qc = QuantumCircuit(qreg, creg) - case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.cx(2, 0) - qc.switch(expr.bit_or(creg, 3), [(labels, case0)], qreg[[0, 1, 2]], creg) - - coupling = CouplingMap.from_line(len(qreg)) - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, seed=58) - test = pass_(qc) - - check = CheckMap(coupling) - check(test) - self.assertTrue(check.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) - case0.cx(0, 1) - case0.cx(1, 2) - case0.swap(0, 1) - case0.cx(2, 1) - expected.switch(expr.bit_or(creg, 3), [(labels, case0)], qreg[[0, 1, 2]], creg) - - self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) - - def test_nested_inner_cnot(self): - """test swap in nested if else controlflow construct; swap in inner""" - seed = 1 - num_qubits = 3 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap.from_line(num_qubits) - check_map_pass = CheckMap(coupling) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.x(1) - qc.measure(0, 0) - true_body = QuantumCircuit(qreg, creg[[0]]) - true_body.x(0) - - for_body = QuantumCircuit(qreg) - for_body.delay(10, 0) - for_body.barrier(qreg) - for_body.cx(0, 2) - loop_parameter = None - true_body.for_loop(range(3), loop_parameter, for_body, qreg, []) - - false_body = QuantumCircuit(qreg, creg[[0]]) - false_body.y(0) - qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]]) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=seed).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.x(1) - expected.measure(0, 0) - etrue_body = QuantumCircuit(qreg, creg[[0]]) - etrue_body.x(0) - - efor_body = QuantumCircuit(qreg) - efor_body.delay(10, 0) - efor_body.barrier(qreg) - efor_body.swap(1, 2) - efor_body.cx(0, 1) - efor_body.swap(1, 2) - etrue_body.for_loop(range(3), loop_parameter, efor_body, qreg, []) - - efalse_body = QuantumCircuit(qreg, creg[[0]]) - efalse_body.y(0) - expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg[[0]]) - expected.measure(qreg, creg) - - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_nested_outer_cnot(self): - """test swap with nested if else controlflow construct; swap in outer""" - seed = 200 - num_qubits = 5 - qreg = QuantumRegister(num_qubits, "q") - creg = ClassicalRegister(num_qubits) - coupling = CouplingMap.from_line(num_qubits) - qc = QuantumCircuit(qreg, creg) - qc.h(0) - qc.x(1) - qc.measure(0, 0) - true_body = QuantumCircuit(qreg, creg[[0]]) - true_body.cx(0, 2) - true_body.x(0) - - for_body = QuantumCircuit(qreg) - for_body.delay(10, 0) - for_body.barrier(qreg) - for_body.cx(1, 3) - loop_parameter = None - true_body.for_loop(range(3), loop_parameter, for_body, qreg, []) - - false_body = QuantumCircuit(qreg, creg[[0]]) - false_body.y(0) - qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]]) - qc.measure(qreg, creg) - - dag = circuit_to_dag(qc) - with self.assertWarns(DeprecationWarning): - cdag = StochasticSwap(coupling, seed=seed).run(dag) - check_map_pass = CheckMap(coupling) - check_map_pass.run(cdag) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qreg, creg) - expected.h(0) - expected.x(1) - expected.measure(0, 0) - etrue_body = QuantumCircuit(qreg, creg[[0]]) - etrue_body.swap(1, 2) - etrue_body.cx(0, 1) - etrue_body.x(0) - - efor_body = QuantumCircuit(qreg) - efor_body.delay(10, 0) - efor_body.barrier(qreg) - efor_body.cx(2, 3) - etrue_body.for_loop(range(3), loop_parameter, efor_body, qreg[[0, 1, 2, 3, 4]], []) - - efalse_body = QuantumCircuit(qreg, creg[[0]]) - efalse_body.y(0) - efalse_body.swap(1, 2) - expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg[[0]]) - expected.measure(qreg, creg[[0, 2, 1, 3, 4]]) - self.assertEqual(dag_to_circuit(cdag), expected) - - def test_disjoint_looping(self): - """Test looping controlflow on different qubit register""" - num_qubits = 4 - cm = CouplingMap.from_line(num_qubits) - qr = QuantumRegister(num_qubits, "q") - qc = QuantumCircuit(qr) - loop_body = QuantumCircuit(2) - loop_body.cx(0, 1) - qc.for_loop((0,), None, loop_body, [0, 2], []) - with self.assertWarns(DeprecationWarning): - cqc = StochasticSwap(cm, seed=0)(qc) - - expected = QuantumCircuit(qr) - efor_body = QuantumCircuit(qr[[0, 1, 2]]) - efor_body.swap(1, 2) - efor_body.cx(0, 1) - efor_body.swap(1, 2) - expected.for_loop((0,), None, efor_body, [0, 1, 2], []) - self.assertEqual(cqc, expected) - - def test_disjoint_multiblock(self): - """Test looping controlflow on different qubit register""" - num_qubits = 4 - cm = CouplingMap.from_line(num_qubits) - qr = QuantumRegister(num_qubits, "q") - cr = ClassicalRegister(1) - qc = QuantumCircuit(qr, cr) - true_body = QuantumCircuit(3, 1) - true_body.cx(0, 1) - false_body = QuantumCircuit(3, 1) - false_body.cx(0, 2) - qc.if_else((cr[0], 1), true_body, false_body, [0, 1, 2], [0]) - with self.assertWarns(DeprecationWarning): - cqc = StochasticSwap(cm, seed=353)(qc) - - expected = QuantumCircuit(qr, cr) - etrue_body = QuantumCircuit(qr[[0, 1, 2]], cr[[0]]) - etrue_body.cx(0, 1) - etrue_body.swap(0, 1) - efalse_body = QuantumCircuit(qr[[0, 1, 2]], cr[[0]]) - efalse_body.swap(0, 1) - efalse_body.cx(1, 2) - expected.if_else((cr[0], 1), etrue_body, efalse_body, [0, 1, 2], cr[[0]]) - self.assertEqual(cqc, expected) - - def test_multiple_ops_per_layer(self): - """Test circuits with multiple operations per layer""" - num_qubits = 6 - coupling = CouplingMap.from_line(num_qubits) - check_map_pass = CheckMap(coupling) - qr = QuantumRegister(num_qubits, "q") - qc = QuantumCircuit(qr) - # This cx and the for_loop are in the same layer. - qc.cx(0, 2) - with qc.for_loop((0,)): - qc.cx(3, 5) - with self.assertWarns(DeprecationWarning): - cqc = StochasticSwap(coupling, seed=0)(qc) - check_map_pass(cqc) - self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) - - expected = QuantumCircuit(qr) - expected.swap(0, 1) - expected.cx(1, 2) - efor_body = QuantumCircuit(qr[[3, 4, 5]]) - efor_body.swap(1, 2) - efor_body.cx(0, 1) - efor_body.swap(2, 1) - expected.for_loop((0,), None, efor_body, [3, 4, 5], []) - self.assertEqual(cqc, expected) - - def test_if_no_else_restores_layout(self): - """Test that an if block with no else branch restores the initial layout. If there is an - else branch, we don't need to guarantee this.""" - qc = QuantumCircuit(8, 1) - with qc.if_test((qc.clbits[0], False)): - # Just some arbitrary gates with no perfect layout. - qc.cx(3, 5) - qc.cx(4, 6) - qc.cx(1, 4) - qc.cx(7, 4) - qc.cx(0, 5) - qc.cx(7, 3) - qc.cx(1, 3) - qc.cx(5, 2) - qc.cx(6, 7) - qc.cx(3, 2) - qc.cx(6, 2) - qc.cx(2, 0) - qc.cx(7, 6) - coupling = CouplingMap.from_line(8) - with self.assertWarns(DeprecationWarning): - pass_ = StochasticSwap(coupling, seed=2022_10_13) - transpiled = pass_(qc) - - # Check the pass claims to have done things right. - initial_layout = Layout.generate_trivial_layout(*qc.qubits) - self.assertEqual(initial_layout, pass_.property_set["final_layout"]) - - # Check that pass really did do it right. - inner_block = transpiled.data[0].operation.blocks[0] - running_layout = initial_layout.copy() - for instruction in inner_block: - if instruction.operation.name == "swap": - running_layout.swap(*instruction.qubits) - self.assertEqual(initial_layout, running_layout) - - -@ddt -class TestStochasticSwapRandomCircuitValidOutput(QiskitTestCase): - """Assert the output of a transpilation with stochastic swap is a physical circuit.""" - - @classmethod - def setUpClass(cls): - super().setUpClass() - with warnings.catch_warnings(): - # Catch warnings since self.assertWarns cannot be used here. - # The `calibrate_instructions` argument is deprecated in Qiksit 1.3 - warnings.simplefilter("ignore", category=DeprecationWarning) - cls.backend = GenericBackendV2( - num_qubits=27, calibrate_instructions=True, control_flow=True, seed=42 - ) - cls.coupling_edge_set = {tuple(x) for x in cls.backend.coupling_map} - cls.basis_gates = set(cls.backend.operation_names) - - def assert_valid_circuit(self, transpiled): - """Assert circuit complies with constraints of backend.""" - self.assertIsInstance(transpiled, QuantumCircuit) - self.assertIsNotNone(getattr(transpiled, "_layout", None)) - - def _visit_block(circuit, qubit_mapping=None): - for instruction in circuit: - if instruction.operation.name in {"barrier", "measure"}: - continue - self.assertIn(instruction.operation.name, self.basis_gates) - qargs = tuple(qubit_mapping[x] for x in instruction.qubits) - if not isinstance(instruction.operation, ControlFlowOp): - if len(qargs) > 2 or len(qargs) < 0: - raise RuntimeError("Invalid number of qargs for instruction") - if len(qargs) == 2: - self.assertIn(qargs, self.coupling_edge_set) - else: - self.assertLessEqual(qargs[0], 26) - else: - for block in instruction.operation.blocks: - self.assertEqual(block.num_qubits, len(instruction.qubits)) - self.assertEqual(block.num_clbits, len(instruction.clbits)) - new_mapping = { - inner: qubit_mapping[outer] - for outer, inner in zip(instruction.qubits, block.qubits) - } - _visit_block(block, new_mapping) - - # Assert routing ran. - _visit_block( - transpiled, - qubit_mapping={qubit: index for index, qubit in enumerate(transpiled.qubits)}, - ) - - @data(*range(1, 27)) - def test_random_circuit_no_control_flow(self, size): - """Test that transpiled random circuits without control flow are physical circuits.""" - circuit = random_circuit(size, 3, measure=True, seed=12342) - with self.assertWarns(DeprecationWarning): - tqc = transpile( - circuit, - self.backend, - routing_method="stochastic", - layout_method="dense", - seed_transpiler=12342, - ) - self.assert_valid_circuit(tqc) - - @data(*range(1, 27)) - def test_random_circuit_no_control_flow_target(self, size): - """Test that transpiled random circuits without control flow are physical circuits.""" - circuit = random_circuit(size, 3, measure=True, seed=12342) - with self.assertWarns(DeprecationWarning): - tqc = transpile( - circuit, - routing_method="stochastic", - layout_method="dense", - seed_transpiler=12342, - target=GenericBackendV2( - num_qubits=27, - coupling_map=MUMBAI_CMAP, - ).target, - ) - self.assert_valid_circuit(tqc) - - @data(*range(4, 27)) - def test_random_circuit_for_loop(self, size): - """Test that transpiled random circuits with nested for loops are physical circuits.""" - circuit = random_circuit(size, 3, measure=False, seed=12342) - for_block = random_circuit(3, 2, measure=False, seed=12342) - inner_for_block = random_circuit(2, 1, measure=False, seed=12342) - with circuit.for_loop((1,)): - with circuit.for_loop((1,)): - circuit.append(inner_for_block, [0, 3]) - circuit.append(for_block, [1, 0, 2]) - circuit.measure_all() - - with self.assertWarns(DeprecationWarning): - tqc = transpile( - circuit, - self.backend, - basis_gates=list(self.basis_gates), - routing_method="stochastic", - layout_method="dense", - seed_transpiler=12342, - ) - self.assert_valid_circuit(tqc) - - @data(*range(6, 27)) - def test_random_circuit_if_else(self, size): - """Test that transpiled random circuits with if else blocks are physical circuits.""" - circuit = random_circuit(size, 3, measure=True, seed=12342) - if_block = random_circuit(3, 2, measure=True, seed=12342) - else_block = random_circuit(2, 1, measure=True, seed=12342) - - rng = numpy.random.default_rng(seed=12342) - inner_clbit_count = max((if_block.num_clbits, else_block.num_clbits)) - if inner_clbit_count > circuit.num_clbits: - circuit.add_bits([Clbit() for _ in [None] * (inner_clbit_count - circuit.num_clbits)]) - clbit_indices = list(range(circuit.num_clbits)) - rng.shuffle(clbit_indices) - - with circuit.if_test((circuit.clbits[0], True)) as else_: - circuit.append(if_block, [0, 2, 1], clbit_indices[: if_block.num_clbits]) - with else_: - circuit.append(else_block, [2, 5], clbit_indices[: else_block.num_clbits]) - - with self.assertWarns(DeprecationWarning): - tqc = transpile( - circuit, - self.backend, - basis_gates=list(self.basis_gates), - routing_method="stochastic", - layout_method="dense", - seed_transpiler=12342, - ) - self.assert_valid_circuit(tqc) - - -if __name__ == "__main__": - unittest.main() From 7c6fc2b443ae7359bc3974d224396f2780a6f6af Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 5 Feb 2025 18:11:06 -0500 Subject: [PATCH 2/2] Update releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml Co-authored-by: Jake Lishman --- .../notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml b/releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml index 7b1b19cc27e6..ea7fea45e397 100644 --- a/releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml +++ b/releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml @@ -1,8 +1,8 @@ --- upgrade_transpiler: - | - The deprecated ``StochasticSwap`` transpiler pass, and it's associated - built-in routing stage plugin `"stochastic"` have been removed. These + The deprecated ``StochasticSwap`` transpiler pass, and its associated + built-in routing stage plugin `"stochastic"`, have been removed. These were marked as deprecated in the Qiskit 1.3.0 release. The pass has been superseded by the :class:`.SabreSwap` which should be used instead as it offers better performance and output quality. For example if the