mirror of https://github.com/Qiskit/qiskit.git
Remove deprecated BIPMapping pass (#10526)
* Remove deprecated BIPMapping pass This commit removes the deprecated BIPMapping pass. It was deprecated in qiskit-terra 0.24.0 and the minimum deprecation period has elapsed for the Qiskit 0.45.0 release. This pass has been moved to an external plugin that can be installed separately and integrated into the transpiler more cleanly than a single pass and also separates the optional dependency on the proprietary CPLEX into a separate package. * Apply suggestions from code review Co-authored-by: Kevin Hartman <kevin@hart.mn> * Remove additional uses of BIPMapping There were still two locations where the BIPMapping pass was being used. First the dedicated unit tests, and then also import redirects for the docs and easier access. This commit removes these locations so nothing left is using the BIPMapping pass. * Update optionals documentation * Update install commands --------- Co-authored-by: Kevin Hartman <kevin@hart.mn>
This commit is contained in:
parent
884cfe6c66
commit
2ab1e547e7
|
@ -45,7 +45,6 @@ Routing
|
|||
LookaheadSwap
|
||||
StochasticSwap
|
||||
SabreSwap
|
||||
BIPMapping
|
||||
Commuting2qGateRouter
|
||||
|
||||
Basis Change
|
||||
|
@ -199,7 +198,6 @@ from .routing import LayoutTransformation
|
|||
from .routing import LookaheadSwap
|
||||
from .routing import StochasticSwap
|
||||
from .routing import SabreSwap
|
||||
from .routing import BIPMapping
|
||||
from .routing import Commuting2qGateRouter
|
||||
|
||||
# basis change
|
||||
|
|
|
@ -17,6 +17,5 @@ from .layout_transformation import LayoutTransformation
|
|||
from .lookahead_swap import LookaheadSwap
|
||||
from .stochastic_swap import StochasticSwap
|
||||
from .sabre_swap import SabreSwap
|
||||
from .bip_mapping import BIPMapping
|
||||
from .commuting_2q_gate_routing.commuting_2q_gate_router import Commuting2qGateRouter
|
||||
from .commuting_2q_gate_routing.swap_strategy import SwapStrategy
|
||||
|
|
|
@ -1,497 +0,0 @@
|
|||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2021.
|
||||
#
|
||||
# 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.
|
||||
"""Integer programming model for quantum circuit compilation."""
|
||||
import copy
|
||||
import logging
|
||||
from functools import lru_cache
|
||||
|
||||
import numpy as np
|
||||
|
||||
from qiskit.transpiler.exceptions import TranspilerError, CouplingError
|
||||
from qiskit.transpiler.layout import Layout
|
||||
from qiskit.circuit.library.standard_gates import SwapGate
|
||||
from qiskit.providers.models import BackendProperties
|
||||
from qiskit.quantum_info import two_qubit_cnot_decompose
|
||||
from qiskit.quantum_info.synthesis.two_qubit_decompose import (
|
||||
TwoQubitWeylDecomposition,
|
||||
trace_to_fid,
|
||||
)
|
||||
from qiskit.utils import optionals as _optionals
|
||||
from qiskit.utils.deprecation import deprecate_func
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@_optionals.HAS_DOCPLEX.require_in_instance
|
||||
class BIPMappingModel:
|
||||
"""Internal model to create and solve a BIP problem for mapping.
|
||||
|
||||
Attributes:
|
||||
problem (Model):
|
||||
A CPLEX problem model object, which is set by calling
|
||||
:method:`create_cpx_problem`. After calling :method:`solve_cpx_problem`,
|
||||
the solution will be stored in :attr:`solution`). None if it's not yet set.
|
||||
"""
|
||||
|
||||
@deprecate_func(
|
||||
since="0.24.0",
|
||||
additional_msg="This has been replaced by a new transpiler plugin package: "
|
||||
"qiskit-bip-mapper. More details can be found here: "
|
||||
"https://github.com/qiskit-community/qiskit-bip-mapper",
|
||||
) # pylint: disable=bad-docstring-quotes
|
||||
def __init__(self, dag, coupling_map, qubit_subset, dummy_timesteps=None):
|
||||
"""
|
||||
Args:
|
||||
dag (DAGCircuit): DAG circuit to be mapped
|
||||
coupling_map (CouplingMap): Coupling map of the device on which the `dag` is mapped.
|
||||
qubit_subset (list[int]): Sublist of physical qubits to be used in the mapping.
|
||||
dummy_timesteps (int):
|
||||
Number of dummy time steps, after each real layer of gates, to
|
||||
allow arbitrary swaps between neighbors.
|
||||
|
||||
Raises:
|
||||
MissingOptionalLibraryError: If docplex is not installed
|
||||
TranspilerError: If size of virtual qubits and physical qubits differ, or
|
||||
if coupling_map is not symmetric (bidirectional).
|
||||
"""
|
||||
|
||||
self._dag = dag
|
||||
self._coupling = copy.deepcopy(coupling_map) # reduced coupling map
|
||||
try:
|
||||
self._coupling = self._coupling.reduce(qubit_subset)
|
||||
except CouplingError as err:
|
||||
raise TranspilerError(
|
||||
"The 'coupling_map' reduced by 'qubit_subset' must be connected."
|
||||
) from err
|
||||
self._coupling.make_symmetric()
|
||||
self.global_qubit = qubit_subset # the map from reduced qubit index to global qubit index
|
||||
|
||||
self.problem = None
|
||||
self.solution = None
|
||||
self.num_vqubits = len(self._dag.qubits)
|
||||
self.num_pqubits = self._coupling.size()
|
||||
self._arcs = self._coupling.get_edges()
|
||||
|
||||
if self.num_vqubits != self.num_pqubits:
|
||||
raise TranspilerError(
|
||||
"BIPMappingModel assumes the same size of virtual and physical qubits."
|
||||
)
|
||||
|
||||
self._index_to_virtual = dict(enumerate(dag.qubits))
|
||||
|
||||
# Construct internal circuit model
|
||||
# Extract layers with 2-qubit gates
|
||||
self._to_su4layer = []
|
||||
self.su4layers = []
|
||||
for lay in dag.layers():
|
||||
laygates = []
|
||||
for node in lay["graph"].two_qubit_ops():
|
||||
i1 = self._dag.find_bit(node.qargs[0]).index
|
||||
i2 = self._dag.find_bit(node.qargs[1]).index
|
||||
laygates.append(((i1, i2), node))
|
||||
if laygates:
|
||||
self._to_su4layer.append(len(self.su4layers))
|
||||
self.su4layers.append(laygates)
|
||||
else:
|
||||
self._to_su4layer.append(-1)
|
||||
# Add dummy time steps inbetween su4layers. Dummy time steps can only contain SWAPs.
|
||||
self.gates = [] # layered 2q-gates with dummy steps
|
||||
for k, lay in enumerate(self.su4layers):
|
||||
self.gates.append(lay)
|
||||
if k == len(self.su4layers) - 1: # do not add dummy steps after the last layer
|
||||
break
|
||||
self.gates.extend([[]] * dummy_timesteps)
|
||||
|
||||
self.bprop = None # Backend properties to compute cx fidelities (set later if necessary)
|
||||
self.default_cx_error_rate = (
|
||||
None # Default cx error rate in case backend properties are not available
|
||||
)
|
||||
|
||||
logger.info("Num virtual qubits: %d", self.num_vqubits)
|
||||
logger.info("Num physical qubits: %d", self.num_pqubits)
|
||||
logger.info("Model depth: %d", self.depth)
|
||||
logger.info("Dummy steps: %d", dummy_timesteps)
|
||||
|
||||
@property
|
||||
def depth(self):
|
||||
"""Number of time-steps (including dummy steps)."""
|
||||
return len(self.gates)
|
||||
|
||||
def is_su4layer(self, depth: int) -> bool:
|
||||
"""Check if the depth-th layer is su4layer (layer containing 2q-gates) or not.
|
||||
|
||||
Args:
|
||||
depth: Depth of the ordinary layer
|
||||
|
||||
Returns:
|
||||
True if the depth-th layer is su4layer, otherwise False
|
||||
"""
|
||||
return self._to_su4layer[depth] >= 0
|
||||
|
||||
def to_su4layer_depth(self, depth: int) -> int:
|
||||
"""Return the depth as a su4layer. If the depth-th layer is not a su4layer, return -1.
|
||||
|
||||
Args:
|
||||
depth: Depth of the ordinary layer
|
||||
|
||||
Returns:
|
||||
su4layer depth if the depth-th layer is a su4layer, otherwise -1
|
||||
"""
|
||||
return self._to_su4layer[depth]
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def _is_dummy_step(self, t: int):
|
||||
"""Check if the time-step t is a dummy step or not."""
|
||||
return len(self.gates[t]) == 0
|
||||
|
||||
@_optionals.HAS_DOCPLEX.require_in_call
|
||||
def create_cpx_problem(
|
||||
self,
|
||||
objective: str,
|
||||
backend_prop: BackendProperties = None,
|
||||
line_symm: bool = False,
|
||||
depth_obj_weight: float = 0.1,
|
||||
default_cx_error_rate: float = 5e-3,
|
||||
):
|
||||
"""Create integer programming model to compile a circuit.
|
||||
|
||||
Args:
|
||||
objective:
|
||||
Type of objective function to be minimized:
|
||||
|
||||
* ``'gate_error'``: Approximate gate error of the circuit, which is given as the sum of
|
||||
negative logarithm of CNOT gate fidelities in the circuit. It takes into account
|
||||
only the CNOT gate errors reported in ``backend_prop``.
|
||||
* ``'depth'``: Depth (number of timesteps) of the circuit
|
||||
* ``'balanced'``: Weighted sum of gate_error and depth
|
||||
|
||||
backend_prop:
|
||||
Backend properties storing gate errors, which are required in computing certain
|
||||
types of objective function such as ``'gate_error'`` or ``'balanced'``.
|
||||
If this is not available, default_cx_error_rate is used instead.
|
||||
|
||||
line_symm:
|
||||
Use symmetry breaking constrainst for line topology. Should
|
||||
only be True if the hardware graph is a chain/line/path.
|
||||
|
||||
depth_obj_weight:
|
||||
Weight of depth objective in ``'balanced'`` objective function.
|
||||
|
||||
default_cx_error_rate:
|
||||
Default CX error rate to be used if backend_prop is not available.
|
||||
|
||||
Raises:
|
||||
TranspilerError: if unknown objective type is specified or invalid options are specified.
|
||||
MissingOptionalLibraryError: If docplex is not installed
|
||||
"""
|
||||
self.bprop = backend_prop
|
||||
self.default_cx_error_rate = default_cx_error_rate
|
||||
if self.bprop is None and self.default_cx_error_rate is None:
|
||||
raise TranspilerError("BackendProperties or default_cx_error_rate must be specified")
|
||||
from docplex.mp.model import Model
|
||||
|
||||
mdl = Model()
|
||||
|
||||
# *** Define main variables ***
|
||||
# Add w variables
|
||||
w = {}
|
||||
for t in range(self.depth):
|
||||
for q in range(self.num_vqubits):
|
||||
for j in range(self.num_pqubits):
|
||||
w[t, q, j] = mdl.binary_var(name=f"w_{t}_{q}_{j}")
|
||||
# Add y variables
|
||||
y = {}
|
||||
for t in range(self.depth):
|
||||
for ((p, q), _) in self.gates[t]:
|
||||
for (i, j) in self._arcs:
|
||||
y[t, p, q, i, j] = mdl.binary_var(name=f"y_{t}_{p}_{q}_{i}_{j}")
|
||||
# Add x variables
|
||||
x = {}
|
||||
for t in range(self.depth - 1):
|
||||
for q in range(self.num_vqubits):
|
||||
for i in range(self.num_pqubits):
|
||||
x[t, q, i, i] = mdl.binary_var(name=f"x_{t}_{q}_{i}_{i}")
|
||||
for j in self._coupling.neighbors(i):
|
||||
x[t, q, i, j] = mdl.binary_var(name=f"x_{t}_{q}_{i}_{j}")
|
||||
|
||||
# *** Define main constraints ***
|
||||
# Assignment constraints for w variables
|
||||
for t in range(self.depth):
|
||||
for q in range(self.num_vqubits):
|
||||
mdl.add_constraint(
|
||||
sum(w[t, q, j] for j in range(self.num_pqubits)) == 1,
|
||||
ctname=f"assignment_vqubits_{q}_at_{t}",
|
||||
)
|
||||
for t in range(self.depth):
|
||||
for j in range(self.num_pqubits):
|
||||
mdl.add_constraint(
|
||||
sum(w[t, q, j] for q in range(self.num_vqubits)) == 1,
|
||||
ctname=f"assignment_pqubits_{j}_at_{t}",
|
||||
)
|
||||
# Each gate must be implemented
|
||||
for t in range(self.depth):
|
||||
for ((p, q), _) in self.gates[t]:
|
||||
mdl.add_constraint(
|
||||
sum(y[t, p, q, i, j] for (i, j) in self._arcs) == 1,
|
||||
ctname=f"implement_gate_{p}_{q}_at_{t}",
|
||||
)
|
||||
# Gate can be implemented iff both of its qubits are located at the associated nodes
|
||||
for t in range(self.depth - 1):
|
||||
for ((p, q), _) in self.gates[t]:
|
||||
for (i, j) in self._arcs:
|
||||
# Apply McCormick to y[t, p, q, i, j] == w[t, p, i] * w[t, q, j]
|
||||
mdl.add_constraint(
|
||||
y[t, p, q, i, j] >= w[t, p, i] + w[t, q, j] - 1,
|
||||
ctname=f"McCormickLB_{p}_{q}_{i}_{j}_at_{t}",
|
||||
)
|
||||
# Stronger version of McCormick: gate (p,q) is implemented at (i, j)
|
||||
# if i moves to i or j, and j moves to i or j
|
||||
mdl.add_constraint(
|
||||
y[t, p, q, i, j] <= x[t, p, i, i] + x[t, p, i, j],
|
||||
ctname=f"McCormickUB1_{p}_{q}_{i}_{j}_at_{t}",
|
||||
)
|
||||
mdl.add_constraint(
|
||||
y[t, p, q, i, j] <= x[t, q, j, i] + x[t, q, j, j],
|
||||
ctname=f"McCormickUB2_{p}_{q}_{i}_{j}_at_{t}",
|
||||
)
|
||||
# For last time step, use regular McCormick
|
||||
for ((p, q), _) in self.gates[self.depth - 1]:
|
||||
for (i, j) in self._arcs:
|
||||
# Apply McCormick to y[self.depth - 1, p, q, i, j]
|
||||
# == w[self.depth - 1, p, i] * w[self.depth - 1, q, j]
|
||||
mdl.add_constraint(
|
||||
y[self.depth - 1, p, q, i, j]
|
||||
>= w[self.depth - 1, p, i] + w[self.depth - 1, q, j] - 1,
|
||||
ctname=f"McCormickLB_{p}_{q}_{i}_{j}_at_last",
|
||||
)
|
||||
mdl.add_constraint(
|
||||
y[self.depth - 1, p, q, i, j] <= w[self.depth - 1, p, i],
|
||||
ctname=f"McCormickUB1_{p}_{q}_{i}_{j}_at_last",
|
||||
)
|
||||
mdl.add_constraint(
|
||||
y[self.depth - 1, p, q, i, j] <= w[self.depth - 1, q, j],
|
||||
ctname=f"McCormickUB2_{p}_{q}_{i}_{j}_at_last",
|
||||
)
|
||||
# Logical qubit flow-out constraints
|
||||
for t in range(self.depth - 1): # Flow out; skip last time step
|
||||
for q in range(self.num_vqubits):
|
||||
for i in range(self.num_pqubits):
|
||||
mdl.add_constraint(
|
||||
w[t, q, i]
|
||||
== x[t, q, i, i] + sum(x[t, q, i, j] for j in self._coupling.neighbors(i)),
|
||||
ctname=f"flow_out_{q}_{i}_at_{t}",
|
||||
)
|
||||
# Logical qubit flow-in constraints
|
||||
for t in range(1, self.depth): # Flow in; skip first time step
|
||||
for q in range(self.num_vqubits):
|
||||
for i in range(self.num_pqubits):
|
||||
mdl.add_constraint(
|
||||
w[t, q, i]
|
||||
== x[t - 1, q, i, i]
|
||||
+ sum(x[t - 1, q, j, i] for j in self._coupling.neighbors(i)),
|
||||
ctname=f"flow_in_{q}_{i}_at_{t}",
|
||||
)
|
||||
# If a gate is implemented, involved qubits cannot swap with other positions
|
||||
for t in range(self.depth - 1):
|
||||
for ((p, q), _) in self.gates[t]:
|
||||
for (i, j) in self._arcs:
|
||||
mdl.add_constraint(
|
||||
x[t, p, i, j] == x[t, q, j, i], ctname=f"swap_{p}_{q}_{i}_{j}_at_{t}"
|
||||
)
|
||||
# Qubit not in gates can flip with their neighbors
|
||||
for t in range(self.depth - 1):
|
||||
q_no_gate = list(range(self.num_vqubits))
|
||||
for ((p, q), _) in self.gates[t]:
|
||||
q_no_gate.remove(p)
|
||||
q_no_gate.remove(q)
|
||||
for (i, j) in self._arcs:
|
||||
mdl.add_constraint(
|
||||
sum(x[t, q, i, j] for q in q_no_gate) == sum(x[t, p, j, i] for p in q_no_gate),
|
||||
ctname=f"swap_no_gate_{i}_{j}_at_{t}",
|
||||
)
|
||||
|
||||
# *** Define supplemental variables ***
|
||||
# Add z variables to count dummy steps (supplemental variables for symmetry breaking)
|
||||
z = {}
|
||||
for t in range(self.depth):
|
||||
if self._is_dummy_step(t):
|
||||
z[t] = mdl.binary_var(name=f"z_{t}")
|
||||
|
||||
# *** Define supplemental constraints ***
|
||||
# See if a dummy time step is needed
|
||||
for t in range(self.depth):
|
||||
if self._is_dummy_step(t):
|
||||
for q in range(self.num_vqubits):
|
||||
mdl.add_constraint(
|
||||
sum(x[t, q, i, j] for (i, j) in self._arcs) <= z[t],
|
||||
ctname=f"dummy_ts_needed_for_vqubit_{q}_at_{t}",
|
||||
)
|
||||
# Symmetry breaking between dummy time steps
|
||||
for t in range(self.depth - 1):
|
||||
# This is a dummy time step and the next one is dummy too
|
||||
if self._is_dummy_step(t) and self._is_dummy_step(t + 1):
|
||||
# We cannot use the next time step unless this one is used too
|
||||
mdl.add_constraint(z[t] >= z[t + 1], ctname=f"dummy_precedence_{t}")
|
||||
# Symmetry breaking on the line -- only works on line topology!
|
||||
if line_symm:
|
||||
for h in range(1, self.num_vqubits):
|
||||
mdl.add_constraint(
|
||||
sum(w[0, p, 0] for p in range(h))
|
||||
+ sum(w[0, q, self.num_pqubits - 1] for q in range(h, self.num_vqubits))
|
||||
>= 1,
|
||||
ctname=f"sym_break_line_{h}",
|
||||
)
|
||||
|
||||
# *** Define objevtive function ***
|
||||
if objective == "depth":
|
||||
objexr = sum(z[t] for t in range(self.depth) if self._is_dummy_step(t))
|
||||
for t in range(self.depth - 1):
|
||||
for q in range(self.num_vqubits):
|
||||
for (i, j) in self._arcs:
|
||||
objexr += 0.01 * x[t, q, i, j]
|
||||
mdl.minimize(objexr)
|
||||
elif objective in ("gate_error", "balanced"):
|
||||
# We add the depth objective with coefficient depth_obj_weight if balanced was selected.
|
||||
objexr = 0
|
||||
for t in range(self.depth - 1):
|
||||
for (p, q), node in self.gates[t]:
|
||||
for (i, j) in self._arcs:
|
||||
# We pay the cost for gate implementation.
|
||||
pbest_fid = -np.log(self._max_expected_fidelity(node, i, j))
|
||||
objexr += y[t, p, q, i, j] * pbest_fid
|
||||
# If a gate is mirrored (followed by a swap on the same qubit pair),
|
||||
# its cost should be replaced with the cost of the combined (mirrored) gate.
|
||||
pbest_fidm = -np.log(self._max_expected_mirrored_fidelity(node, i, j))
|
||||
objexr += x[t, q, i, j] * (pbest_fidm - pbest_fid) / 2
|
||||
# Cost of swaps on unused qubits
|
||||
for q in range(self.num_vqubits):
|
||||
used_qubits = {q for (pair, _) in self.gates[t] for q in pair}
|
||||
if q not in used_qubits:
|
||||
for i in range(self.num_pqubits):
|
||||
for j in self._coupling.neighbors(i):
|
||||
objexr += x[t, q, i, j] * -3 / 2 * np.log(self._cx_fidelity(i, j))
|
||||
# Cost for the last layer (x variables are not defined for depth-1)
|
||||
for (p, q), node in self.gates[self.depth - 1]:
|
||||
for (i, j) in self._arcs:
|
||||
pbest_fid = -np.log(self._max_expected_fidelity(node, i, j))
|
||||
objexr += y[self.depth - 1, p, q, i, j] * pbest_fid
|
||||
if objective == "balanced":
|
||||
objexr += depth_obj_weight * sum(
|
||||
z[t] for t in range(self.depth) if self._is_dummy_step(t)
|
||||
)
|
||||
mdl.minimize(objexr)
|
||||
else:
|
||||
raise TranspilerError(f"Unknown objective type: {objective}")
|
||||
|
||||
self.problem = mdl
|
||||
logger.info("BIP problem stats: %s", self.problem.statistics)
|
||||
|
||||
def _max_expected_fidelity(self, node, i, j):
|
||||
return max(
|
||||
gfid * self._cx_fidelity(i, j) ** k
|
||||
for k, gfid in enumerate(self._gate_fidelities(node))
|
||||
)
|
||||
|
||||
def _max_expected_mirrored_fidelity(self, node, i, j):
|
||||
return max(
|
||||
gfid * self._cx_fidelity(i, j) ** k
|
||||
for k, gfid in enumerate(self._mirrored_gate_fidelities(node))
|
||||
)
|
||||
|
||||
def _cx_fidelity(self, i, j) -> float:
|
||||
# fidelity of cx on global physical qubits
|
||||
if self.bprop is not None:
|
||||
return 1.0 - self.bprop.gate_error("cx", [self.global_qubit[i], self.global_qubit[j]])
|
||||
else:
|
||||
return 1.0 - self.default_cx_error_rate
|
||||
|
||||
@staticmethod
|
||||
@lru_cache()
|
||||
def _gate_fidelities(node):
|
||||
matrix = node.op.to_matrix()
|
||||
target = TwoQubitWeylDecomposition(matrix)
|
||||
traces = two_qubit_cnot_decompose.traces(target)
|
||||
return [trace_to_fid(traces[i]) for i in range(4)]
|
||||
|
||||
@staticmethod
|
||||
@lru_cache()
|
||||
def _mirrored_gate_fidelities(node):
|
||||
matrix = node.op.to_matrix()
|
||||
swap = SwapGate().to_matrix()
|
||||
targetm = TwoQubitWeylDecomposition(matrix @ swap)
|
||||
tracesm = two_qubit_cnot_decompose.traces(targetm)
|
||||
return [trace_to_fid(tracesm[i]) for i in range(4)]
|
||||
|
||||
@_optionals.HAS_CPLEX.require_in_call
|
||||
def solve_cpx_problem(self, time_limit: float = 60, threads: int = None) -> str:
|
||||
"""Solve the BIP problem using CPLEX.
|
||||
|
||||
Args:
|
||||
time_limit:
|
||||
Time limit (seconds) given to CPLEX.
|
||||
|
||||
threads:
|
||||
Number of threads to be allowed for CPLEX to use.
|
||||
|
||||
Returns:
|
||||
Status string that CPLEX returned after solving the BIP problem.
|
||||
|
||||
Raises:
|
||||
MissingOptionalLibraryError: If CPLEX is not installed
|
||||
"""
|
||||
self.problem.set_time_limit(time_limit)
|
||||
if threads is not None:
|
||||
self.problem.context.cplex_parameters.threads = threads
|
||||
self.problem.context.cplex_parameters.randomseed = 777
|
||||
|
||||
self.solution = self.problem.solve()
|
||||
|
||||
status = self.problem.solve_details.status
|
||||
logger.info("BIP solution status: %s", status)
|
||||
return status
|
||||
|
||||
def get_layout(self, t: int) -> Layout:
|
||||
"""Get layout at time-step t.
|
||||
|
||||
Args:
|
||||
t: Time-step
|
||||
|
||||
Returns:
|
||||
Layout
|
||||
"""
|
||||
dic = {}
|
||||
for q in range(self.num_vqubits):
|
||||
for i in range(self.num_pqubits):
|
||||
if self.solution.get_value(f"w_{t}_{q}_{i}") > 0.5:
|
||||
dic[self._index_to_virtual[q]] = self.global_qubit[i]
|
||||
layout = Layout(dic)
|
||||
for reg in self._dag.qregs.values():
|
||||
layout.add_register(reg)
|
||||
return layout
|
||||
|
||||
def get_swaps(self, t: int) -> list:
|
||||
"""Get swaps (pairs of physical qubits) inserted at time-step ``t``.
|
||||
|
||||
Args:
|
||||
t: Time-step (<= depth - 1)
|
||||
|
||||
Returns:
|
||||
List of swaps (pairs of physical qubits (integers))
|
||||
"""
|
||||
swaps = []
|
||||
for (i, j) in self._arcs:
|
||||
if i >= j:
|
||||
continue
|
||||
for q in range(self.num_vqubits):
|
||||
if self.solution.get_value(f"x_{t}_{q}_{i}_{j}") > 0.5:
|
||||
swaps.append((self.global_qubit[i], self.global_qubit[j]))
|
||||
return swaps
|
|
@ -1,273 +0,0 @@
|
|||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2021.
|
||||
#
|
||||
# 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 given ``coupling_map``, allocating qubits and adding swap gates."""
|
||||
import copy
|
||||
import logging
|
||||
import math
|
||||
|
||||
from qiskit.circuit import QuantumRegister
|
||||
from qiskit.circuit.library.standard_gates import SwapGate
|
||||
from qiskit.dagcircuit import DAGCircuit, DAGOpNode
|
||||
from qiskit.utils import optionals as _optionals
|
||||
from qiskit.transpiler import TransformationPass
|
||||
from qiskit.transpiler.exceptions import TranspilerError
|
||||
from qiskit.transpiler.passes.routing.algorithms.bip_model import BIPMappingModel
|
||||
from qiskit.transpiler.target import target_to_backend_properties, Target
|
||||
from qiskit.utils.deprecation import deprecate_func
|
||||
from qiskit.transpiler.passes.layout import disjoint_utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@_optionals.HAS_CPLEX.require_in_instance("BIP-based mapping pass")
|
||||
@_optionals.HAS_DOCPLEX.require_in_instance("BIP-based mapping pass")
|
||||
class BIPMapping(TransformationPass):
|
||||
r"""Map a DAGCircuit onto a given ``coupling_map``, allocating qubits and adding swap gates.
|
||||
|
||||
The BIP mapper tries to find the best layout and routing at once by
|
||||
solving a BIP (binary integer programming) problem as described in [1].
|
||||
|
||||
The BIP problem represents the layer-by-layer mapping of 2-qubit gates, assuming all the gates
|
||||
in a layer can be run on the ``coupling_map``. In the problem, the variables :math:`w` represent
|
||||
the layout of qubits for each layer and the variables :math:`x` represent which pair of qubits
|
||||
should be swapped in between layers. Based on the values in the solution of the BIP problem,
|
||||
the mapped circuit will be constructed.
|
||||
|
||||
The BIP mapper depends on ``docplex`` to represent the BIP problem and CPLEX (``cplex``)
|
||||
to solve it. Those packages can be installed with ``pip install qiskit-terra[bip-mapper]``.
|
||||
Since the free version of CPLEX can solve only small BIP problems, i.e. mapping of circuits
|
||||
with less than about 5 qubits, the paid version of CPLEX may be needed to map larger circuits.
|
||||
|
||||
If you want to fix physical qubits to be used in the mapping (e.g. running Quantum Volume
|
||||
circuits), you need to supply ``qubit_subset``, i.e. list of physical qubits to be used
|
||||
within the ``coupling_map``.
|
||||
Please do not use ``initial_layout`` for that purpose because the BIP mapper gracefully
|
||||
ignores ``initial_layout`` (and tries to determines its best layout).
|
||||
|
||||
.. warning::
|
||||
The BIP mapper does not scale very well with respect to the number of qubits or gates.
|
||||
For example, it may not work with ``qubit_subset`` beyond 10 qubits because
|
||||
the BIP solver (CPLEX) may not find any solution within the default time limit.
|
||||
|
||||
**References:**
|
||||
|
||||
[1] G. Nannicini et al. "Optimal qubit assignment and routing via integer programming."
|
||||
`arXiv:2106.06446 <https://arxiv.org/abs/2106.06446>`_
|
||||
"""
|
||||
|
||||
@deprecate_func(
|
||||
since="0.24.0",
|
||||
additional_msg="This has been replaced by a new transpiler plugin package: "
|
||||
"qiskit-bip-mapper. More details can be found here: "
|
||||
"https://github.com/qiskit-community/qiskit-bip-mapper",
|
||||
) # pylint: disable=bad-docstring-quotes
|
||||
def __init__(
|
||||
self,
|
||||
coupling_map,
|
||||
qubit_subset=None,
|
||||
objective="balanced",
|
||||
backend_prop=None,
|
||||
time_limit=30,
|
||||
threads=None,
|
||||
max_swaps_inbetween_layers=None,
|
||||
depth_obj_weight=0.1,
|
||||
default_cx_error_rate=5e-3,
|
||||
):
|
||||
"""BIPMapping initializer.
|
||||
|
||||
Args:
|
||||
coupling_map (Union[CouplingMap, Target]): Directed graph represented a coupling map.
|
||||
qubit_subset (list[int]): Sublist of physical qubits to be used in the mapping.
|
||||
If None, all qubits in the coupling_map will be considered.
|
||||
objective (str): Type of objective function to be minimized:
|
||||
|
||||
* ``'gate_error'``: Approximate gate error of the circuit, which is given as the sum of
|
||||
negative logarithm of 2q-gate fidelities in the circuit. It takes into account only
|
||||
the 2q-gate (CNOT) errors reported in ``backend_prop`` and ignores the other errors
|
||||
in such as 1q-gates, SPAMs and idle times.
|
||||
* ``'depth'``: Depth (number of 2q-gate layers) of the circuit.
|
||||
* ``'balanced'``: [Default] Weighted sum of ``'gate_error'`` and ``'depth'``
|
||||
|
||||
backend_prop (BackendProperties): Backend properties object containing 2q-gate gate errors,
|
||||
which are required in computing certain types of objective function
|
||||
such as ``'gate_error'`` or ``'balanced'``. If this is not available,
|
||||
default_cx_error_rate is used instead.
|
||||
time_limit (float): Time limit for solving BIP in seconds
|
||||
threads (int): Number of threads to be allowed for CPLEX to solve BIP
|
||||
max_swaps_inbetween_layers (int):
|
||||
Number of swaps allowed in between layers. If None, automatically set.
|
||||
Large value could decrease the probability to build infeasible BIP problem but also
|
||||
could reduce the chance of finding a feasible solution within the ``time_limit``.
|
||||
|
||||
depth_obj_weight (float):
|
||||
Weight of depth objective in ``'balanced'`` objective. The balanced objective is the
|
||||
sum of error_rate + depth_obj_weight * depth.
|
||||
|
||||
default_cx_error_rate (float):
|
||||
Default CX error rate to be used if backend_prop is not available.
|
||||
|
||||
Raises:
|
||||
MissingOptionalLibraryError: if cplex or docplex are not installed.
|
||||
TranspilerError: if invalid options are specified.
|
||||
"""
|
||||
super().__init__()
|
||||
if isinstance(coupling_map, Target):
|
||||
self.target = coupling_map
|
||||
self.coupling_map = self.target.build_coupling_map()
|
||||
self.backend_prop = target_to_backend_properties(self.target)
|
||||
else:
|
||||
self.target = None
|
||||
self.coupling_map = coupling_map
|
||||
self.backend_prop = None
|
||||
self.qubit_subset = qubit_subset
|
||||
self.objective = objective
|
||||
if backend_prop is not None:
|
||||
self.backend_prop = backend_prop
|
||||
self.time_limit = time_limit
|
||||
self.threads = threads
|
||||
self.max_swaps_inbetween_layers = max_swaps_inbetween_layers
|
||||
self.depth_obj_weight = depth_obj_weight
|
||||
self.default_cx_error_rate = default_cx_error_rate
|
||||
if self.coupling_map is not None and self.qubit_subset is None:
|
||||
self.qubit_subset = list(range(self.coupling_map.size()))
|
||||
|
||||
def run(self, dag):
|
||||
"""Run the BIPMapping pass on `dag`, assuming the number of virtual qubits (defined in
|
||||
`dag`) and the number of physical qubits (defined in `coupling_map`) are the same.
|
||||
|
||||
Args:
|
||||
dag (DAGCircuit): DAG to map.
|
||||
|
||||
Returns:
|
||||
DAGCircuit: A mapped DAG. If there is no 2q-gate in DAG or it fails to map,
|
||||
returns the original dag.
|
||||
|
||||
Raises:
|
||||
TranspilerError: if the number of virtual and physical qubits are not the same.
|
||||
AssertionError: if the final layout is not valid.
|
||||
"""
|
||||
if self.coupling_map is None:
|
||||
return dag
|
||||
|
||||
if len(dag.qubits) > len(self.qubit_subset):
|
||||
raise TranspilerError("More virtual qubits exist than physical qubits.")
|
||||
|
||||
if len(dag.qubits) != len(self.qubit_subset):
|
||||
raise TranspilerError(
|
||||
"BIPMapping requires the number of virtual and physical qubits to be the same. "
|
||||
"Supply 'qubit_subset' to specify physical qubits to use."
|
||||
)
|
||||
disjoint_utils.require_layout_isolated_to_component(
|
||||
dag, self.coupling_map if self.target is None else self.target
|
||||
)
|
||||
|
||||
original_dag = dag
|
||||
|
||||
dummy_steps = math.ceil(math.sqrt(dag.num_qubits()))
|
||||
if self.max_swaps_inbetween_layers is not None:
|
||||
dummy_steps = max(0, self.max_swaps_inbetween_layers - 1)
|
||||
|
||||
model = BIPMappingModel(
|
||||
dag=dag,
|
||||
coupling_map=self.coupling_map,
|
||||
qubit_subset=self.qubit_subset,
|
||||
dummy_timesteps=dummy_steps,
|
||||
)
|
||||
|
||||
if len(model.su4layers) == 0:
|
||||
logger.info("BIPMapping is skipped due to no 2q-gates.")
|
||||
return original_dag
|
||||
|
||||
model.create_cpx_problem(
|
||||
objective=self.objective,
|
||||
backend_prop=self.backend_prop,
|
||||
depth_obj_weight=self.depth_obj_weight,
|
||||
default_cx_error_rate=self.default_cx_error_rate,
|
||||
)
|
||||
|
||||
status = model.solve_cpx_problem(time_limit=self.time_limit, threads=self.threads)
|
||||
if model.solution is None:
|
||||
logger.warning("Failed to solve a BIP problem. Status: %s", status)
|
||||
return original_dag
|
||||
|
||||
# Get the optimized initial layout
|
||||
optimized_layout = model.get_layout(0)
|
||||
|
||||
# Create a layout to track changes in layout for each layer
|
||||
layout = copy.deepcopy(optimized_layout)
|
||||
|
||||
# Construct the mapped circuit
|
||||
canonical_qreg = QuantumRegister(self.coupling_map.size(), "q")
|
||||
mapped_dag = self._create_empty_dagcircuit(dag, canonical_qreg)
|
||||
interval = dummy_steps + 1
|
||||
for k, layer in enumerate(dag.layers()):
|
||||
if model.is_su4layer(k):
|
||||
su4dep = model.to_su4layer_depth(k)
|
||||
# add swaps between (su4dep-1)-th and su4dep-th su4layer
|
||||
from_steps = max(interval * (su4dep - 1), 0)
|
||||
to_steps = min(interval * su4dep, model.depth - 1)
|
||||
for t in range(from_steps, to_steps): # pylint: disable=invalid-name
|
||||
for (i, j) in model.get_swaps(t):
|
||||
mapped_dag.apply_operation_back(
|
||||
op=SwapGate(),
|
||||
qargs=[canonical_qreg[i], canonical_qreg[j]],
|
||||
)
|
||||
# update layout, swapping physical qubits (i, j)
|
||||
layout.swap(i, j)
|
||||
|
||||
# map gates in k-th layer
|
||||
for node in layer["graph"].nodes():
|
||||
if isinstance(node, DAGOpNode):
|
||||
mapped_dag.apply_operation_back(
|
||||
op=copy.deepcopy(node.op),
|
||||
qargs=[canonical_qreg[layout[q]] for q in node.qargs],
|
||||
cargs=node.cargs,
|
||||
)
|
||||
# TODO: double check with y values?
|
||||
|
||||
# Check final layout
|
||||
final_layout = model.get_layout(model.depth - 1)
|
||||
if layout != final_layout:
|
||||
raise AssertionError(
|
||||
f"Bug: final layout {final_layout} != the layout computed from swaps {layout}"
|
||||
)
|
||||
|
||||
self.property_set["layout"] = self._to_full_layout(optimized_layout)
|
||||
self.property_set["final_layout"] = self._to_full_layout(final_layout)
|
||||
|
||||
return mapped_dag
|
||||
|
||||
@staticmethod
|
||||
def _create_empty_dagcircuit(source_dag: DAGCircuit, canonical_qreg: QuantumRegister):
|
||||
target_dag = DAGCircuit()
|
||||
target_dag.name = source_dag.name
|
||||
target_dag._global_phase = source_dag._global_phase
|
||||
target_dag.metadata = source_dag.metadata
|
||||
|
||||
target_dag.add_qreg(canonical_qreg)
|
||||
for creg in source_dag.cregs.values():
|
||||
target_dag.add_creg(creg)
|
||||
|
||||
return target_dag
|
||||
|
||||
def _to_full_layout(self, layout):
|
||||
# fill layout with ancilla qubits (required by drawers)
|
||||
idle_physical_qubits = [
|
||||
q for q in range(self.coupling_map.size()) if q not in layout.get_physical_bits()
|
||||
]
|
||||
if idle_physical_qubits:
|
||||
qreg = QuantumRegister(len(idle_physical_qubits), name="ancilla")
|
||||
for idx, idle_q in enumerate(idle_physical_qubits):
|
||||
layout[idle_q] = qreg[idx]
|
||||
layout.add_register(qreg)
|
||||
return layout
|
|
@ -58,7 +58,8 @@ External Python Libraries
|
|||
* - .. py:data:: HAS_CPLEX
|
||||
- The `IBM CPLEX Optimizer <https://www.ibm.com/analytics/cplex-optimizer>`__ is a
|
||||
high-performance mathematical programming solver for linear, mixed-integer and quadratic
|
||||
programming. It is required by the :class:`.BIPMapping` transpiler pass.
|
||||
programming. This is no longer by Qiskit, but it weas historically and the optional
|
||||
remains for backwards compatibility.
|
||||
|
||||
* - .. py:data:: HAS_CVXPY
|
||||
- `CVXPY <https://www.cvxpy.org/>`__ is a Python package for solving convex optimization
|
||||
|
@ -68,7 +69,8 @@ External Python Libraries
|
|||
* - .. py:data:: HAS_DOCPLEX
|
||||
- `IBM Decision Optimization CPLEX Modelling
|
||||
<http://ibmdecisionoptimization.github.io/docplex-doc/>`__ is a library for prescriptive
|
||||
analysis. Like CPLEX, it is required for the :class:`.BIPMapping` transpiler pass.
|
||||
analysis. Like CPLEX, this is no longer by Qiskit, but it weas historically and the
|
||||
optional remains for backwards compatibility.
|
||||
|
||||
* - .. py:data:: HAS_FIXTURES
|
||||
- The test suite has additional features that are available if the optional `fixtures
|
||||
|
@ -246,13 +248,13 @@ HAS_CONSTRAINT = _LazyImportTester(
|
|||
|
||||
HAS_CPLEX = _LazyImportTester(
|
||||
"cplex",
|
||||
install="pip install 'qiskit-terra[bip-mapper]'",
|
||||
install="pip install cplex",
|
||||
msg="This may not be possible for all Python versions and OSes",
|
||||
)
|
||||
HAS_CVXPY = _LazyImportTester("cvxpy", install="pip install cvxpy")
|
||||
HAS_DOCPLEX = _LazyImportTester(
|
||||
{"docplex": (), "docplex.mp.model": ("Model",)},
|
||||
install="pip install 'qiskit-terra[bip-mapper]'",
|
||||
install="pip install docplex",
|
||||
msg="This may not be possible for all Python versions and OSes",
|
||||
)
|
||||
HAS_FIXTURES = _LazyImportTester("fixtures", install="pip install fixtures")
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
upgrade:
|
||||
- |
|
||||
The deprecated transpiler routing pass, ``BIPMapping`` has been removed.
|
||||
It was marked as deprecated in the Qiskit 0.43.0 release. It has been
|
||||
replaced by an external plugin package: ``qiskit-bip-mapper``. Details for
|
||||
this new package can be found at the package's github repository:
|
||||
|
||||
https://github.com/qiskit-community/qiskit-bip-mapper
|
||||
|
||||
The pass was made into a separate plugin package for two reasons, first
|
||||
the dependency on CPLEX makes it harder to use and secondly the plugin
|
||||
package more cleanly integrates with :func:`~.transpile`. The optional
|
||||
extra ``bip-mapper`` to install the ``cplex`` and ``docplex`` to support
|
||||
this pass has been removed as nothing in Qiskit optionally requires it anymore.
|
|
@ -1,379 +0,0 @@
|
|||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2021.
|
||||
#
|
||||
# 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 BIPMapping pass"""
|
||||
|
||||
import unittest
|
||||
|
||||
from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister
|
||||
from qiskit.circuit import Barrier
|
||||
from qiskit.circuit.library.standard_gates import SwapGate, CXGate
|
||||
from qiskit.converters import circuit_to_dag
|
||||
from qiskit.test import QiskitTestCase
|
||||
from qiskit.providers.fake_provider import FakeLima
|
||||
from qiskit.transpiler import CouplingMap, Layout, PassManager, Target
|
||||
from qiskit.transpiler.exceptions import TranspilerError
|
||||
from qiskit.transpiler.passes import BIPMapping
|
||||
from qiskit.transpiler.passes import CheckMap, Collect2qBlocks, ConsolidateBlocks, UnitarySynthesis
|
||||
from qiskit.utils import optionals
|
||||
|
||||
|
||||
@unittest.skipUnless(optionals.HAS_CPLEX, "cplex is required to run the BIPMapping tests")
|
||||
@unittest.skipUnless(optionals.HAS_DOCPLEX, "docplex is required to run the BIPMapping tests")
|
||||
class TestBIPMapping(QiskitTestCase):
|
||||
"""Tests the BIPMapping pass."""
|
||||
|
||||
def test_empty(self):
|
||||
"""Returns the original circuit if the circuit is empty."""
|
||||
coupling = CouplingMap([[0, 1]])
|
||||
circuit = QuantumCircuit(2)
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling)(circuit)
|
||||
self.assertEqual(circuit, actual)
|
||||
|
||||
def test_no_two_qubit_gates(self):
|
||||
"""Returns the original circuit if the circuit has no 2q-gates
|
||||
q0:--[H]--
|
||||
q1:-------
|
||||
CouplingMap map: [0]--[1]
|
||||
"""
|
||||
coupling = CouplingMap([[0, 1]])
|
||||
|
||||
circuit = QuantumCircuit(2)
|
||||
circuit.h(0)
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling)(circuit)
|
||||
|
||||
self.assertEqual(circuit, actual)
|
||||
|
||||
def test_trivial_case(self):
|
||||
"""No need to have any swap, the CX are distance 1 to each other
|
||||
q0:--(+)-[H]-(+)-
|
||||
| |
|
||||
q1:---.-------|--
|
||||
|
|
||||
q2:-----------.--
|
||||
CouplingMap map: [1]--[0]--[2]
|
||||
"""
|
||||
coupling = CouplingMap([[0, 1], [0, 2]])
|
||||
|
||||
circuit = QuantumCircuit(3)
|
||||
circuit.cx(1, 0)
|
||||
circuit.h(0)
|
||||
circuit.cx(2, 0)
|
||||
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling)(circuit)
|
||||
self.assertEqual(3, len(actual))
|
||||
for inst, _, _ in actual.data: # there are no swaps
|
||||
self.assertFalse(isinstance(inst, SwapGate))
|
||||
|
||||
def test_no_swap(self):
|
||||
"""Adding no swap if not giving initial layout"""
|
||||
coupling = CouplingMap([[0, 1], [0, 2]])
|
||||
|
||||
circuit = QuantumCircuit(3)
|
||||
circuit.cx(1, 2)
|
||||
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling)(circuit)
|
||||
|
||||
q = QuantumRegister(3, name="q")
|
||||
expected = QuantumCircuit(q)
|
||||
expected.cx(q[0], q[1])
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_ignore_initial_layout(self):
|
||||
"""Ignoring initial layout even when it is supplied"""
|
||||
coupling = CouplingMap([[0, 1], [0, 2]])
|
||||
|
||||
circuit = QuantumCircuit(3)
|
||||
circuit.cx(1, 2)
|
||||
|
||||
property_set = {"layout": Layout.generate_trivial_layout(*circuit.qubits)}
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling)(circuit, property_set)
|
||||
|
||||
q = QuantumRegister(3, name="q")
|
||||
expected = QuantumCircuit(q)
|
||||
expected.cx(q[0], q[1])
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_can_map_measurements_correctly(self):
|
||||
"""Verify measurement nodes are updated to map correct cregs to re-mapped qregs."""
|
||||
coupling = CouplingMap([[0, 1], [0, 2]])
|
||||
|
||||
qr = QuantumRegister(3, "qr")
|
||||
cr = ClassicalRegister(2)
|
||||
circuit = QuantumCircuit(qr, cr)
|
||||
circuit.cx(qr[1], qr[2])
|
||||
circuit.measure(qr[1], cr[0])
|
||||
circuit.measure(qr[2], cr[1])
|
||||
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling)(circuit)
|
||||
|
||||
q = QuantumRegister(3, "q")
|
||||
expected = QuantumCircuit(q, cr)
|
||||
expected.cx(q[0], q[1])
|
||||
expected.measure(q[0], cr[0]) # <- changed due to initial layout change
|
||||
expected.measure(q[1], cr[1]) # <- changed due to initial layout change
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_can_map_measurements_correctly_with_target(self):
|
||||
"""Verify measurement nodes are updated to map correct cregs to re-mapped qregs."""
|
||||
target = Target()
|
||||
target.add_instruction(CXGate(), {(0, 1): None, (0, 2): None})
|
||||
|
||||
qr = QuantumRegister(3, "qr")
|
||||
cr = ClassicalRegister(2)
|
||||
circuit = QuantumCircuit(qr, cr)
|
||||
circuit.cx(qr[1], qr[2])
|
||||
circuit.measure(qr[1], cr[0])
|
||||
circuit.measure(qr[2], cr[1])
|
||||
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(target)(circuit)
|
||||
|
||||
q = QuantumRegister(3, "q")
|
||||
expected = QuantumCircuit(q, cr)
|
||||
expected.cx(q[0], q[1])
|
||||
expected.measure(q[0], cr[0]) # <- changed due to initial layout change
|
||||
expected.measure(q[1], cr[1]) # <- changed due to initial layout change
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_never_modify_mapped_circuit(self):
|
||||
"""Test that the mapping is idempotent.
|
||||
It should not modify a circuit which is already compatible with the
|
||||
coupling map, and can be applied repeatedly without modifying the circuit.
|
||||
"""
|
||||
coupling = CouplingMap([[0, 1], [0, 2]])
|
||||
|
||||
circuit = QuantumCircuit(3, 2)
|
||||
circuit.cx(1, 2)
|
||||
circuit.measure(1, 0)
|
||||
circuit.measure(2, 1)
|
||||
dag = circuit_to_dag(circuit)
|
||||
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
mapped_dag = BIPMapping(coupling).run(dag)
|
||||
remapped_dag = BIPMapping(coupling).run(mapped_dag)
|
||||
|
||||
self.assertEqual(mapped_dag, remapped_dag)
|
||||
|
||||
def test_no_swap_multi_layer(self):
|
||||
"""Can find the best layout for a circuit with multiple layers."""
|
||||
coupling = CouplingMap([[0, 1], [1, 2], [2, 3]])
|
||||
|
||||
qr = QuantumRegister(4, name="qr")
|
||||
circuit = QuantumCircuit(qr)
|
||||
circuit.cx(qr[1], qr[0])
|
||||
circuit.cx(qr[0], qr[3])
|
||||
|
||||
property_set = {}
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling, objective="depth")(circuit, property_set)
|
||||
self.assertEqual(2, actual.depth())
|
||||
|
||||
CheckMap(coupling)(actual, property_set)
|
||||
self.assertTrue(property_set["is_swap_mapped"])
|
||||
|
||||
def test_unmappable_cnots_in_a_layer(self):
|
||||
"""Test mapping of a circuit with 2 cnots in a layer into T-shape coupling,
|
||||
which BIPMapping cannot map."""
|
||||
qr = QuantumRegister(4, "q")
|
||||
cr = ClassicalRegister(4, "c")
|
||||
circuit = QuantumCircuit(qr, cr)
|
||||
circuit.cx(qr[0], qr[1])
|
||||
circuit.cx(qr[2], qr[3])
|
||||
circuit.measure(qr, cr)
|
||||
|
||||
coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]}
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling)(circuit)
|
||||
|
||||
# Fails to map and returns the original circuit
|
||||
self.assertEqual(circuit, actual)
|
||||
|
||||
def test_multi_cregs(self):
|
||||
"""Test for multiple ClassicalRegisters."""
|
||||
|
||||
# ┌───┐ ░ ┌─┐
|
||||
# qr_0: ──■────────────┤ X ├─░─┤M├─────────
|
||||
# ┌─┴─┐ ┌───┐└─┬─┘ ░ └╥┘┌─┐
|
||||
# qr_1: ┤ X ├──■──┤ H ├──■───░──╫─┤M├──────
|
||||
# └───┘┌─┴─┐└───┘ ░ ║ └╥┘┌─┐
|
||||
# qr_2: ──■──┤ X ├───────────░──╫──╫─┤M├───
|
||||
# ┌─┴─┐└───┘ ░ ║ ║ └╥┘┌─┐
|
||||
# qr_3: ┤ X ├────────────────░──╫──╫──╫─┤M├
|
||||
# └───┘ ░ ║ ║ ║ └╥┘
|
||||
# c: 2/════════════════════════╩══╬══╩══╬═
|
||||
# 0 ║ 1 ║
|
||||
# ║ ║
|
||||
# d: 2/═══════════════════════════╩═════╩═
|
||||
# 0 1
|
||||
qr = QuantumRegister(4, "qr")
|
||||
cr1 = ClassicalRegister(2, "c")
|
||||
cr2 = ClassicalRegister(2, "d")
|
||||
circuit = QuantumCircuit(qr, cr1, cr2)
|
||||
circuit.cx(qr[0], qr[1])
|
||||
circuit.cx(qr[2], qr[3])
|
||||
circuit.cx(qr[1], qr[2])
|
||||
circuit.h(qr[1])
|
||||
circuit.cx(qr[1], qr[0])
|
||||
circuit.barrier(qr)
|
||||
circuit.measure(qr[0], cr1[0])
|
||||
circuit.measure(qr[1], cr2[0])
|
||||
circuit.measure(qr[2], cr1[1])
|
||||
circuit.measure(qr[3], cr2[1])
|
||||
|
||||
coupling = CouplingMap([[0, 1], [0, 2], [2, 3]]) # linear [1, 0, 2, 3]
|
||||
property_set = {}
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling, objective="depth")(circuit, property_set)
|
||||
self.assertEqual(5, actual.depth())
|
||||
|
||||
CheckMap(coupling)(actual, property_set)
|
||||
self.assertTrue(property_set["is_swap_mapped"])
|
||||
|
||||
def test_swaps_in_dummy_steps(self):
|
||||
"""Test the case when swaps are inserted in dummy steps."""
|
||||
|
||||
# ┌───┐ ░ ░
|
||||
# q_0: ──■──┤ H ├─░───■────────░───■───────
|
||||
# ┌─┴─┐├───┤ ░ │ ░ │
|
||||
# q_1: ┤ X ├┤ H ├─░───┼────■───░───┼────■──
|
||||
# └───┘├───┤ ░ │ ┌─┴─┐ ░ ┌─┴─┐ │
|
||||
# q_2: ──■──┤ H ├─░───┼──┤ X ├─░─┤ X ├──┼──
|
||||
# ┌─┴─┐├───┤ ░ ┌─┴─┐└───┘ ░ └───┘┌─┴─┐
|
||||
# q_3: ┤ X ├┤ H ├─░─┤ X ├──────░──────┤ X ├
|
||||
# └───┘└───┘ ░ └───┘ ░ └───┘
|
||||
circuit = QuantumCircuit(4)
|
||||
circuit.cx(0, 1)
|
||||
circuit.cx(2, 3)
|
||||
circuit.h([0, 1, 2, 3])
|
||||
circuit.barrier()
|
||||
circuit.cx(0, 3)
|
||||
circuit.cx(1, 2)
|
||||
circuit.barrier()
|
||||
circuit.cx(0, 2)
|
||||
circuit.cx(1, 3)
|
||||
|
||||
coupling = CouplingMap.from_line(4)
|
||||
property_set = {}
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling, objective="depth")(circuit, property_set)
|
||||
self.assertEqual(7, actual.depth())
|
||||
|
||||
CheckMap(coupling)(actual, property_set)
|
||||
self.assertTrue(property_set["is_swap_mapped"])
|
||||
|
||||
# no swaps before the first barrier
|
||||
for inst, _, _ in actual.data:
|
||||
if isinstance(inst, Barrier):
|
||||
break
|
||||
self.assertFalse(isinstance(inst, SwapGate))
|
||||
|
||||
def test_different_number_of_virtual_and_physical_qubits(self):
|
||||
"""Test the case when number of virtual and physical qubits are different."""
|
||||
|
||||
# q_0: ──■────■───────
|
||||
# ┌─┴─┐ │
|
||||
# q_1: ┤ X ├──┼────■──
|
||||
# └───┘ │ ┌─┴─┐
|
||||
# q_2: ──■────┼──┤ X ├
|
||||
# ┌─┴─┐┌─┴─┐└───┘
|
||||
# q_3: ┤ X ├┤ X ├─────
|
||||
# └───┘└───┘
|
||||
circuit = QuantumCircuit(4)
|
||||
circuit.cx(0, 1)
|
||||
circuit.cx(2, 3)
|
||||
circuit.cx(0, 3)
|
||||
circuit.cx(1, 2)
|
||||
|
||||
coupling = CouplingMap.from_line(5)
|
||||
with self.assertRaises(TranspilerError):
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
BIPMapping(coupling)(circuit)
|
||||
|
||||
def test_qubit_subset(self):
|
||||
"""Test if `qubit_subset` option works as expected."""
|
||||
circuit = QuantumCircuit(3)
|
||||
circuit.cx(0, 1)
|
||||
circuit.cx(1, 2)
|
||||
circuit.cx(0, 2)
|
||||
|
||||
coupling = CouplingMap([(0, 1), (1, 3), (3, 2)])
|
||||
qubit_subset = [0, 1, 3]
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
actual = BIPMapping(coupling, qubit_subset=qubit_subset)(circuit)
|
||||
# all used qubits are in qubit_subset
|
||||
bit_indices = {bit: index for index, bit in enumerate(actual.qubits)}
|
||||
for _, qargs, _ in actual.data:
|
||||
for q in qargs:
|
||||
self.assertTrue(bit_indices[q] in qubit_subset)
|
||||
# ancilla qubits are set in the resulting qubit
|
||||
idle = QuantumRegister(1, name="ancilla")
|
||||
self.assertEqual(idle[0], actual._layout.initial_layout[2])
|
||||
|
||||
def test_unconnected_qubit_subset(self):
|
||||
"""Fails if qubits in `qubit_subset` are not connected."""
|
||||
circuit = QuantumCircuit(3)
|
||||
circuit.cx(0, 1)
|
||||
|
||||
coupling = CouplingMap([(0, 1), (1, 3), (3, 2)])
|
||||
with self.assertRaises(TranspilerError):
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
BIPMapping(coupling, qubit_subset=[0, 1, 2])(circuit)
|
||||
|
||||
def test_objective_function(self):
|
||||
"""Test if ``objective`` functions prioritize metrics correctly."""
|
||||
|
||||
# ┌──────┐┌──────┐ ┌──────┐
|
||||
# q_0: ┤0 ├┤0 ├─────┤0 ├
|
||||
# │ Dcx ││ │ │ Dcx │
|
||||
# q_1: ┤1 ├┤ Dcx ├──■──┤1 ├
|
||||
# └──────┘│ │ │ └──────┘
|
||||
# q_2: ───■────┤1 ├──┼─────■────
|
||||
# ┌─┴─┐ └──────┘┌─┴─┐ ┌─┴─┐
|
||||
# q_3: ─┤ X ├──────────┤ X ├─┤ X ├──
|
||||
# └───┘ └───┘ └───┘
|
||||
qc = QuantumCircuit(4)
|
||||
qc.dcx(0, 1)
|
||||
qc.cx(2, 3)
|
||||
qc.dcx(0, 2)
|
||||
qc.cx(1, 3)
|
||||
qc.dcx(0, 1)
|
||||
qc.cx(2, 3)
|
||||
coupling = CouplingMap(FakeLima().configuration().coupling_map)
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
dep_opt = BIPMapping(coupling, objective="depth", qubit_subset=[0, 1, 3, 4])(qc)
|
||||
with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
|
||||
err_opt = BIPMapping(
|
||||
coupling,
|
||||
objective="gate_error",
|
||||
qubit_subset=[0, 1, 3, 4],
|
||||
backend_prop=FakeLima().properties(),
|
||||
)(qc)
|
||||
# depth = number of su4 layers (mirrored gates have to be consolidated as single su4 gates)
|
||||
pm_ = PassManager([Collect2qBlocks(), ConsolidateBlocks(basis_gates=["cx", "u"])])
|
||||
dep_opt = pm_.run(dep_opt)
|
||||
err_opt = pm_.run(err_opt)
|
||||
self.assertLessEqual(dep_opt.depth(), err_opt.depth())
|
||||
# count CNOTs after synthesized
|
||||
dep_opt = UnitarySynthesis(basis_gates=["cx", "u"])(dep_opt)
|
||||
err_opt = UnitarySynthesis(basis_gates=["cx", "u"])(err_opt)
|
||||
self.assertGreater(dep_opt.count_ops()["cx"], err_opt.count_ops()["cx"])
|
Loading…
Reference in New Issue