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 76b26d22a771..bcb21ee3774e 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -25,7 +25,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; @@ -48,7 +47,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 91568c863b44..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_os_rng(), - }; - let seed_vec: Vec = outer_rng - .sample_iter(&rand::distr::StandardUniform) - .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 f93f2af32a81..0e08cf72d03a 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -60,7 +60,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 7b190a0bd6d8..6b9bc40b6ce6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,7 +132,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 9ac2702f4cf2..ab585b9cf621 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/__init__.py b/qiskit/transpiler/__init__.py index 3751dc1ced07..198dc40c5d79 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -518,10 +518,6 @@ * - :ref:`basic ` - Greedy swap insertion to route a single operation at a time. - * - :ref:`stochastic ` - - Consider operations layer-by-layer, using a stochastic algorithm to find swap networks that - implement a suitable permutation to make the layer executable. - * - :ref:`lookahead ` - Breadth-first search with heuristic pruning to find swaps that make gates executable. @@ -557,25 +553,6 @@ This method typically has poor output quality. -.. _transpiler-preset-stage-routing-stochastic: - -Built-in ``stochastic`` plugin -.............................. - -.. deprecated:: 1.3 - Use :ref:`transpiler-preset-stage-routing-sabre` instead. - -Uses the :class:`.StochasticSwap` algorithm to route. In short, this stratifies the circuit into -layers, then uses a stochastic algorithm to find a permutation that will allow the layer to execute, -and a series of swaps that will implement that permutation in a hardware-valid way. - -The optimization level affects the number of stochastic trials used for each layer, and the amount -of work spent in :class:`.VF2PostLayout` to optimize the initial layout. - -This was Qiskit's primary routing algorithm for several years, until approximately 2021. Now, it -is reliably beaten in runtime and output quality by :ref:`Qiskit's custom Sabre-based routing -algorithm `. - .. _transpiler-preset-stage-routing-lookahead: Built-in ``lookahead`` plugin diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 1513863ba5e6..894fc072b25c 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -43,7 +43,6 @@ BasicSwap LookaheadSwap - StochasticSwap SabreSwap Commuting2qGateRouter StarPreRouting @@ -195,7 +194,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 b5a0c6f976c7..dfbc283e53d5 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 @@ -319,59 +318,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 - 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, - 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, - 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..453dc79f5d7f --- /dev/null +++ b/releasenotes/notes/remove-stochastic-swap-88e2d6be4c0d5713.yaml @@ -0,0 +1,42 @@ +--- +upgrade_transpiler: + - | + 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 + 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/benchmarks/mapping_passes.py b/test/benchmarks/mapping_passes.py index 07a6b037db5b..c5e00b8df2f0 100644 --- a/test/benchmarks/mapping_passes.py +++ b/test/benchmarks/mapping_passes.py @@ -100,11 +100,6 @@ def setup(self, n_qubits, depth): self.dag = apply_pass.run(self.enlarge_dag) self.backend_props = Fake20QV1().properties() - def time_stochastic_swap(self, _, __): - swap = StochasticSwap(self.coupling_map, seed=42) - swap.property_set["layout"] = self.layout - swap.run(self.dag) - def time_sabre_swap(self, _, __): swap = SabreSwap(self.coupling_map, seed=42) swap.property_set["layout"] = self.layout @@ -230,7 +225,6 @@ def setup(self, n_qubits, depth): apply_pass.property_set["layout"] = self.layout self.dag = apply_pass.run(self.enlarge_dag) self.backend_props = Fake20QV1().properties() - self.routed_dag = StochasticSwap(self.coupling_map, seed=42).run(self.dag) def time_gate_direction(self, _, __): GateDirection(self.coupling_map).run(self.routed_dag) diff --git a/test/benchmarks/transpiler_qualitative.py b/test/benchmarks/transpiler_qualitative.py index e53c153353ba..6f21280f7d35 100644 --- a/test/benchmarks/transpiler_qualitative.py +++ b/test/benchmarks/transpiler_qualitative.py @@ -21,12 +21,12 @@ class TranspilerQualitativeBench: - params = ([0, 1, 2, 3], ["stochastic", "sabre"], ["dense", "sabre"]) - param_names = ["optimization level", "routing method", "layout method"] + params = [0, 1, 2, 3] + param_names = ["optimization level"] timeout = 600 # pylint: disable=unused-argument - def setup(self, optimization_level, routing_method, layout_method): + def setup(self, optimization_level): self.backend = Fake27QPulseV1() self.qasm_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "qasm")) @@ -50,62 +50,50 @@ def setup(self, optimization_level, routing_method, layout_method): os.path.join(self.qasm_path, "time_qft_16.qasm") ) - def track_depth_transpile_4gt10_v1_81(self, optimization_level, routing_method, layout_method): + def track_depth_transpile_4gt10_v1_81(self, optimization_level): return transpile( self.depth_4gt10_v1_81, self.backend, - routing_method=routing_method, - layout_method=layout_method, optimization_level=optimization_level, seed_transpiler=0, ).depth() - def track_depth_transpile_4mod5_v0_19(self, optimization_level, routing_method, layout_method): + def track_depth_transpile_4mod5_v0_19(self, optimization_level): return transpile( self.depth_4mod5_v0_19, self.backend, - routing_method=routing_method, - layout_method=layout_method, optimization_level=optimization_level, seed_transpiler=0, ).depth() - def track_depth_transpile_mod8_10_178(self, optimization_level, routing_method, layout_method): + def track_depth_transpile_mod8_10_178(self, optimization_level): return transpile( self.depth_mod8_10_178, self.backend, - routing_method=routing_method, - layout_method=layout_method, optimization_level=optimization_level, seed_transpiler=0, ).depth() - def time_transpile_time_cnt3_5_179(self, optimization_level, routing_method, layout_method): + def time_transpile_time_cnt3_5_179(self, optimization_level): transpile( self.time_cnt3_5_179, self.backend, - routing_method=routing_method, - layout_method=layout_method, optimization_level=optimization_level, seed_transpiler=0, ) - def time_transpile_time_cnt3_5_180(self, optimization_level, routing_method, layout_method): + def time_transpile_time_cnt3_5_180(self, optimization_level): transpile( self.time_cnt3_5_180, self.backend, - routing_method=routing_method, - layout_method=layout_method, optimization_level=optimization_level, seed_transpiler=0, ) - def time_transpile_time_qft_16(self, optimization_level, routing_method, layout_method): + def time_transpile_time_qft_16(self, optimization_level): transpile( self.time_qft_16, self.backend, - routing_method=routing_method, - layout_method=layout_method, optimization_level=optimization_level, seed_transpiler=0, ) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 1607acd51c47..f59925226b58 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -955,10 +955,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: @@ -3404,32 +3401,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): @@ -3480,57 +3451,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.""" @@ -3593,64 +3513,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): @@ -3713,71 +3575,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..78bc23babc9a 100644 --- a/test/python/transpiler/test_mappers.py +++ b/test/python/transpiler/test_mappers.py @@ -71,13 +71,12 @@ def test_a_common_test(self): import unittest import os import sys -import warnings from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit, transpile 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 @@ -105,15 +104,8 @@ def create_passmanager(self, coupling_map, initial_layout=None): if initial_layout: 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)) + # pylint: disable=not-callable + passmanager.append(self.pass_class(CouplingMap(coupling_map), **self.additional_args)) return passmanager def create_backend(self): @@ -289,13 +281,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 fdec5b98be85..761d6cf2475c 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( @@ -307,13 +306,10 @@ def test_support_var_with_explicit_routing_pass(self): qc.cx(4, 0) 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_(qc) + 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 bae6328a9b84..000000000000 --- a/test/python/transpiler/test_stochastic_swap.py +++ /dev/null @@ -1,1654 +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 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() - cls.backend = GenericBackendV2(num_qubits=27, 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()