Start making the transpiler Target aware (#7227)

* Start making the BasisTranslator Target/BackendV2 aware

In #5885 we added the next version of the abstract Backend interface
BackendV2 which added the concept of a Target which represents a
compiler target for the transpiler. It contains all the information
about the constraints of a backend for the compiler to use and replaces
the combination of basis_gates, coupling_map, etc and expands the
representation to model more complex devices. However, in #5885 we only
introduced the interface and didn't modify the transpiler to use the
Target natively or any of the extra information it contains. This commit
is the start of the process of updated the transpiler to work with a
target natively. To start if a backend has a target that is now passed
through from transpile to the passmanager_config so we can start passing
it directly to passes as we enable it. Then the basis translator is
updated to work natively with a target instead of the basis gates list
it used before. In addition to using a target directly support is added
for heterogeneous gate sets so that target instructions can work on only
a subset of qargs.

Building off this in the future There are additional features in target
that we might want to expand support for in the BasisTranslator in the
future, such as supporting custom variants of the same gate, or handling
fixed angle rotation gate variants, etc.

* Deduplicate computation of non-global operations

The modifications to the BasisTranslator needed to compute operations in
the basis which weren't global for a particular target and accomplished
this via a dedicated helper function in the pass module. However the
BackendV2 class had an identical function for accomplishing this so it
could log a warning when basis gates were queried. This commit
deduplicates them and creates a dedicated method on the target for
returning the list (and caches it so it's only computed once per target).

* Add missing check for a dag calibration on the target path

* Rename extra_basis_transforms -> qarg_local_basis_transforms

* Make GateDirection and CheckGateDirection Target aware too

In debugging the basis translator changes we realized that we should be
relying on the GateDirection pass to fix directionality on non-symmetric
2q gates instead of trying to handle it natively in the basis translator
for now. To do this with a target we need to make the GateDirection and
CheckGateDirection passes target aware and update the basis translator
to treat all 2q gates as symmetric. This commit makes this change and
updates the preset pass managers to pass targets to GateDirection and
CheckGateDirection (also updates the rule determining if we need to run
them to leverage the target not the coupling map).

* Handle working with a non-global 1q basis

In the case of a non-global 1q basis where there are 1q gates only
available on a subset of qubits the basis translator was not correctly
able to translate multi-qubit gates. This was due to how the local basis
search was done just on the global target basis gates and the local
target basis gates for that argument, with multi-qubit gates the
translations also typically involve a 1q rotation. But if there are no
global 1q gates those rotation gates can't be translated and the search
fails. To address this the 1q local gates for the individual qubits in
the multi-qubit argument are added to the local search for non-global
multi-qubit gates.

* Also use target for gate direction in level 0

* Add release notes

* Consider all non-local subset operations for multiqubit non local gates

* Update qiskit/providers/backend.py

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* Finish incomplete strict_direction docstring

* Adjust tests to be fp precision tolerant

* Relax tests further for windows

* Update qiskit/transpiler/target.py

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* Correct detection of non-global ops with strict_direction=False

Co-authored-by: georgios-ts <45130028+georgios-ts@users.noreply.github.com>

* Fix handling of non-local subset searching

* Simplify target path in gate direction pass

* Rename extra_source_basis -> local_source_basis

* Rename incomplete_basis -> non_global_operations

* Rename qarg_with_incomplete -> qargs_with_non_global_operation and incomplete_source_basis -> qargs_local_source_basis

* Update target handling in transpile()

This fixes 2 issues found in review with the the transpile() functions
handling of target. First if a target kwarg is passed by a user that
target will be used instead of the backend for not just the target but
also all derived quantities (such as coupling_map and basis_gates).
Also previously the backend_properties field was not being correctly
built from a target (both for a backendv2 or a standalone target). So a
converter helper function was added to go from a target and build a
BackendPropeties object from the data contained in the target. This will
enable noise aware transpilation until the transpiler passes are all
target aware.

* Calculate qargs with non global operation once per pass instance

* Expand docstring to include non-global operation behavior description

* Add test assertion that it matches the target

* Add GateDirection to the optimization loop for level 3

Optimization level 3 is different from the other lower optimization
levels because it uses unitary synthesis by default. The unitary
synthesis pass is basis and coupling map to optimize the synthesized
circuit for a unitary to be hardware efficient. However when using a
target the basis gates and coupling map don't give a complete picture of
the device constraints (mainly the non-global gates if any). To account
for this we need to run the gate direction pass after unitary synthesis
to correct an incorrect decision the unitary synthesis pass might make
based on it's incomplete data. Once UnitarySynthesis is target aware we
probably do not require this anymore.

* Add tests of 5q ghz on non-linear non-global target with different entangling gates

* Use frozenset instead of tuple for local search and transformation tracking

* Fix copy paste error for gate error in _target_to_backend_properties

* Apply suggestions from code review

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* Only run gate direction in level 3 with a target

* Move target overrides inside _parse_transpile_args()

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>
Co-authored-by: georgios-ts <45130028+georgios-ts@users.noreply.github.com>
This commit is contained in:
Matthew Treinish 2021-12-03 19:24:52 -05:00 committed by GitHub
parent 37684d48a7
commit 1a9f93804f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1460 additions and 131 deletions

View File

@ -11,6 +11,7 @@
# that they have been altered from the originals.
"""Circuit transpile function"""
import datetime
import logging
import warnings
from time import time
@ -40,6 +41,7 @@ from qiskit.transpiler.preset_passmanagers import (
level_3_pass_manager,
)
from qiskit.transpiler.timing_constraints import TimingConstraints
from qiskit.transpiler.target import Target
logger = logging.getLogger(__name__)
@ -67,6 +69,7 @@ def transpile(
output_name: Optional[Union[str, List[str]]] = None,
unitary_synthesis_method: str = "default",
unitary_synthesis_plugin_config: dict = None,
target: Target = None,
) -> Union[QuantumCircuit, List[QuantumCircuit]]:
"""Transpile one or more circuits, according to some desired transpilation targets.
@ -229,7 +232,10 @@ def transpile(
the ``unitary_synthesis`` argument. As this is custom for each
unitary synthesis plugin refer to the plugin documentation for how
to use this option.
target: A backend transpiler target. Normally this is specified as part of
the ``backend`` argument, but if you have manually constructed a
:class:`~qiskit.transpiler.Target` object you can specify it manually here.
This will override the target from ``backend``.
Returns:
The transpiled circuit(s).
@ -268,6 +274,7 @@ def transpile(
approximation_degree=approximation_degree,
unitary_synthesis_method=unitary_synthesis_method,
backend=backend,
target=target,
)
warnings.warn(
@ -284,7 +291,12 @@ def transpile(
config = user_config.get_config()
optimization_level = config.get("transpile_optimization_level", 1)
if scheduling_method is not None and backend is None and not instruction_durations:
if (
scheduling_method is not None
and backend is None
and target is None
and not instruction_durations
):
warnings.warn(
"When scheduling circuits without backend,"
" 'instruction_durations' should be usually provided.",
@ -314,6 +326,7 @@ def transpile(
timing_constraints,
unitary_synthesis_method,
unitary_synthesis_plugin_config,
target,
)
_check_circuits_coupling_map(circuits, transpile_args, backend)
@ -505,6 +518,7 @@ def _parse_transpile_args(
timing_constraints,
unitary_synthesis_method,
unitary_synthesis_plugin_config,
target,
) -> List[Dict]:
"""Resolve the various types of args allowed to the transpile() function through
duck typing, overriding args, etc. Refer to the transpile() docstring for details on
@ -526,6 +540,24 @@ def _parse_transpile_args(
# number of circuits. If single, duplicate to create a list of that size.
num_circuits = len(circuits)
# If a target is specified have it override any implicit selections from a backend
# but if an argument is explicitly passed use that instead of the target version
if target is not None:
if coupling_map is None:
coupling_map = target.coupling_map()
if basis_gates is None:
basis_gates = target.operation_names()
if instruction_durations is None:
instruction_durations = target.durations()
if inst_map is None:
inst_map = target.instruction_schedule_map()
if dt is None:
dt = target.dt
if timing_constraints is None:
timing_constraints = target.timing_constraints()
if backend_properties is None:
backend_properties = _target_to_backend_properties(target)
basis_gates = _parse_basis_gates(basis_gates, backend, circuits)
inst_map = _parse_inst_map(inst_map, backend, num_circuits)
faulty_qubits_map = _parse_faulty_qubits_map(backend, num_circuits)
@ -550,6 +582,7 @@ def _parse_transpile_args(
durations = _parse_instruction_durations(backend, instruction_durations, dt, circuits)
scheduling_method = _parse_scheduling_method(scheduling_method, num_circuits)
timing_constraints = _parse_timing_constraints(backend, timing_constraints, num_circuits)
target = _parse_target(backend, target, num_circuits)
if scheduling_method and any(d is None for d in durations):
raise TranspilerError(
"Transpiling a circuit with a scheduling method"
@ -579,6 +612,7 @@ def _parse_transpile_args(
"faulty_qubits_map": faulty_qubits_map,
"unitary_synthesis_method": unitary_synthesis_method,
"unitary_synthesis_plugin_config": unitary_synthesis_plugin_config,
"target": target,
}
):
transpile_args = {
@ -598,6 +632,7 @@ def _parse_transpile_args(
seed_transpiler=kwargs["seed_transpiler"],
unitary_synthesis_method=kwargs["unitary_synthesis_method"],
unitary_synthesis_plugin_config=kwargs["unitary_synthesis_plugin_config"],
target=kwargs["target"],
),
"optimization_level": kwargs["optimization_level"],
"output_name": kwargs["output_name"],
@ -726,38 +761,122 @@ def _parse_coupling_map(coupling_map, backend, num_circuits):
return coupling_map
def _target_to_backend_properties(target: Target):
properties_dict = {
"backend_name": "",
"backend_version": "",
"last_update_date": None,
"general": [],
}
gates = []
qubits = []
for gate, qargs_list in target.items():
if gate != "measure":
for qargs, props in qargs_list.items():
property_list = []
if props is not None:
if props.duration is not None:
property_list.append(
{
"date": datetime.datetime.utcnow(),
"name": "gate_length",
"unit": "s",
"value": props.duration,
}
)
if props.error is not None:
property_list.append(
{
"date": datetime.datetime.utcnow(),
"name": "gate_error",
"unit": "",
"value": props.error,
}
)
if property_list:
gates.append(
{
"gate": gate,
"qubits": list(qargs),
"parameters": property_list,
"name": gate + "_".join([str(x) for x in qargs]),
}
)
else:
qubit_props = {x: None for x in range(target.num_qubits)}
for qargs, props in qargs_list.items():
qubit = qargs[0]
props_list = []
if props.error is not None:
props_list.append(
{
"date": datetime.datetime.utcnow(),
"name": "readout_error",
"unit": "",
"value": props.error,
}
)
if props.duration is not None:
props_list.append(
{
"date": datetime.datetime.utcnow(),
"name": "readout_length",
"unit": "s",
"value": props.duration,
}
)
if not props_list:
qubit_props = {}
break
qubit_props[qubit] = props_list
if qubit_props and all(x is not None for x in qubit_props.values()):
qubits = [qubit_props[i] for i in range(target.num_qubits)]
if gates or qubits:
properties_dict["gates"] = gates
properties_dict["qubits"] = qubits
return BackendProperties.from_dict(properties_dict)
else:
return None
def _parse_backend_properties(backend_properties, backend, num_circuits):
# try getting backend_properties from user, else backend
if backend_properties is None:
if getattr(backend, "properties", None):
backend_properties = backend.properties()
if backend_properties and (
backend_properties.faulty_qubits() or backend_properties.faulty_gates()
):
faulty_qubits = sorted(backend_properties.faulty_qubits(), reverse=True)
faulty_edges = [gates.qubits for gates in backend_properties.faulty_gates()]
# remove faulty qubits in backend_properties.qubits
for faulty_qubit in faulty_qubits:
del backend_properties.qubits[faulty_qubit]
backend_version = getattr(backend, "version", None)
if not isinstance(backend_version, int):
backend_version = 0
if backend_version <= 1:
if getattr(backend, "properties", None):
backend_properties = backend.properties()
if backend_properties and (
backend_properties.faulty_qubits() or backend_properties.faulty_gates()
):
faulty_qubits = sorted(backend_properties.faulty_qubits(), reverse=True)
faulty_edges = [gates.qubits for gates in backend_properties.faulty_gates()]
# remove faulty qubits in backend_properties.qubits
for faulty_qubit in faulty_qubits:
del backend_properties.qubits[faulty_qubit]
gates = []
for gate in backend_properties.gates:
# remove gates using faulty edges or with faulty qubits (and remap the
# gates in terms of faulty_qubits_map)
faulty_qubits_map = _create_faulty_qubits_map(backend)
if (
any(faulty_qubits_map[qubits] is not None for qubits in gate.qubits)
or gate.qubits in faulty_edges
):
continue
gate_dict = gate.to_dict()
replacement_gate = Gate.from_dict(gate_dict)
gate_dict["qubits"] = [faulty_qubits_map[qubit] for qubit in gate.qubits]
args = "_".join([str(qubit) for qubit in gate_dict["qubits"]])
gate_dict["name"] = "{}{}".format(gate_dict["gate"], args)
gates.append(replacement_gate)
gates = []
for gate in backend_properties.gates:
# remove gates using faulty edges or with faulty qubits (and remap the
# gates in terms of faulty_qubits_map)
faulty_qubits_map = _create_faulty_qubits_map(backend)
if (
any(faulty_qubits_map[qubits] is not None for qubits in gate.qubits)
or gate.qubits in faulty_edges
):
continue
gate_dict = gate.to_dict()
replacement_gate = Gate.from_dict(gate_dict)
gate_dict["qubits"] = [faulty_qubits_map[qubit] for qubit in gate.qubits]
args = "_".join([str(qubit) for qubit in gate_dict["qubits"]])
gate_dict["name"] = "{}{}".format(gate_dict["gate"], args)
gates.append(replacement_gate)
backend_properties.gates = gates
backend_properties.gates = gates
else:
backend_properties = _target_to_backend_properties(backend.target)
if not isinstance(backend_properties, list):
backend_properties = [backend_properties] * num_circuits
return backend_properties
@ -898,6 +1017,15 @@ def _parse_unitary_plugin_config(unitary_synthesis_plugin_config, num_circuits):
return unitary_synthesis_plugin_config
def _parse_target(backend, target, num_circuits):
backend_target = getattr(backend, "target", None)
if target is None:
target = backend_target
if not isinstance(target, list):
target = [target] * num_circuits
return target
def _parse_seed_transpiler(seed_transpiler, num_circuits):
if not isinstance(seed_transpiler, list):
seed_transpiler = [seed_transpiler] * num_circuits

View File

@ -17,7 +17,6 @@
from abc import ABC
from abc import abstractmethod
from collections import defaultdict
import datetime
import logging
from typing import List, Union, Iterable, Tuple
@ -334,7 +333,6 @@ class BackendV2(Backend, ABC):
if field not in self._options.data:
raise AttributeError("Options field %s is not valid for this backend" % field)
self._options.update_config(**fields)
self._basis_gates_all = None
self.name = name
self.description = description
self.online_date = online_date
@ -350,29 +348,12 @@ class BackendV2(Backend, ABC):
"""A list of :class:`~qiskit.circuit.Instruction` instances that the backend supports."""
return list(self.target.operations)
def _compute_non_global_basis(self):
incomplete_basis_gates = []
size_dict = defaultdict(int)
size_dict[1] = self.target.num_qubits
for qarg in self.target.qargs:
if len(qarg) == 1:
continue
size_dict[len(qarg)] += 1
for inst, qargs in self.target.items():
qarg_sample = next(iter(qargs))
if qarg_sample is None:
continue
if len(qargs) != size_dict[len(qarg_sample)]:
incomplete_basis_gates.append(inst)
self._basis_gates_all = incomplete_basis_gates
@property
def operation_names(self) -> List[str]:
"""A list of instruction names that the backend supports."""
if self._basis_gates_all is None:
self._compute_non_global_basis()
if self._basis_gates_all:
invalid_str = ",".join(self._basis_gates_all)
non_global_ops = self.target.get_non_global_operation_names(strict_direction=True)
if non_global_ops:
invalid_str = ",".join(non_global_ops)
msg = (
f"This backend's operations: {invalid_str} only apply to a subset of "
"qubits. Using this property to get 'basis_gates' for the "

View File

@ -23,7 +23,8 @@ The mock devices are mainly for testing the compiler.
from .fake_provider import FakeProvider, FakeLegacyProvider
from .fake_provider import FakeProviderFactory
from .fake_backend import FakeBackend, FakeLegacyBackend
from .fake_backend_v2 import FakeBackendV2
from .fake_backend_v2 import FakeBackendV2, FakeBackend5QV2
from .fake_mumbai_v2 import FakeMumbaiV2
from .fake_job import FakeJob, FakeLegacyJob
from .fake_qobj import FakeQobj

View File

@ -20,7 +20,12 @@ import numpy as np
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.measure import Measure
from qiskit.circuit.library.standard_gates import CXGate, UGate, ECRGate, RXGate
from qiskit.circuit.library.standard_gates import (
CXGate,
UGate,
ECRGate,
RXGate,
)
from qiskit.providers.backend import BackendV2, QubitProperties
from qiskit.providers.options import Options
from qiskit.transpiler import Target, InstructionProperties
@ -58,7 +63,6 @@ class FakeBackendV2(BackendV2):
self._target.add_instruction(UGate(self._theta, self._phi, self._lam), u_props)
cx_props = {
(0, 1): InstructionProperties(duration=5.23e-7, error=0.00098115),
(1, 0): InstructionProperties(duration=4.52e-7, error=0.00132115),
}
self._target.add_instruction(CXGate(), cx_props)
measure_props = {
@ -95,3 +99,80 @@ class FakeBackendV2(BackendV2):
if isinstance(qubit, int):
return self._qubit_properties[qubit]
return [self._qubit_properties[i] for i in qubit]
class FakeBackend5QV2(BackendV2):
"""A mock backend that doesn't implement run() to test compatibility with Terra internals."""
def __init__(self, bidirectional=True):
super().__init__(
None,
name="Fake5QV2",
description="A fake BackendV2 example",
online_date=datetime.datetime.utcnow(),
backend_version="0.0.1",
)
self._target = Target()
self._theta = Parameter("theta")
self._phi = Parameter("phi")
self._lam = Parameter("lambda")
u_props = {
(0,): InstructionProperties(duration=5.23e-8, error=0.00038115),
(1,): InstructionProperties(duration=4.52e-8, error=0.00032115),
(2,): InstructionProperties(duration=5.23e-8, error=0.00038115),
(3,): InstructionProperties(duration=4.52e-8, error=0.00032115),
(4,): InstructionProperties(duration=4.52e-8, error=0.00032115),
}
self._target.add_instruction(UGate(self._theta, self._phi, self._lam), u_props)
cx_props = {
(0, 1): InstructionProperties(duration=5.23e-7, error=0.00098115),
(3, 4): InstructionProperties(duration=5.23e-7, error=0.00098115),
}
if bidirectional:
cx_props[(1, 0)] = InstructionProperties(duration=6.23e-7, error=0.00099115)
cx_props[(4, 3)] = InstructionProperties(duration=7.23e-7, error=0.00099115)
self._target.add_instruction(CXGate(), cx_props)
measure_props = {
(0,): InstructionProperties(duration=6e-6, error=5e-6),
(1,): InstructionProperties(duration=1e-6, error=9e-6),
(2,): InstructionProperties(duration=6e-6, error=5e-6),
(3,): InstructionProperties(duration=1e-6, error=9e-6),
(4,): InstructionProperties(duration=1e-6, error=9e-6),
}
self._target.add_instruction(Measure(), measure_props)
ecr_props = {
(1, 2): InstructionProperties(duration=4.52e-9, error=0.0000132115),
(2, 3): InstructionProperties(duration=4.52e-9, error=0.0000132115),
}
if bidirectional:
ecr_props[(2, 1)] = InstructionProperties(duration=5.52e-9, error=0.0000232115)
ecr_props[(3, 2)] = InstructionProperties(duration=5.52e-9, error=0.0000232115)
self._target.add_instruction(ECRGate(), ecr_props)
self.options.set_validator("shots", (1, 4096))
self._qubit_properties = {
0: QubitProperties(t1=63.48783e-6, t2=112.23246e-6, frequency=5.17538e9),
1: QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9),
2: QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9),
3: QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9),
4: QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9),
}
@property
def target(self):
return self._target
@property
def max_circuits(self):
return None
@classmethod
def _default_options(cls):
return Options(shots=1024)
def run(self, run_input, **options):
raise NotImplementedError
def qubit_properties(self, qubit):
if isinstance(qubit, int):
return self._qubit_properties[qubit]
return [self._qubit_properties[i] for i in qubit]

View File

@ -0,0 +1,638 @@
# 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.
# pylint: disable=no-name-in-module,import-error
"""Mock BackendV2 object without run implemented for testing backwards compat"""
import datetime
import numpy as np
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.measure import Measure
from qiskit.circuit.reset import Reset
from qiskit.circuit.library.standard_gates import (
CXGate,
RZXGate,
XGate,
SXGate,
RZGate,
)
from qiskit.providers.backend import BackendV2, QubitProperties
from qiskit.providers.options import Options
from qiskit.transpiler import Target, InstructionProperties
class FakeMumbaiV2(BackendV2):
"""A fake mumbai backend."""
def __init__(self):
super().__init__(
name="FakeMumbaiV2",
description="A fake BackendV2 example based on IBM Mumbai",
online_date=datetime.datetime.utcnow(),
backend_version="0.0.1",
)
dt = 0.2222222222222222e-9
self._target = Target(dt=dt)
self._phi = Parameter("phi")
rz_props = {
(0,): InstructionProperties(duration=0.0, error=0),
(1,): InstructionProperties(duration=0.0, error=0),
(2,): InstructionProperties(duration=0.0, error=0),
(3,): InstructionProperties(duration=0.0, error=0),
(4,): InstructionProperties(duration=0.0, error=0),
(5,): InstructionProperties(duration=0.0, error=0),
(6,): InstructionProperties(duration=0.0, error=0),
(7,): InstructionProperties(duration=0.0, error=0),
(8,): InstructionProperties(duration=0.0, error=0),
(9,): InstructionProperties(duration=0.0, error=0),
(10,): InstructionProperties(duration=0.0, error=0),
(11,): InstructionProperties(duration=0.0, error=0),
(12,): InstructionProperties(duration=0.0, error=0),
(13,): InstructionProperties(duration=0.0, error=0),
(14,): InstructionProperties(duration=0.0, error=0),
(15,): InstructionProperties(duration=0.0, error=0),
(16,): InstructionProperties(duration=0.0, error=0),
(17,): InstructionProperties(duration=0.0, error=0),
(18,): InstructionProperties(duration=0.0, error=0),
(19,): InstructionProperties(duration=0.0, error=0),
(20,): InstructionProperties(duration=0.0, error=0),
(21,): InstructionProperties(duration=0.0, error=0),
(22,): InstructionProperties(duration=0.0, error=0),
(23,): InstructionProperties(duration=0.0, error=0),
(24,): InstructionProperties(duration=0.0, error=0),
(25,): InstructionProperties(duration=0.0, error=0),
(26,): InstructionProperties(duration=0.0, error=0),
}
self._target.add_instruction(RZGate(self._phi), rz_props)
x_props = {
(0,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00020056469709026198
),
(1,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0004387432040599484
),
(2,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002196765027963209
),
(3,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0003065541555566093
),
(4,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002402026686478811
),
(5,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002162777062721698
),
(6,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00021981280474256117
),
(7,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00018585647396926756
),
(8,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00027053333211825124
),
(9,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002603116226593832
),
(10,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00023827406030798066
),
(11,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00024856063217108685
),
(12,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002065075637361354
),
(13,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00024898181450337464
),
(14,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00017758796319636606
),
(15,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00016530893922883836
),
(16,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0003213658218204255
),
(17,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00024068450432012685
),
(18,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00026676441863976344
),
(19,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00017090891698571018
),
(20,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00021057196071004095
),
(21,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00030445404779882887
),
(22,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00019322295843406375
),
(23,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00030966037392287727
),
(24,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00023570754161126
),
(25,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00018367783963229033
),
(26,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00019630609928571516
),
}
self._target.add_instruction(XGate(), x_props)
sx_props = {
(0,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00020056469709026198
),
(1,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0004387432040599484
),
(2,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002196765027963209
),
(3,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0003065541555566093
),
(4,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002402026686478811
),
(5,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002162777062721698
),
(6,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00021981280474256117
),
(7,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00018585647396926756
),
(8,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00027053333211825124
),
(9,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002603116226593832
),
(10,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00023827406030798066
),
(11,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00024856063217108685
),
(12,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0002065075637361354
),
(13,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00024898181450337464
),
(14,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00017758796319636606
),
(15,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00016530893922883836
),
(16,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.0003213658218204255
),
(17,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00024068450432012685
),
(18,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00026676441863976344
),
(19,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00017090891698571018
),
(20,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00021057196071004095
),
(21,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00030445404779882887
),
(22,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00019322295843406375
),
(23,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00030966037392287727
),
(24,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00023570754161126
),
(25,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00018367783963229033
),
(26,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00019630609928571516
),
}
self._target.add_instruction(SXGate(), sx_props)
chunk_size = 16
cx_props = {
(0, 1): InstructionProperties(
duration=101 * chunk_size * dt, error=0.030671121181161276
),
(4, 1): InstructionProperties(
duration=70 * chunk_size * dt, error=0.014041986073052737
),
(4, 7): InstructionProperties(duration=74 * chunk_size * dt, error=0.0052040275323747),
(10, 7): InstructionProperties(
duration=92 * chunk_size * dt, error=0.005625282141655502
),
(10, 12): InstructionProperties(
duration=84 * chunk_size * dt, error=0.005771827440726435
),
(15, 12): InstructionProperties(
duration=84 * chunk_size * dt, error=0.0050335609425562755
),
(15, 18): InstructionProperties(
duration=64 * chunk_size * dt, error=0.0051374141171115495
),
(12, 13): InstructionProperties(
duration=70 * chunk_size * dt, error=0.011361175954064051
),
(13, 14): InstructionProperties(
duration=101 * chunk_size * dt, error=0.005231334872256355
),
# From FakeMumbai:
(1, 0): InstructionProperties(
duration=4.551111111111111e-07, error=0.030671121181161276
),
(1, 2): InstructionProperties(
duration=7.395555555555556e-07, error=0.03420291964205785
),
(1, 4): InstructionProperties(
duration=3.6266666666666663e-07, error=0.014041986073052737
),
(2, 1): InstructionProperties(duration=7.04e-07, error=0.03420291964205785),
(2, 3): InstructionProperties(
duration=4.266666666666666e-07, error=0.005618162036535312
),
(3, 2): InstructionProperties(
duration=3.911111111111111e-07, error=0.005618162036535312
),
(3, 5): InstructionProperties(
duration=3.5555555555555553e-07, error=0.006954580732294352
),
(5, 3): InstructionProperties(
duration=3.911111111111111e-07, error=0.006954580732294352
),
(5, 8): InstructionProperties(
duration=1.3155555555555553e-06, error=0.021905829471073668
),
(6, 7): InstructionProperties(
duration=2.4177777777777775e-07, error=0.011018069718028878
),
(7, 4): InstructionProperties(
duration=3.7688888888888884e-07, error=0.0052040275323747
),
(7, 6): InstructionProperties(
duration=2.7733333333333333e-07, error=0.011018069718028878
),
(7, 10): InstructionProperties(
duration=4.337777777777778e-07, error=0.005625282141655502
),
(8, 5): InstructionProperties(
duration=1.351111111111111e-06, error=0.021905829471073668
),
(8, 9): InstructionProperties(
duration=6.897777777777777e-07, error=0.011889378687341773
),
(8, 11): InstructionProperties(
duration=5.902222222222222e-07, error=0.009523844852027258
),
(9, 8): InstructionProperties(
duration=6.542222222222222e-07, error=0.011889378687341773
),
(11, 8): InstructionProperties(
duration=6.257777777777777e-07, error=0.009523844852027258
),
(11, 14): InstructionProperties(
duration=4.053333333333333e-07, error=0.004685421425282804
),
(12, 10): InstructionProperties(
duration=3.9822222222222215e-07, error=0.005771827440726435
),
(12, 15): InstructionProperties(
duration=4.053333333333333e-07, error=0.0050335609425562755
),
(13, 12): InstructionProperties(
duration=5.831111111111111e-07, error=0.011361175954064051
),
(14, 11): InstructionProperties(
duration=3.697777777777778e-07, error=0.004685421425282804
),
(14, 13): InstructionProperties(
duration=3.5555555555555553e-07, error=0.005231334872256355
),
(14, 16): InstructionProperties(
duration=3.484444444444444e-07, error=0.0051117141032224755
),
(16, 14): InstructionProperties(
duration=3.1288888888888885e-07, error=0.0051117141032224755
),
(16, 19): InstructionProperties(
duration=7.537777777777777e-07, error=0.013736796355458464
),
(17, 18): InstructionProperties(
duration=2.488888888888889e-07, error=0.007267536233537236
),
(18, 15): InstructionProperties(
duration=3.413333333333333e-07, error=0.0051374141171115495
),
(18, 17): InstructionProperties(
duration=2.8444444444444443e-07, error=0.007267536233537236
),
(18, 21): InstructionProperties(
duration=4.977777777777778e-07, error=0.007718304749257138
),
(19, 16): InstructionProperties(
duration=7.182222222222222e-07, error=0.013736796355458464
),
(19, 20): InstructionProperties(
duration=4.266666666666666e-07, error=0.005757038521092134
),
(19, 22): InstructionProperties(
duration=3.6266666666666663e-07, error=0.004661878013991871
),
(20, 19): InstructionProperties(
duration=3.911111111111111e-07, error=0.005757038521092134
),
(21, 18): InstructionProperties(
duration=5.333333333333332e-07, error=0.007718304749257138
),
(21, 23): InstructionProperties(
duration=3.911111111111111e-07, error=0.007542515578725928
),
(22, 19): InstructionProperties(
duration=3.271111111111111e-07, error=0.004661878013991871
),
(22, 25): InstructionProperties(
duration=4.835555555555555e-07, error=0.005536735115231589
),
(23, 21): InstructionProperties(
duration=4.266666666666666e-07, error=0.007542515578725928
),
(23, 24): InstructionProperties(
duration=6.613333333333332e-07, error=0.010797784688907186
),
(24, 23): InstructionProperties(
duration=6.257777777777777e-07, error=0.010797784688907186
),
(24, 25): InstructionProperties(
duration=4.337777777777778e-07, error=0.006127506135155392
),
(25, 22): InstructionProperties(duration=4.48e-07, error=0.005536735115231589),
(25, 24): InstructionProperties(
duration=4.693333333333333e-07, error=0.006127506135155392
),
(25, 26): InstructionProperties(
duration=3.484444444444444e-07, error=0.0048451525929122385
),
(26, 25): InstructionProperties(
duration=3.1288888888888885e-07, error=0.0048451525929122385
),
}
self.target.add_instruction(CXGate(), cx_props)
# Error and duration the same as CX
rzx_90_props = {
(0, 1): InstructionProperties(
duration=101 * chunk_size * dt, error=0.030671121181161276
),
(4, 1): InstructionProperties(
duration=70 * chunk_size * dt, error=0.014041986073052737
),
(4, 7): InstructionProperties(duration=74 * chunk_size * dt, error=0.0052040275323747),
(10, 7): InstructionProperties(
duration=92 * chunk_size * dt, error=0.005625282141655502
),
(10, 12): InstructionProperties(
duration=84 * chunk_size * dt, error=0.005771827440726435
),
(15, 12): InstructionProperties(
duration=84 * chunk_size * dt, error=0.0050335609425562755
),
(15, 18): InstructionProperties(
duration=64 * chunk_size * dt, error=0.0051374141171115495
),
(12, 13): InstructionProperties(
duration=70 * chunk_size * dt, error=0.011361175954064051
),
(13, 14): InstructionProperties(
duration=101 * chunk_size * dt, error=0.005231334872256355
),
}
self.target.add_instruction(RZXGate(np.pi / 2), rzx_90_props, name="rzx_90")
rzx_45_props = {
(0, 1): InstructionProperties(
duration=52 * chunk_size * dt, error=0.030671121181161276 / 2
),
(4, 1): InstructionProperties(
duration=37 * chunk_size * dt, error=0.014041986073052737 / 2
),
(4, 7): InstructionProperties(
duration=40 * chunk_size * dt, error=0.0052040275323747 / 2
),
(10, 7): InstructionProperties(
duration=46 * chunk_size * dt, error=0.005625282141655502 / 2
),
(10, 12): InstructionProperties(
duration=45 * chunk_size * dt, error=0.005771827440726435 / 2
),
(15, 12): InstructionProperties(
duration=42 * chunk_size * dt, error=0.0050335609425562755 / 2
),
(15, 18): InstructionProperties(
duration=34 * chunk_size * dt, error=0.0051374141171115495 / 2
),
(12, 13): InstructionProperties(
duration=37 * chunk_size * dt, error=0.011361175954064051 / 2
),
(13, 14): InstructionProperties(
duration=52 * chunk_size * dt, error=0.005231334872256355 / 2
),
}
self.target.add_instruction(RZXGate(np.pi / 4), rzx_45_props, name="rzx_45")
rzx_30_props = {
(0, 1): InstructionProperties(
duration=37 * chunk_size * dt, error=0.030671121181161276 / 3
),
(4, 1): InstructionProperties(
duration=24 * chunk_size * dt, error=0.014041986073052737 / 3
),
(4, 7): InstructionProperties(
duration=29 * chunk_size * dt, error=0.0052040275323747 / 3
),
(10, 7): InstructionProperties(
duration=32 * chunk_size * dt, error=0.005625282141655502 / 3
),
(10, 12): InstructionProperties(
duration=32 * chunk_size * dt, error=0.005771827440726435 / 3
),
(15, 12): InstructionProperties(
duration=29 * chunk_size * dt, error=0.0050335609425562755 / 3
),
(15, 18): InstructionProperties(
duration=26 * chunk_size * dt, error=0.0051374141171115495 / 3
),
(12, 13): InstructionProperties(
duration=24 * chunk_size * dt, error=0.011361175954064051 / 3
),
(13, 14): InstructionProperties(
duration=377 * chunk_size * dt, error=0.005231334872256355 / 3
),
}
self.target.add_instruction(RZXGate(np.pi / 6), rzx_30_props, name="rzx_30")
reset_props = {(i,): InstructionProperties(duration=3676.4444444444443) for i in range(27)}
self._target.add_instruction(Reset(), reset_props)
meas_props = {
(0,): InstructionProperties(duration=3.552e-06, error=0.02089999999999992),
(1,): InstructionProperties(duration=3.552e-06, error=0.020199999999999996),
(2,): InstructionProperties(duration=3.552e-06, error=0.014100000000000001),
(3,): InstructionProperties(duration=3.552e-06, error=0.03710000000000002),
(4,): InstructionProperties(duration=3.552e-06, error=0.015100000000000002),
(5,): InstructionProperties(duration=3.552e-06, error=0.01869999999999994),
(6,): InstructionProperties(duration=3.552e-06, error=0.013000000000000012),
(7,): InstructionProperties(duration=3.552e-06, error=0.02059999999999995),
(8,): InstructionProperties(duration=3.552e-06, error=0.06099999999999994),
(9,): InstructionProperties(duration=3.552e-06, error=0.02950000000000008),
(10,): InstructionProperties(duration=3.552e-06, error=0.040000000000000036),
(11,): InstructionProperties(duration=3.552e-06, error=0.017299999999999982),
(12,): InstructionProperties(duration=3.552e-06, error=0.04410000000000003),
(13,): InstructionProperties(duration=3.552e-06, error=0.017199999999999993),
(14,): InstructionProperties(duration=3.552e-06, error=0.10119999999999996),
(15,): InstructionProperties(duration=3.552e-06, error=0.07840000000000003),
(16,): InstructionProperties(duration=3.552e-06, error=0.014499999999999957),
(17,): InstructionProperties(duration=3.552e-06, error=0.021299999999999986),
(18,): InstructionProperties(duration=3.552e-06, error=0.022399999999999975),
(19,): InstructionProperties(duration=3.552e-06, error=0.01859999999999995),
(20,): InstructionProperties(duration=3.552e-06, error=0.02859999999999996),
(21,): InstructionProperties(duration=3.552e-06, error=0.021600000000000064),
(22,): InstructionProperties(duration=3.552e-06, error=0.030200000000000005),
(23,): InstructionProperties(duration=3.552e-06, error=0.01970000000000005),
(24,): InstructionProperties(duration=3.552e-06, error=0.03079999999999994),
(25,): InstructionProperties(duration=3.552e-06, error=0.04400000000000004),
(26,): InstructionProperties(duration=3.552e-06, error=0.026800000000000046),
}
self.target.add_instruction(Measure(), meas_props)
self._qubit_properties = {
0: QubitProperties(
t1=0.00015987993124584417, t2=0.00016123516590787283, frequency=5073462814.921423
),
1: QubitProperties(
t1=0.00017271188343294773, t2=3.653713654834547e-05, frequency=4943844681.620448
),
2: QubitProperties(
t1=7.179635917914033e-05, t2=0.00012399765778639733, frequency=4668157502.363186
),
3: QubitProperties(
t1=0.0001124203171256432, t2=0.0001879954854434302, frequency=4887315883.214115
),
4: QubitProperties(
t1=9.568769051084652e-05, t2=6.9955557231525e-05, frequency=5016355075.77537
),
5: QubitProperties(
t1=9.361326963775646e-05, t2=0.00012561361411231962, frequency=4950539585.866738
),
6: QubitProperties(
t1=9.735672898365994e-05, t2=0.00012522003396944046, frequency=4970622491.726983
),
7: QubitProperties(
t1=0.00012117839009784141, t2=0.0001492370106539427, frequency=4889863864.167805
),
8: QubitProperties(
t1=8.394707006435891e-05, t2=5.5194256398727296e-05, frequency=4769852625.405966
),
9: QubitProperties(
t1=0.00012392229685657686, t2=5.97129502818714e-05, frequency=4948868138.885028
),
10: QubitProperties(
t1=0.00011193014813922708, t2=0.00014091085124119432, frequency=4966294754.357908
),
11: QubitProperties(
t1=0.000124426408667364, t2=9.561432905002298e-05, frequency=4664636564.282378
),
12: QubitProperties(
t1=0.00012469120424014884, t2=7.1792446286313e-05, frequency=4741461907.952719
),
13: QubitProperties(
t1=0.00010010942474357871, t2=9.260751861141544e-05, frequency=4879064835.799635
),
14: QubitProperties(
t1=0.00010793367069728063, t2=0.00020462601085738193, frequency=4774809501.962878
),
15: QubitProperties(
t1=0.00010814279470918582, t2=0.00014052616328020083, frequency=4860834948.367331
),
16: QubitProperties(
t1=9.889617874757627e-05, t2=0.00012160357011388956, frequency=4978318747.333388
),
17: QubitProperties(
t1=8.435212562619916e-05, t2=4.43587633824445e-05, frequency=5000300619.491221
),
18: QubitProperties(
t1=0.00011719166507869474, t2=5.461866556148401e-05, frequency=4772460318.985625
),
19: QubitProperties(
t1=0.00013321880066203932, t2=0.0001704632622810825, frequency=4807707035.998121
),
20: QubitProperties(
t1=9.14192211953385e-05, t2=0.00014298332288799443, frequency=5045028334.669125
),
21: QubitProperties(
t1=5.548103716494676e-05, t2=9.328101902519704e-05, frequency=4941029753.792485
),
22: QubitProperties(
t1=0.00017109481586484562, t2=0.00019209594920551097, frequency=4906801587.246266
),
23: QubitProperties(
t1=0.00010975552427765991, t2=0.00015616813868639905, frequency=4891601685.652732
),
24: QubitProperties(
t1=0.0001612962696960434, t2=6.940808472789023e-05, frequency=4664347869.784967
),
25: QubitProperties(
t1=0.00015414506978323392, t2=8.382170181880107e-05, frequency=4742061753.511209
),
26: QubitProperties(
t1=0.00011828557676958944, t2=0.00016963640893557827, frequency=4961661099.733828
),
}
@property
def target(self):
return self._target
@property
def max_circuits(self):
return None
@classmethod
def _default_options(cls):
return Options(shots=1024)
def run(self, run_input, **options):
raise NotImplementedError
def qubit_properties(self, qubit):
if isinstance(qubit, int):
return self._qubit_properties[qubit]
return [self._qubit_properties[i] for i in qubit]

View File

@ -48,9 +48,36 @@ class BasisTranslator(TransformationPass):
* The composed replacement rules are applied in-place to each op node which
is not already in the target_basis.
If the target keyword argument is specified and that
:class:`~qiskit.transpiler.Target` objects contains operations
which are non-global (i.e. they are defined only for a subset of qubits),
as calculated by :meth:`~qiskit.transpiler.Target.get_non_global_operation_names`,
this pass will attempt to match the output translation to those constraints.
For 1 qubit operations this is straightforward, the pass will perform a
search using the union of the set of global operations with the set of operations
defined solely on that qubit. For multi-qubit gates this is a bit more involved,
while the behavior is initially similar to the single qubit case, just using all
the qubits the operation is run on (where order is not significant) isn't sufficient.
We also need to consider any potential local qubits defined on subsets of the
quantum arguments for the multi-qubit operation. This means the target used for the
search of a non-global multi-qubit gate is the union of global operations, non-global
multi-qubit gates sharing the same qubits, and any non-global gates defined on
any subset of the qubits used.
.. note::
In the case of non-global operations it is possible for a single
execution of this pass to output an incomplete translation if any
non-global gates are defined on qubits that are a subset of a larger
multi-qubit gate. For example, if you have a ``u`` gate only defined on
qubit 0 and an ``x`` gate only on qubit 1 it is possible when
translating a 2 qubit operation on qubit 0 and 1 that the output might
have ``u`` on qubit 1 and ``x`` on qubit 0. Typically running this pass
a second time will correct these issues.
"""
def __init__(self, equivalence_library, target_basis):
def __init__(self, equivalence_library, target_basis, target=None):
"""Initialize a BasisTranslator instance.
Args:
@ -58,12 +85,21 @@ class BasisTranslator(TransformationPass):
which will be used by the BasisTranslator pass. (Instructions in
this library will not be unrolled by this pass.)
target_basis (list[str]): Target basis names to unroll to, e.g. `['u3', 'cx']`.
target (Target): The backend compilation target
"""
super().__init__()
self._equiv_lib = equivalence_library
self._target_basis = target_basis
self._target = target
self._non_global_operations = None
self._qargs_with_non_global_operation = {} # pylint: disable=invalid-name
if target is not None:
self._non_global_operations = self._target.get_non_global_operation_names()
self._qargs_with_non_global_operation = defaultdict(set)
for gate in self._non_global_operations:
for qarg in self._target[gate]:
self._qargs_with_non_global_operation[qarg].add(gate)
def run(self, dag):
"""Translate an input DAGCircuit to the target basis.
@ -77,19 +113,46 @@ class BasisTranslator(TransformationPass):
Returns:
DAGCircuit: translated circuit.
"""
if self._target_basis is None:
if self._target_basis is None and self._target is None:
return dag
qarg_indices = {qubit: index for index, qubit in enumerate(dag.qubits)}
# Names of instructions assumed to supported by any backend.
basic_instrs = ["measure", "reset", "barrier", "snapshot", "delay"]
if self._target is None:
basic_instrs = ["measure", "reset", "barrier", "snapshot", "delay"]
target_basis = set(self._target_basis)
source_basis = set()
for node in dag.op_nodes():
if not dag.has_calibration_for(node):
source_basis.add((node.name, node.op.num_qubits))
qargs_local_source_basis = {}
else:
basic_instrs = ["barrier", "snapshot"]
source_basis = set()
target_basis = self._target.keys() - set(self._non_global_operations)
qargs_local_source_basis = defaultdict(set)
for node in dag.op_nodes():
qargs = tuple(qarg_indices[bit] for bit in node.qargs)
if dag.has_calibration_for(node):
continue
# Treat the instruction as on an incomplete basis if the qargs are in the
# qargs_with_non_global_operation dictionary or if any of the qubits in qargs
# are a superset for a non-local operation. For example, if the qargs
# are (0, 1) and that's a global (ie no non-local operations on (0, 1)
# operation but there is a non-local operation on (1,) we need to
# do an extra non-local search for this op to ensure we include any
# single qubit operation for (1,) as valid. This pattern also holds
# true for > 2q ops too (so for 4q operations we need to check for 3q, 2q,
# and 1q opertaions in the same manner)
if qargs in self._qargs_with_non_global_operation or any(
frozenset(qargs).issuperset(incomplete_qargs)
for incomplete_qargs in self._qargs_with_non_global_operation
):
qargs_local_source_basis[frozenset(qargs)].add((node.name, node.op.num_qubits))
else:
source_basis.add((node.name, node.op.num_qubits))
target_basis = set(self._target_basis).union(basic_instrs)
source_basis = set()
for node in dag.op_nodes():
if not dag.has_calibration_for(node):
source_basis.add((node.name, node.op.num_qubits))
target_basis = set(target_basis).union(basic_instrs)
logger.info(
"Begin BasisTranslator from source basis %s to target basis %s.",
@ -98,11 +161,34 @@ class BasisTranslator(TransformationPass):
)
# Search for a path from source to target basis.
search_start_time = time.time()
basis_transforms = _basis_search(
self._equiv_lib, source_basis, target_basis, _basis_heuristic
)
qarg_local_basis_transforms = {}
for qarg, local_source_basis in qargs_local_source_basis.items():
expanded_target = target_basis | self._qargs_with_non_global_operation[qarg]
# For any multiqubit operation that contains a subset of qubits that
# has a non-local operation, include that non-local operation in the
# search. This matches with the check we did above to include those
# subset non-local operations in the check here.
if len(qarg) > 1:
for non_local_qarg, local_basis in self._qargs_with_non_global_operation.items():
if qarg.issuperset(non_local_qarg):
expanded_target |= local_basis
logger.info(
"Performing BasisTranslator search from source basis %s to target "
"basis %s on qarg %s.",
local_source_basis,
expanded_target,
qarg,
)
qarg_local_basis_transforms[qarg] = _basis_search(
self._equiv_lib, local_source_basis, expanded_target, _basis_heuristic
)
search_end_time = time.time()
logger.info(
"Basis translation path search completed in %.3fs.", search_end_time - search_start_time
@ -118,6 +204,10 @@ class BasisTranslator(TransformationPass):
compose_start_time = time.time()
instr_map = _compose_transforms(basis_transforms, source_basis, dag)
extra_instr_map = {
qarg: _compose_transforms(transforms, qargs_local_source_basis[qarg], dag)
for qarg, transforms in qarg_local_basis_transforms.items()
}
compose_end_time = time.time()
logger.info(
@ -128,15 +218,22 @@ class BasisTranslator(TransformationPass):
replace_start_time = time.time()
for node in dag.op_nodes():
node_qargs = tuple(qarg_indices[bit] for bit in node.qargs)
qubit_set = frozenset(node_qargs)
if node.name in target_basis:
continue
if (
node_qargs in self._qargs_with_non_global_operation
and node.name in self._qargs_with_non_global_operation[node_qargs]
):
continue
if dag.has_calibration_for(node):
continue
if (node.op.name, node.op.num_qubits) in instr_map:
def replace_node(node, instr_map):
target_params, target_dag = instr_map[node.op.name, node.op.num_qubits]
if len(node.op.params) != len(target_params):
raise TranspilerError(
"Translation num_params not equal to op num_params."
@ -174,6 +271,11 @@ class BasisTranslator(TransformationPass):
dag.global_phase += bound_target_dag.global_phase
else:
dag.substitute_node_with_dag(node, bound_target_dag)
if qubit_set in extra_instr_map:
replace_node(node, extra_instr_map[qubit_set])
elif (node.op.name, node.op.num_qubits) in instr_map:
replace_node(node, instr_map)
else:
raise TranspilerError(f"BasisTranslator did not map {node.name}.")

View File

@ -21,14 +21,17 @@ class CheckGateDirection(AnalysisPass):
respect to the coupling map.
"""
def __init__(self, coupling_map):
def __init__(self, coupling_map, target=None):
"""CheckGateDirection initializer.
Args:
coupling_map (CouplingMap): Directed graph representing a coupling map.
target (Target): The backend target to use for this pass. If this is specified
it will be used instead of the coupling map
"""
super().__init__()
self.coupling_map = coupling_map
self.target = target
def run(self, dag):
"""Run the CheckGateDirection pass on `dag`.
@ -41,13 +44,20 @@ class CheckGateDirection(AnalysisPass):
"""
self.property_set["is_direction_mapped"] = True
edges = self.coupling_map.get_edges()
trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values())
if self.target is None:
for gate in dag.two_qubit_ops():
physical_q0 = trivial_layout[gate.qargs[0]]
physical_q1 = trivial_layout[gate.qargs[1]]
for gate in dag.two_qubit_ops():
physical_q0 = trivial_layout[gate.qargs[0]]
physical_q1 = trivial_layout[gate.qargs[1]]
if (physical_q0, physical_q1) not in edges:
self.property_set["is_direction_mapped"] = False
return
else:
for gate in dag.two_qubit_ops():
physical_q0 = trivial_layout[gate.qargs[0]]
physical_q1 = trivial_layout[gate.qargs[1]]
if (physical_q0, physical_q1) not in edges:
self.property_set["is_direction_mapped"] = False
return
if (physical_q0, physical_q1) not in self.target[gate.op.name]:
self.property_set["is_direction_mapped"] = False
return

View File

@ -47,14 +47,17 @@ class GateDirection(TransformationPass):
"""
def __init__(self, coupling_map):
def __init__(self, coupling_map, target=None):
"""GateDirection pass.
Args:
coupling_map (CouplingMap): Directed graph represented a coupling map.
target (Target): The backend target to use for this pass. If this is specified
it will be used instead of the coupling map
"""
super().__init__()
self.coupling_map = coupling_map
self.target = target
# Create the replacement dag and associated register.
self._cx_dag = DAGCircuit()
@ -103,46 +106,92 @@ class GateDirection(TransformationPass):
TranspilerError: If the circuit cannot be mapped just by flipping the
cx nodes.
"""
cmap_edges = set(self.coupling_map.get_edges())
if not cmap_edges:
return dag
self.coupling_map.compute_distance_matrix()
trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values())
layout_map = trivial_layout.get_virtual_bits()
if len(dag.qregs) > 1:
raise TranspilerError(
"GateDirection expects a single qreg input DAG,"
"but input DAG had qregs: {}.".format(dag.qregs)
)
if self.target is None:
cmap_edges = set(self.coupling_map.get_edges())
if not cmap_edges:
return dag
trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values())
layout_map = trivial_layout.get_virtual_bits()
dist_matrix = self.coupling_map.distance_matrix
self.coupling_map.compute_distance_matrix()
for node in dag.two_qubit_ops():
control = node.qargs[0]
target = node.qargs[1]
dist_matrix = self.coupling_map.distance_matrix
physical_q0 = layout_map[control]
physical_q1 = layout_map[target]
for node in dag.two_qubit_ops():
control = node.qargs[0]
target = node.qargs[1]
if dist_matrix[physical_q0, physical_q1] != 1:
raise TranspilerError(
"The circuit requires a connection between physical "
"qubits %s and %s" % (physical_q0, physical_q1)
)
physical_q0 = layout_map[control]
physical_q1 = layout_map[target]
if dist_matrix[physical_q0, physical_q1] != 1:
raise TranspilerError(
"The circuit requires a connection between physical "
"qubits %s and %s" % (physical_q0, physical_q1)
)
if (physical_q0, physical_q1) not in cmap_edges:
if node.name == "cx":
dag.substitute_node_with_dag(node, self._cx_dag)
elif node.name == "ecr":
dag.substitute_node_with_dag(node, self._ecr_dag)
elif node.name == "rzx":
dag.substitute_node_with_dag(node, self._rzx_dag(*node.op.params))
else:
raise TranspilerError(
f"Flipping of gate direction is only supported "
f"for CX, ECR, and RZX at this time, not {node.name}."
)
else:
# TODO: Work with the gate instances and only use names as look up keys.
# This will require iterating over the target names to build a mapping
# of names to gates that implement CXGate, ECRGate, RZXGate (including
# fixed angle variants)
for node in dag.two_qubit_ops():
control = node.qargs[0]
target = node.qargs[1]
physical_q0 = layout_map[control]
physical_q1 = layout_map[target]
if (physical_q0, physical_q1) not in cmap_edges:
if node.name == "cx":
dag.substitute_node_with_dag(node, self._cx_dag)
if (physical_q0, physical_q1) in self.target["cx"]:
continue
if (physical_q1, physical_q0) in self.target["cx"]:
dag.substitute_node_with_dag(node, self._cx_dag)
else:
raise TranspilerError(
"The circuit requires a connection between physical "
"qubits %s and %s for cx" % (physical_q0, physical_q1)
)
elif node.name == "ecr":
dag.substitute_node_with_dag(node, self._ecr_dag)
if (physical_q0, physical_q1) in self.target["ecr"]:
continue
if (physical_q1, physical_q0) in self.target["ecr"]:
dag.substitute_node_with_dag(node, self._ecr_dag)
else:
raise TranspilerError(
"The circuit requires a connection between physical "
"qubits %s and %s for ecr" % (physical_q0, physical_q1)
)
elif node.name == "rzx":
dag.substitute_node_with_dag(node, self._rzx_dag(*node.op.params))
if (physical_q0, physical_q1) in self.target["rzx"]:
continue
if (physical_q1, physical_q0) in self.target["rzx"]:
dag.substitute_node_with_dag(node, self._rzx_dag(*node.op.params))
else:
raise TranspilerError(
"The circuit requires a connection between physical "
"qubits %s and %s for rzx" % (physical_q0, physical_q1)
)
else:
raise TranspilerError(
f"Flipping of gate direction is only supported "
f"for CX, ECR, and RZX at this time, not {node.name}."
)
return dag

View File

@ -36,6 +36,7 @@ class PassManagerConfig:
timing_constraints=None,
unitary_synthesis_method="default",
unitary_synthesis_plugin_config=None,
target=None,
):
"""Initialize a PassManagerConfig object
@ -66,6 +67,7 @@ class PassManagerConfig:
unitary_synthesis_method (str): The string method to use for the
:class:`~qiskit.transpiler.passes.UnitarySynthesis` pass. Will
search installed plugins for a valid method.
target (Target): The backend target
"""
self.initial_layout = initial_layout
self.basis_gates = basis_gates
@ -82,6 +84,7 @@ class PassManagerConfig:
self.timing_constraints = timing_constraints
self.unitary_synthesis_method = unitary_synthesis_method
self.unitary_synthesis_plugin_config = unitary_synthesis_plugin_config
self.target = target
@classmethod
def from_backend(cls, backend, **pass_manager_options):
@ -113,5 +116,11 @@ class PassManagerConfig:
res.instruction_durations = InstructionDurations.from_backend(backend)
if res.backend_properties is None:
res.backend_properties = backend.properties()
if res.target is None:
backend_version = getattr(backend, "version", 0)
if not isinstance(backend_version, int):
backend_version = 0
if backend_version >= 2:
res.target = backend.target
return res

View File

@ -93,6 +93,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
timing_constraints = pass_manager_config.timing_constraints or TimingConstraints()
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
target = pass_manager_config.target
# 1. Decompose so only 1-qubit and 2-qubit gates remain
_unroll3q = [
@ -171,7 +172,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
plugin_config=unitary_synthesis_plugin_config,
),
UnrollCustomDefinitions(sel, basis_gates),
BasisTranslator(sel, basis_gates),
BasisTranslator(sel, basis_gates, target),
]
elif translation_method == "synthesis":
_unroll = [
@ -201,12 +202,12 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
raise TranspilerError("Invalid translation method %s." % translation_method)
# 6. Fix any bad CX directions
_direction_check = [CheckGateDirection(coupling_map)]
_direction_check = [CheckGateDirection(coupling_map, target)]
def _direction_condition(property_set):
return not property_set["is_direction_mapped"]
_direction = [GateDirection(coupling_map)]
_direction = [GateDirection(coupling_map, target)]
# 7. Unify all durations (either SI, or convert to dt if known)
# Schedule the circuit only when scheduling_method is supplied
@ -251,7 +252,9 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
pm0.append(_swap_check)
pm0.append(_swap, condition=_swap_condition)
pm0.append(_unroll)
if coupling_map and not coupling_map.is_symmetric:
if (coupling_map and not coupling_map.is_symmetric) or (
target is not None and target.get_non_global_operation_names(strict_direction=True)
):
pm0.append(_direction_check)
pm0.append(_direction, condition=_direction_condition)
pm0.append(_unroll)

View File

@ -100,6 +100,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
timing_constraints = pass_manager_config.timing_constraints or TimingConstraints()
target = pass_manager_config.target
# 1. Use trivial layout if no layout given
_given_layout = SetLayout(initial_layout)
@ -192,7 +193,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
plugin_config=unitary_synthesis_plugin_config,
),
UnrollCustomDefinitions(sel, basis_gates),
BasisTranslator(sel, basis_gates),
BasisTranslator(sel, basis_gates, target),
]
elif translation_method == "synthesis":
_unroll = [
@ -222,12 +223,12 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
raise TranspilerError("Invalid translation method %s." % translation_method)
# 7. Fix any bad CX directions
_direction_check = [CheckGateDirection(coupling_map)]
_direction_check = [CheckGateDirection(coupling_map, target)]
def _direction_condition(property_set):
return not property_set["is_direction_mapped"]
_direction = [GateDirection(coupling_map)]
_direction = [GateDirection(coupling_map, target)]
# 8. Remove zero-state reset
_reset = RemoveResetInZeroState()
@ -284,7 +285,9 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
pm1.append(_swap_check)
pm1.append(_swap, condition=_swap_condition)
pm1.append(_unroll)
if coupling_map and not coupling_map.is_symmetric:
if (coupling_map and not coupling_map.is_symmetric) or (
target is not None and target.get_non_global_operation_names(strict_direction=True)
):
pm1.append(_direction_check)
pm1.append(_direction, condition=_direction_condition)
pm1.append(_reset)

View File

@ -104,6 +104,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method
timing_constraints = pass_manager_config.timing_constraints or TimingConstraints()
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
target = pass_manager_config.target
# 1. Unroll to 1q or 2q gates
_unroll3q = [
@ -225,7 +226,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
plugin_config=unitary_synthesis_plugin_config,
),
UnrollCustomDefinitions(sel, basis_gates),
BasisTranslator(sel, basis_gates),
BasisTranslator(sel, basis_gates, target),
]
elif translation_method == "synthesis":
_unroll = [
@ -256,12 +257,12 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
raise TranspilerError("Invalid translation method %s." % translation_method)
# 6. Fix any bad CX directions
_direction_check = [CheckGateDirection(coupling_map)]
_direction_check = [CheckGateDirection(coupling_map, target)]
def _direction_condition(property_set):
return not property_set["is_direction_mapped"]
_direction = [GateDirection(coupling_map)]
_direction = [GateDirection(coupling_map, target)]
# 7. Remove zero-state reset
_reset = RemoveResetInZeroState()
@ -322,7 +323,9 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
pm2.append(_swap_check)
pm2.append(_swap, condition=_swap_condition)
pm2.append(_unroll)
if coupling_map and not coupling_map.is_symmetric:
if (coupling_map and not coupling_map.is_symmetric) or (
target is not None and target.get_non_global_operation_names(strict_direction=True)
):
pm2.append(_direction_check)
pm2.append(_direction, condition=_direction_condition)
pm2.append(_reset)

View File

@ -107,6 +107,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method
timing_constraints = pass_manager_config.timing_constraints or TimingConstraints()
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
target = pass_manager_config.target
# 1. Unroll to 1q or 2q gates
_unroll3q = [
@ -226,7 +227,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
method=unitary_synthesis_method,
),
UnrollCustomDefinitions(sel, basis_gates),
BasisTranslator(sel, basis_gates),
BasisTranslator(sel, basis_gates, target),
]
elif translation_method == "synthesis":
_unroll = [
@ -255,12 +256,12 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
raise TranspilerError("Invalid translation method %s." % translation_method)
# 6. Fix any CX direction mismatch
_direction_check = [CheckGateDirection(coupling_map)]
_direction_check = [CheckGateDirection(coupling_map, target)]
def _direction_condition(property_set):
return not property_set["is_direction_mapped"]
_direction = [GateDirection(coupling_map)]
_direction = [GateDirection(coupling_map, target)]
# 8. Optimize iteratively until no more change in depth. Removes useless gates
# after reset and before measure, commutes gates and optimizes contiguous blocks.
@ -334,11 +335,23 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
pm3.append(_swap_check)
pm3.append(_swap, condition=_swap_condition)
pm3.append(_unroll)
if coupling_map and not coupling_map.is_symmetric:
if (coupling_map and not coupling_map.is_symmetric) or (
target is not None and target.get_non_global_operation_names(strict_direction=True)
):
pm3.append(_direction_check)
pm3.append(_direction, condition=_direction_condition)
pm3.append(_reset)
pm3.append(_depth_check + _opt + _unroll, do_while=_opt_control)
pm3.append(_reset)
# For transpiling to a target we need to run GateDirection in the
# optimization loop to correct for incorrect directions that might be
# inserted by UnitarySynthesis which is direction aware but only via
# the coupling map which with a target doesn't give a full picture
if target is not None:
pm3.append(_depth_check + _opt + _unroll + _direction, do_while=_opt_control)
else:
pm3.append(_depth_check + _opt + _unroll, do_while=_opt_control)
else:
pm3.append(_reset)
pm3.append(_depth_check + _opt + _unroll, do_while=_opt_control)
if inst_map and inst_map.has_custom_gate():
pm3.append(PulseGates(inst_map=inst_map))
if scheduling_method:

View File

@ -167,6 +167,8 @@ class Target(Mapping):
"min_length",
"pulse_alignment",
"aquire_alignment",
"_non_global_basis",
"_non_global_strict_basis",
)
def __init__(
@ -223,6 +225,8 @@ class Target(Mapping):
self.min_length = min_length
self.pulse_alignment = pulse_alignment
self.aquire_alignment = aquire_alignment
self._non_global_basis = None
self._non_global_strict_basis = None
def add_instruction(self, instruction, properties=None, name=None):
"""Add a new instruction to the :class:`~qiskit.transpiler.Target`
@ -300,6 +304,8 @@ class Target(Mapping):
self._coupling_graph = None
self._instruction_durations = None
self._instruction_schedule_map = None
self._non_global_basis = None
self._non_global_strict_basis = None
def update_instruction_properties(self, instruction, qargs, properties):
"""Update the property object for an instruction qarg pair already in the Target
@ -600,6 +606,55 @@ class Target(Mapping):
"""Returns a sorted list of physical_qubits"""
return list(range(self.num_qubits))
def get_non_global_operation_names(self, strict_direction=False):
"""Return the non-global operation names for the target
The non-global operations are those in the target which don't apply
on all qubits (for single qubit operations) or all multiqubit qargs
(for multi-qubit operations).
Args:
strict_direction (bool): If set to ``True`` the multi-qubit
operations considered as non-global respect the strict
direction (or order of qubits in the qargs is signifcant). For
example, if ``cx`` is defined on ``(0, 1)`` and ``ecr`` is
defined over ``(1, 0)`` by default neither would be considered
non-global, but if ``strict_direction`` is set ``True`` both
``cx`` and ``ecr`` would be returned.
Returns:
List[str]: A list of operation names for operations that aren't global in this target
"""
if strict_direction:
if self._non_global_strict_basis is not None:
return self._non_global_strict_basis
search_set = self._qarg_gate_map.keys()
else:
if self._non_global_basis is not None:
return self._non_global_basis
search_set = {frozenset(qarg) for qarg in self._qarg_gate_map if len(qarg) != 1}
incomplete_basis_gates = []
size_dict = defaultdict(int)
size_dict[1] = self.num_qubits
for qarg in search_set:
if len(qarg) == 1:
continue
size_dict[len(qarg)] += 1
for inst, qargs in self._gate_map.items():
qarg_sample = next(iter(qargs))
if qarg_sample is None:
continue
if not strict_direction:
qargs = {frozenset(qarg) for qarg in qargs}
if len(qargs) != size_dict[len(qarg_sample)]:
incomplete_basis_gates.append(inst)
if strict_direction:
self._non_global_strict_basis = incomplete_basis_gates
else:
self._non_global_basis = incomplete_basis_gates
return incomplete_basis_gates
def __iter__(self):
return iter(self._gate_map)

View File

@ -0,0 +1,81 @@
---
features:
- |
The :class:`~qiskit.transpiler.passes.BasisTranslator`,
:class:`~qiskit.transpiler.passes.GateDirection`, and
:class:`~qiskit.transpiler.passes.CheckGateDirection` transpiler passes have
a new kwarg on the constructor, ``target`` which can be used to set
a :class:`~qiskit.transpiler.Target` object as the target for the pass. If
it is set it will be used instead of the ``basis_gates`` (in the case of
the :class:`~qiskit.transpiler.passes.BasisTranslator` pass) or
``coupling_map`` (in the case of the
:class:`~qiskit.transpiler.passes.GateDirection` and
:class:`~qiskit.transpiler.passes.CheckGateDirection` passes) arguments.
issues:
- |
When running the :class:`~qiskit.transpiler.pass.BasisTranslator` in
isolation with the ``target`` argument set to a
:class:`~qiskit.transpiler.Target` object set with non-overlapping sets
of single qubit operations on different qubits the output circuit might
incorrectly include operations outside the set defined on that single qubit.
For example, if you ran::
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.circuit.library import UGate, RZGate, XGate, SXGate, CXGate
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel
from qiskit.transpiler import PassManager, Target, InstructionProperties
from qiskit.transpiler.passes import BasisTranslator
gmap = Target()
# U gate in qubit 0.
theta = Parameter('theta')
phi = Parameter('phi')
lam = Parameter('lambda')
u_props = {
(0,): InstructionProperties(duration=5.23e-8, error=0.00038115),
}
gmap.add_instruction(UGate(theta, phi, lam), u_props)
# Rz gate in qubit 1.
phi = Parameter("phi")
rz_props = {
(1,): InstructionProperties(duration=0.0, error=0),
}
gmap.add_instruction(RZGate(phi), rz_props)
# X gate in qubit 1.
x_props = {
(1,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00020056469709026198
),
}
gmap.add_instruction(XGate(), x_props)
# SX gate in qubit 1.
sx_props = {
(1,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00020056469709026198
),
}
gmap.add_instruction(SXGate(), sx_props)
cx_props = {
(0, 1): InstructionProperties(duration=5.23e-7, error=0.00098115),
(1, 0): InstructionProperties(duration=4.52e-7, error=0.00132115),
}
gmap.add_instruction(CXGate(), cx_props)
bt_pass = BasisTranslator(sel, target_basis=None, target=gmap)
qc = QuantumCircuit(2)
qc.iswap(0, 1)
output = bt_pass(qc)
``output`` will have `RZGate` and ``SXGate`` on qubit 0. To correct this
you can normally run the basis translator a second time (i.e.
``output = bt_pass(output)`` in the above example) to correct this. This
should not effect the output of running the
:func:`~qiskit.compiler.transpile` function and is only an issue if you run
the pass by itself.

View File

@ -15,17 +15,35 @@
import math
from qiskit.circuit import QuantumCircuit
from test import combine
from ddt import ddt, data
from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit.compiler import transpile
from qiskit.test.base import QiskitTestCase
from qiskit.test.mock.fake_backend_v2 import FakeBackendV2
from qiskit.test.mock.fake_backend_v2 import FakeBackendV2, FakeBackend5QV2
from qiskit.test.mock.fake_mumbai_v2 import FakeMumbaiV2
from qiskit.quantum_info import Operator
@ddt
class TestBackendV2(QiskitTestCase):
def setUp(self):
super().setUp()
self.backend = FakeBackendV2()
def assertMatchesTargetConstraints(self, tqc, target):
qubit_indices = {qubit: index for index, qubit in enumerate(tqc.qubits)}
for instr, qargs, _ in tqc.data:
qargs = tuple(qubit_indices[x] for x in qargs)
target_set = target[instr.name].keys()
self.assertIn(
qargs,
target_set,
f"qargs: {qargs} not found in target for operation {instr.name}: {set(target_set)}",
)
def test_qubit_properties(self):
"""Test that qubit properties are returned as expected."""
props = self.backend.qubit_properties([1, 0])
@ -42,27 +60,107 @@ class TestBackendV2(QiskitTestCase):
"Specified value for 'shots' is not a valid value, must be >=1 or <=4096",
)
def test_transpile(self):
@data(0, 1, 2, 3)
def test_transpile(self, opt_level):
"""Test that transpile() works with a BackendV2 backend."""
qc = QuantumCircuit(2)
qc.h(1)
qc.cz(1, 0)
qc.measure_all()
with self.assertLogs("qiskit.providers.backend", level="WARN") as log:
tqc = transpile(qc, self.backend)
tqc = transpile(qc, self.backend, optimization_level=opt_level)
self.assertEqual(
log.output,
[
"WARNING:qiskit.providers.backend:This backend's operations: "
"ecr only apply to a subset of qubits. Using this property to "
"cx,ecr only apply to a subset of qubits. Using this property to "
"get 'basis_gates' for the transpiler may potentially create "
"invalid output"
],
)
self.assertTrue(Operator(tqc).equiv(qc))
self.assertMatchesTargetConstraints(tqc, self.backend.target)
@combine(
opt_level=[0, 1, 2, 3],
gate=["cx", "ecr", "cz"],
bidirectional=[True, False],
dsc=(
"Test GHZ circuit with {gate} using opt level {opt_level} on backend "
"with bidirectional={bidirectional}"
),
name="{gate}_level_{opt_level}_bidirectional_{bidirectional}",
)
def test_5q_ghz(self, opt_level, gate, bidirectional):
backend = FakeBackend5QV2(bidirectional)
qc = QuantumCircuit(5)
qc.h(0)
getattr(qc, gate)(0, 1)
getattr(qc, gate)(2, 1)
getattr(qc, gate)(2, 3)
getattr(qc, gate)(4, 3)
tqc = transpile(qc, backend, optimization_level=opt_level)
self.assertTrue(Operator(tqc).equiv(qc))
self.assertMatchesTargetConstraints(tqc, backend.target)
def test_transpile_respects_arg_constraints(self):
"""Test that transpile() respects a heterogenous basis."""
# Test CX on wrong link
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(1, 0)
tqc = transpile(qc, self.backend)
self.assertTrue(Operator(tqc).equiv(qc))
# Below is done to check we're decomposing cx(1, 0) with extra
# rotations to correct for direction. However because of fp
# differences between windows and other platforms the optimization
# from the 1q optimization passes differ and the output gates
# change (while still being equivalent). This relaxes the check to
# still ensure it's valid but not so specific that it fails on windows
self.assertEqual(tqc.count_ops().keys(), {"cx", "u"})
self.assertEqual(tqc.count_ops()["cx"], 1)
self.assertLessEqual(tqc.count_ops()["u"], 4)
self.assertMatchesTargetConstraints(tqc, self.backend.target)
# Test ECR on wrong link
qc = QuantumCircuit(2)
qc.h(0)
qc.ecr(0, 1)
tqc = transpile(qc, self.backend)
self.assertTrue(Operator(tqc).equiv(qc))
self.assertEqual(tqc.count_ops(), {"ecr": 1, "u": 4})
self.assertMatchesTargetConstraints(tqc, self.backend.target)
def test_transpile_relies_on_gate_direction(self):
"""Test that transpile() relies on gate direction pass for 2q."""
qc = QuantumCircuit(2)
qc.h(0)
qc.ecr(0, 1)
tqc = transpile(qc, self.backend)
expected = QuantumCircuit(2)
expected.u(0, 0, -math.pi, 0)
expected.u(math.pi / 2, 0, 0, 1)
expected.ecr(1, 0)
expected.u(math.pi / 2, 0, -math.pi, 0)
expected.u(math.pi / 2, 0, -math.pi, 1)
self.assertTrue(Operator(tqc).equiv(qc))
self.assertEqual(tqc.count_ops(), {"ecr": 1, "u": 4})
self.assertMatchesTargetConstraints(tqc, self.backend.target)
def test_transpile_mumbai_target(self):
"""Test that transpile respects a more involved target for a fake mumbai."""
backend = FakeMumbaiV2()
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(1, 0)
qc.measure_all()
tqc = transpile(qc, backend)
qr = QuantumRegister(27, "q")
cr = ClassicalRegister(2, "meas")
expected = QuantumCircuit(qr, cr, global_phase=math.pi / 4)
expected.rz(math.pi / 2, 0)
expected.sx(0)
expected.rz(math.pi / 2, 0)
expected.cx(1, 0)
expected.u(math.pi / 2, 0, -math.pi, 0)
expected.measure_all()
self.assertEqual(tqc, expected)
expected.barrier(qr[0:2])
expected.measure(qr[0], cr[0])
expected.measure(qr[1], cr[1])
self.assertEqual(expected, tqc)

View File

@ -21,10 +21,22 @@ from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit import transpile
from qiskit.test import QiskitTestCase
from qiskit.circuit import Gate, Parameter, EquivalenceLibrary
from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, CU1Gate, CU3Gate
from qiskit.circuit.library import (
U1Gate,
U2Gate,
U3Gate,
CU1Gate,
CU3Gate,
UGate,
RZGate,
XGate,
SXGate,
CXGate,
)
from qiskit.converters import circuit_to_dag, dag_to_circuit, circuit_to_instruction
from qiskit.exceptions import QiskitError
from qiskit.quantum_info import Operator
from qiskit.transpiler.target import Target, InstructionProperties
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.passes.basis import BasisTranslator, UnrollCustomDefinitions
@ -834,3 +846,65 @@ class TestBasisExamples(QiskitTestCase):
seed_transpiler=42,
)
self.assertEqual(circ_transpiled.count_ops(), {"cx": 91, "rz": 66, "sx": 22})
class TestBasisTranslatorWithTarget(QiskitTestCase):
"""Test the basis translator when running with a Target."""
def setUp(self):
super().setUp()
self.target = Target()
# U gate in qubit 0.
self.theta = Parameter("theta")
self.phi = Parameter("phi")
self.lam = Parameter("lambda")
u_props = {
(0,): InstructionProperties(duration=5.23e-8, error=0.00038115),
}
self.target.add_instruction(UGate(self.theta, self.phi, self.lam), u_props)
# Rz gate in qubit 1.
rz_props = {
(1,): InstructionProperties(duration=0.0, error=0),
}
self.target.add_instruction(RZGate(self.phi), rz_props)
# X gate in qubit 1.
x_props = {
(1,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00020056469709026198
),
}
self.target.add_instruction(XGate(), x_props)
# SX gate in qubit 1.
sx_props = {
(1,): InstructionProperties(
duration=3.5555555555555554e-08, error=0.00020056469709026198
),
}
self.target.add_instruction(SXGate(), sx_props)
cx_props = {
(0, 1): InstructionProperties(duration=5.23e-7, error=0.00098115),
(1, 0): InstructionProperties(duration=4.52e-7, error=0.00132115),
}
self.target.add_instruction(CXGate(), cx_props)
def test_2q_with_non_global_1q(self):
"""Test translation works with a 2q gate on an non-global 1q basis."""
qc = QuantumCircuit(2)
qc.cz(0, 1)
bt_pass = BasisTranslator(std_eqlib, target_basis=None, target=self.target)
output = bt_pass(qc)
expected = QuantumCircuit(2, global_phase=pi / 2)
expected.rz(pi / 2, 1)
expected.sx(1)
expected.rz(pi / 2, 1)
expected.cx(0, 1)
expected.rz(pi / 2, 1)
expected.sx(1)
expected.rz(pi / 2, 1)
self.assertEqual(output, expected)

View File

@ -244,7 +244,7 @@ class TestTarget(QiskitTestCase):
self.assertEqual(
self.aqt_target.qargs_for_operation_name("rz"), {(0,), (1,), (2,), (3,), (4,)}
)
self.assertEqual(self.fake_backend_target.qargs_for_operation_name("cx"), {(0, 1), (1, 0)})
self.assertEqual(self.fake_backend_target.qargs_for_operation_name("cx"), {(0, 1)})
self.assertEqual(
self.fake_backend_target.qargs_for_operation_name("ecr"),
{
@ -375,7 +375,7 @@ class TestTarget(QiskitTestCase):
res = self.ibm_target.operations_for_qargs((0,))
for gate in expected:
self.assertIn(gate, res)
expected = [CXGate(), ECRGate()]
expected = [ECRGate()]
res = self.fake_backend_target.operations_for_qargs((1, 0))
for gate in expected:
self.assertIn(gate, res)