From 1a9f93804f2696e3a6c119ecbebd3724df008dfa Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 3 Dec 2021 19:24:52 -0500 Subject: [PATCH] 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 * 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 * 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 * Only run gate direction in level 3 with a target * Move target overrides inside _parse_transpile_args() Co-authored-by: Kevin Krsulich Co-authored-by: georgios-ts <45130028+georgios-ts@users.noreply.github.com> --- qiskit/compiler/transpiler.py | 190 +++++- qiskit/providers/backend.py | 25 +- qiskit/test/mock/__init__.py | 3 +- qiskit/test/mock/fake_backend_v2.py | 85 ++- qiskit/test/mock/fake_mumbai_v2.py | 638 ++++++++++++++++++ .../passes/basis/basis_translator.py | 130 +++- .../passes/utils/check_gate_direction.py | 30 +- .../transpiler/passes/utils/gate_direction.py | 99 ++- qiskit/transpiler/passmanager_config.py | 9 + .../transpiler/preset_passmanagers/level0.py | 11 +- .../transpiler/preset_passmanagers/level1.py | 11 +- .../transpiler/preset_passmanagers/level2.py | 11 +- .../transpiler/preset_passmanagers/level3.py | 25 +- qiskit/transpiler/target.py | 55 ++ ...target-in-transpiler-c0a97bd33ad9417d.yaml | 81 +++ test/python/providers/test_backend_v2.py | 116 +++- .../transpiler/test_basis_translator.py | 76 ++- test/python/transpiler/test_target.py | 4 +- 18 files changed, 1464 insertions(+), 135 deletions(-) create mode 100644 qiskit/test/mock/fake_mumbai_v2.py create mode 100644 releasenotes/notes/target-in-transpiler-c0a97bd33ad9417d.yaml diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 342195921c38..ed4b76bce1f6 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -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 @@ 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 callback_func(**kwargs): 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 callback_func(**kwargs): approximation_degree=approximation_degree, unitary_synthesis_method=unitary_synthesis_method, backend=backend, + target=target, ) warnings.warn( @@ -284,7 +291,12 @@ def callback_func(**kwargs): 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 callback_func(**kwargs): 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] - - 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_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) + + 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 diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index 6034d5106c9f..51b0da959157 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -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 @@ def __init__( 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 @@ def operations(self) -> List[Instruction]: """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 " diff --git a/qiskit/test/mock/__init__.py b/qiskit/test/mock/__init__.py index d578d50ff7df..8995dd2f562e 100644 --- a/qiskit/test/mock/__init__.py +++ b/qiskit/test/mock/__init__.py @@ -23,7 +23,8 @@ 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 diff --git a/qiskit/test/mock/fake_backend_v2.py b/qiskit/test/mock/fake_backend_v2.py index c4901191ab4b..e46683e90e24 100644 --- a/qiskit/test/mock/fake_backend_v2.py +++ b/qiskit/test/mock/fake_backend_v2.py @@ -20,7 +20,12 @@ 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 @@ def __init__(self): 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 @@ def qubit_properties(self, qubit): 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] diff --git a/qiskit/test/mock/fake_mumbai_v2.py b/qiskit/test/mock/fake_mumbai_v2.py new file mode 100644 index 000000000000..f11aa3983c1a --- /dev/null +++ b/qiskit/test/mock/fake_mumbai_v2.py @@ -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] diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 217fa318af08..c1341d74f61a 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -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 @@ def __init__(self, equivalence_library, target_basis): 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 @@ def run(self, dag): 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"] - - target_basis = set(self._target_basis).union(basic_instrs) + 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)) - 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 @@ def run(self, dag): ) # 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 @@ def run(self, dag): 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 @@ def run(self, dag): 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 @@ def run(self, dag): 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}.") diff --git a/qiskit/transpiler/passes/utils/check_gate_direction.py b/qiskit/transpiler/passes/utils/check_gate_direction.py index 07f60001cc3d..a9d339788744 100644 --- a/qiskit/transpiler/passes/utils/check_gate_direction.py +++ b/qiskit/transpiler/passes/utils/check_gate_direction.py @@ -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 @@ def run(self, dag): """ self.property_set["is_direction_mapped"] = True edges = self.coupling_map.get_edges() - trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) - - 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 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]] + + 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 self.target[gate.op.name]: + self.property_set["is_direction_mapped"] = False + return diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 52a4a50e573a..90e66821a268 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -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 @@ def run(self, dag): 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 diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 12343f9c6ec3..80f01f0cd078 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -36,6 +36,7 @@ def __init__( timing_constraints=None, unitary_synthesis_method="default", unitary_synthesis_plugin_config=None, + target=None, ): """Initialize a PassManagerConfig object @@ -66,6 +67,7 @@ def __init__( 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 @@ def __init__( 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 @@ def from_backend(cls, backend, **pass_manager_options): 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 diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 46cc34f8c0b5..168d65155404 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -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 _swap_condition(property_set): 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 _swap_condition(property_set): 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 _contains_delay(property_set): 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) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 21c362044273..1db056c15244 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -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 _swap_condition(property_set): 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 _swap_condition(property_set): 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 _contains_delay(property_set): 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) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index d0a6396626d2..1120db671468 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -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 _swap_condition(property_set): 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 _swap_condition(property_set): 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 _contains_delay(property_set): 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) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 93fdd9ccbb7b..dab463e60202 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -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 _swap_condition(property_set): 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 _swap_condition(property_set): 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 _contains_delay(property_set): 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: diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index dae7fb768d09..6789c1856ea6 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -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 @@ def __init__( 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 @@ def add_instruction(self, instruction, properties=None, name=None): 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 @@ def physical_qubits(self): """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) diff --git a/releasenotes/notes/target-in-transpiler-c0a97bd33ad9417d.yaml b/releasenotes/notes/target-in-transpiler-c0a97bd33ad9417d.yaml new file mode 100644 index 000000000000..c6d2a07c1615 --- /dev/null +++ b/releasenotes/notes/target-in-transpiler-c0a97bd33ad9417d.yaml @@ -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. diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index a81bbef3d94b..b1ebe69a1d9f 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -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 @@ def test_option_bounds(self): "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) diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py index 6236438a4d13..5ae27f03cf88 100644 --- a/test/python/transpiler/test_basis_translator.py +++ b/test/python/transpiler/test_basis_translator.py @@ -21,10 +21,22 @@ 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 @@ def test_skip_target_basis_equivalences_1(self): 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) diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index d518ec7fce58..5c719f64dd22 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -244,7 +244,7 @@ def test_qargs_for_operation_name(self): 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 @@ def test_get_instructions_for_qargs(self): 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)