diff --git a/qiskit_aer/backends/aer_simulator.py b/qiskit_aer/backends/aer_simulator.py index 2ede8e3bbb..04fc439592 100644 --- a/qiskit_aer/backends/aer_simulator.py +++ b/qiskit_aer/backends/aer_simulator.py @@ -695,7 +695,9 @@ class AerSimulator(AerBackend): _AVAILABLE_DEVICES = None - def __init__(self, configuration=None, properties=None, provider=None, **backend_options): + def __init__( + self, configuration=None, properties=None, provider=None, target=None, **backend_options + ): self._controller = aer_controller_execute() # Update available methods and devices for class @@ -717,7 +719,11 @@ def __init__(self, configuration=None, properties=None, provider=None, **backend self._cached_basis_gates = self._BASIS_GATES["automatic"] super().__init__( - configuration, properties=properties, provider=provider, backend_options=backend_options + configuration, + properties=properties, + provider=provider, + target=target, + backend_options=backend_options, ) @classmethod @@ -812,6 +818,11 @@ def _name(self): def from_backend(cls, backend, **options): """Initialize simulator from backend.""" if isinstance(backend, BackendV2): + if backend.description is None: + description = "created by AerSimulator.from_backend" + else: + description = backend.description + configuration = QasmBackendConfiguration( backend_name=f"'aer_simulator({backend.name})", backend_version=backend.backend_version, @@ -826,9 +837,10 @@ def from_backend(cls, backend, **options): max_shots=int(1e6), coupling_map=list(backend.coupling_map.get_edges()), max_experiments=backend.max_circuits, - description=backend.description, + description=description, ) properties = target_to_backend_properties(backend.target) + target = backend.target elif isinstance(backend, BackendV1): # Get configuration and properties from backend configuration = copy.copy(backend.configuration()) @@ -837,6 +849,8 @@ def from_backend(cls, backend, **options): # Customize configuration name name = configuration.backend_name configuration.backend_name = f"aer_simulator({name})" + + target = None else: raise TypeError( "The backend argument requires a BackendV2 or BackendV1 object, " @@ -853,7 +867,7 @@ def from_backend(cls, backend, **options): options["noise_model"] = noise_model # Initialize simulator - sim = cls(configuration=configuration, properties=properties, **options) + sim = cls(configuration=configuration, properties=properties, target=target, **options) return sim def available_methods(self): diff --git a/qiskit_aer/backends/aerbackend.py b/qiskit_aer/backends/aerbackend.py index 22f620ba77..36545ffe8c 100644 --- a/qiskit_aer/backends/aerbackend.py +++ b/qiskit_aer/backends/aerbackend.py @@ -29,6 +29,7 @@ from qiskit.pulse import Schedule, ScheduleBlock from qiskit.qobj import QasmQobj, PulseQobj from qiskit.result import Result +from qiskit.transpiler import CouplingMap from ..aererror import AerError from ..jobs import AerJob, AerJobSet, split_qobj from ..noise.noise_model import NoiseModel, QuantumErrorLocation @@ -48,7 +49,7 @@ class AerBackend(Backend, ABC): """Aer Backend class.""" def __init__( - self, configuration, properties=None, defaults=None, backend_options=None, provider=None + self, configuration, properties=None, provider=None, target=None, backend_options=None ): """Aer class for backends. @@ -59,8 +60,8 @@ def __init__( Args: configuration (BackendConfiguration): backend configuration. properties (BackendProperties or None): Optional, backend properties. - defaults (PulseDefaults or None): Optional, backend pulse defaults. provider (Provider): Optional, provider responsible for this backend. + target (Target): initial target for backend backend_options (dict or None): Optional set custom backend options. Raises: @@ -76,22 +77,24 @@ def __init__( backend_version=configuration.backend_version, ) - # Initialize backend properties and pulse defaults. + # Initialize backend properties self._properties = properties - self._defaults = defaults self._configuration = configuration - # Custom option values for config, properties, and defaults + # Custom option values for config, properties self._options_configuration = {} - self._options_defaults = {} self._options_properties = {} - self._target = None + self._target = target self._mapping = NAME_MAPPING # Set options from backend_options dictionary if backend_options is not None: self.set_options(**backend_options) + # build coupling map + if self.configuration().coupling_map is not None: + self._coupling_map = CouplingMap(self.configuration().coupling_map) + def _convert_circuit_binds(self, circuit, binds, idx_map): parameterizations = [] @@ -330,18 +333,6 @@ def properties(self): setattr(properties, key, val) return properties - def defaults(self): - """Return the simulator backend pulse defaults. - - Returns: - PulseDefaults: The backend pulse defaults or ``None`` if the - backend does not support pulse. - """ - defaults = copy.copy(self._defaults) - for key, val in self._options_defaults.items(): - setattr(defaults, key, val) - return defaults - @property def max_circuits(self): if hasattr(self.configuration(), "max_experiments"): @@ -351,17 +342,16 @@ def max_circuits(self): @property def target(self): - self._target = convert_to_target( - self.configuration(), self.properties(), self.defaults(), self._mapping - ) - return self._target + if self._target is not None: + return self._target + + return convert_to_target(self.configuration(), self.properties(), None, NAME_MAPPING) def clear_options(self): """Reset the simulator options to default values.""" self._options = self._default_options() self._options_configuration = {} self._options_properties = {} - self._options_defaults = {} def status(self): """Return backend status. @@ -702,8 +692,6 @@ def set_option(self, key, value): self._set_configuration_option(key, value) elif hasattr(self._properties, key): self._set_properties_option(key, value) - elif hasattr(self._defaults, key): - self._set_defaults_option(key, value) else: if not hasattr(self._options, key): raise AerError(f"Invalid option {key}") @@ -735,15 +723,15 @@ def _set_properties_option(self, key, value): elif key in self._options_properties: self._options_properties.pop(key) - def _set_defaults_option(self, key, value): - """Special handling for setting backend defaults options.""" - if value is not None: - self._options_defaults[key] = value - elif key in self._options_defaults: - self._options_defaults.pop(key) - def __repr__(self): """String representation of an AerBackend.""" name = self.__class__.__name__ display = f"'{self.name}'" return f"{name}({display})" + + def get_translation_stage_plugin(self): + """use custom translation method to avoid gate exchange""" + if self._target is None: + return "aer_backend_plugin" + else: + return None diff --git a/qiskit_aer/backends/name_mapping.py b/qiskit_aer/backends/name_mapping.py index 0caadc1999..419e3cde37 100644 --- a/qiskit_aer/backends/name_mapping.py +++ b/qiskit_aer/backends/name_mapping.py @@ -17,21 +17,23 @@ from qiskit.circuit import ControlledGate, Parameter from qiskit.circuit.reset import Reset from qiskit.circuit.library import ( - SXGate, - MCPhaseGate, - MCXGate, - RZGate, - RXGate, U2Gate, - U1Gate, - U3Gate, - YGate, - ZGate, - PauliGate, - SwapGate, RGate, + CYGate, + CZGate, + CSXGate, + CU3Gate, + CSwapGate, + PauliGate, + DiagonalGate, + UnitaryGate, + MCPhaseGate, + MCXGate, + CRXGate, + CRYGate, + CRZGate, + MCU1Gate, MCXGrayCode, - RYGate, ) from qiskit.circuit.controlflow import ( IfElseOp, @@ -41,8 +43,8 @@ BreakLoopOp, SwitchCaseOp, ) -from qiskit.extensions import Initialize, UnitaryGate -from qiskit.extensions.quantum_initializer import DiagonalGate, UCGate +from qiskit.extensions import Initialize +from qiskit.extensions.quantum_initializer import UCGate from qiskit.quantum_info.operators.channel.kraus import Kraus from qiskit.quantum_info.operators.channel import SuperOp from qiskit.quantum_info.operators.channel.quantum_channel import QuantumChannel @@ -85,7 +87,7 @@ def __init__(self, num_ctrl_qubits, ctrl_state=None): None, num_ctrl_qubits, ctrl_state=ctrl_state, - base_gate=SXGate(), + base_gate=CSXGate(), ) @@ -100,7 +102,7 @@ def __init__(self, num_ctrl_qubits, ctrl_state=None): None, num_ctrl_qubits, ctrl_state=ctrl_state, - base_gate=YGate(), + base_gate=CYGate(), ) @@ -115,7 +117,7 @@ def __init__(self, num_ctrl_qubits, ctrl_state=None): None, num_ctrl_qubits, ctrl_state=ctrl_state, - base_gate=ZGate(), + base_gate=CZGate(), ) @@ -130,7 +132,7 @@ def __init__(self, theta, num_ctrl_qubits, ctrl_state=None): None, num_ctrl_qubits, ctrl_state=ctrl_state, - base_gate=RXGate(theta), + base_gate=CRXGate(theta), ) @@ -145,7 +147,7 @@ def __init__(self, theta, num_ctrl_qubits, ctrl_state=None): None, num_ctrl_qubits, ctrl_state=ctrl_state, - base_gate=RYGate(theta), + base_gate=CRYGate(theta), ) @@ -160,7 +162,7 @@ def __init__(self, theta, num_ctrl_qubits, ctrl_state=None): None, num_ctrl_qubits, ctrl_state=ctrl_state, - base_gate=RZGate(theta), + base_gate=CRZGate(theta), ) @@ -179,21 +181,6 @@ def __init__(self, theta, phi, num_ctrl_qubits, ctrl_state=None): ) -class MCU1Gate(ControlledGate): - """mcu1 gate""" - - def __init__(self, theta, num_ctrl_qubits, ctrl_state=None): - super().__init__( - "mcu1", - 1 + num_ctrl_qubits, - [theta], - None, - num_ctrl_qubits, - ctrl_state=ctrl_state, - base_gate=U1Gate(theta), - ) - - class MCU2Gate(ControlledGate): """mcu2 gate""" @@ -220,7 +207,7 @@ def __init__(self, theta, lam, phi, num_ctrl_qubits, ctrl_state=None): None, num_ctrl_qubits, ctrl_state=ctrl_state, - base_gate=U3Gate(theta, phi, lam), + base_gate=CU3Gate(theta, phi, lam), ) @@ -235,7 +222,7 @@ def __init__(self, theta, lam, phi, num_ctrl_qubits, ctrl_state=None): None, num_ctrl_qubits, ctrl_state=ctrl_state, - base_gate=U3Gate(theta, phi, lam), + base_gate=CU3Gate(theta, phi, lam), ) @@ -250,33 +237,43 @@ def __init__(self, num_ctrl_qubits, ctrl_state=None): None, num_ctrl_qubits, ctrl_state=ctrl_state, - base_gate=SwapGate(), + base_gate=CSwapGate(), ) PHI = Parameter("phi") LAM = Parameter("lam") NAME_MAPPING = { + "cu2": U2Gate(PHI, LAM).control(), + "pauli": PauliGate, + "diagonal": DiagonalGate, + "unitary": UnitaryGate, "mcsx": MCSXGate, "mcp": MCPhaseGate, "mcphase": MCPhaseGate, + "mcu": MCUGate, + "mcu1": MCU1Gate, + "mcu2": MCU2Gate, + "mcu3": MCU3Gate, + "mcx": MCXGate, + "mcy": MCYGate, + "mcz": MCZGate, + "mcr": MCRGate, + "mcrx": MCRXGate, + "mcry": MCRYGate, + "mcrz": MCRZGate, + "mcx_gray": MCXGrayCode, + "mcswap": MCSwapGate, + "multiplexer": UCGate, + "kraus": Kraus, + "superop": SuperOp, "initialize": Initialize, "quantum_channel": QuantumChannel, "save_expval": SaveExpectationValue, - "diagonal": DiagonalGate, "save_amplitudes": SaveAmplitudes, "roerror": ReadoutError, - "mcrx": MCRXGate, - "kraus": Kraus, "save_statevector_dict": SaveStatevectorDict, - "mcx": MCXGate, - "mcu1": MCU1Gate, - "mcu2": MCU2Gate, - "mcu3": MCU3Gate, "save_superop": SaveSuperOp, - "multiplexer": UCGate, - "mcy": MCYGate, - "superop": SuperOp, "save_clifford": SaveClifford, "save_matrix_product_state": SaveMatrixProductState, "save_density_matrix": SaveDensityMatrix, @@ -288,30 +285,20 @@ def __init__(self, num_ctrl_qubits, ctrl_state=None): "break_loop": BreakLoopOp, "continue_loop": ContinueLoopOp, "save_statevector": SaveStatevector, - "mcu": MCUGate, "set_density_matrix": SetDensityMatrix, "qerror_loc": QuantumErrorLocation, - "unitary": UnitaryGate, - "mcz": MCZGate, - "pauli": PauliGate, "set_unitary": SetUnitary, "save_state": SaveState, - "mcswap": MCSwapGate, "set_matrix_product_state": SetMatrixProductState, "save_unitary": SaveUnitary, - "mcr": MCRGate, - "mcx_gray": MCXGrayCode, - "mcrz": MCRZGate, "set_superop": SetSuperOp, "save_expval_var": SaveExpectationValueVariance, "save_stabilizer": SaveStabilizer, "set_statevector": SetStatevector, - "mcry": MCRYGate, "set_stabilizer": SetStabilizer, "save_amplitudes_sq": SaveAmplitudesSquared, "save_probabilities_dict": SaveProbabilitiesDict, "save_probs_ket": SaveProbabilitiesDict, "save_probs": SaveProbabilities, - "cu2": U2Gate(PHI, LAM).control(), "reset": Reset(), } diff --git a/qiskit_aer/backends/plugin/__init__.py b/qiskit_aer/backends/plugin/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/qiskit_aer/backends/plugin/aer_backend_plugin.py b/qiskit_aer/backends/plugin/aer_backend_plugin.py new file mode 100644 index 0000000000..73be26fcef --- /dev/null +++ b/qiskit_aer/backends/plugin/aer_backend_plugin.py @@ -0,0 +1,126 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. +""" +Aer simulator backend transpiler plug-in +""" +from qiskit.transpiler.preset_passmanagers.plugin import PassManagerStagePlugin +from qiskit.transpiler import PassManager, TransformationPass +from qiskit.transpiler.passes import BasisTranslator +from qiskit.transpiler.passes import UnitarySynthesis +from qiskit.transpiler.passes import HighLevelSynthesis +from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel +from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping +from qiskit.circuit.measure import Measure +from qiskit.circuit.library import Barrier +from qiskit.circuit import ControlFlowOp +from qiskit.converters import circuit_to_dag +from qiskit_aer.backends.name_mapping import NAME_MAPPING + + +class AerBackendRebuildGateSetsFromCircuit(TransformationPass): + """custom translation class to rebuild basis gates with gates in circuit""" + + def __init__(self, config, opt_lvl): + super().__init__() + self.config = config + if opt_lvl is None: + self.optimization_level = 1 + else: + self.optimization_level = opt_lvl + self.qiskit_inst_name_map = get_standard_gate_name_mapping() + self.qiskit_inst_name_map["barrier"] = Barrier + + def _add_ops(self, dag, ops: set): + num_unsupported_ops = 0 + opnodes = dag.op_nodes() + if opnodes is None: + return num_unsupported_ops + + for node in opnodes: + if isinstance(node.op, ControlFlowOp): + for block in node.op.blocks: + num_unsupported_ops += self._add_ops(circuit_to_dag(block), ops) + if node.name in self.qiskit_inst_name_map: + ops.add(node.name) + elif node.name in self.config.target: + ops.add(node.name) + else: + num_unsupported_ops = num_unsupported_ops + 1 + return num_unsupported_ops + + def run(self, dag): + # do nothing for higher optimization level + if self.optimization_level > 1: + return dag + if self.config is None or self.config.target is None: + return dag + + # search ops in supported name mapping + ops = set() + num_unsupported_ops = self._add_ops(dag, ops) + + # if there are some unsupported node (i.e. RealAmplitudes) do nothing + if num_unsupported_ops > 0 or len(ops) < 1: + return dag + + # clear all instructions in target + self.config.target._gate_map.clear() + self.config.target._gate_name_map.clear() + self.config.target._qarg_gate_map.clear() + self.config.target._global_operations.clear() + + # rebuild gate sets from circuit + for name in ops: + if name in self.qiskit_inst_name_map: + self.config.target.add_instruction(self.qiskit_inst_name_map[name], name=name) + else: + self.config.target.add_instruction(NAME_MAPPING[name], name=name) + if "measure" not in ops: + self.config.target.add_instruction(Measure()) + self.config.basis_gates = list(self.config.target.operation_names) + + return dag + + +# This plugin should not be used outside of simulator +# TODO : this plugin should be moved to optimization stage plugin +# if Qiskit will have custom optimizaiton stage plugin interface +# in that case just return pass without Optimize1qGatesDecomposition +class AerBackendPlugin(PassManagerStagePlugin): + """custom passmanager to avoid unnecessary gate changes""" + + def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager: + return PassManager( + [ + UnitarySynthesis( + pass_manager_config.basis_gates, + approximation_degree=pass_manager_config.approximation_degree, + coupling_map=pass_manager_config.coupling_map, + backend_props=pass_manager_config.backend_properties, + plugin_config=pass_manager_config.unitary_synthesis_plugin_config, + method=pass_manager_config.unitary_synthesis_method, + target=pass_manager_config.target, + ), + HighLevelSynthesis( + hls_config=pass_manager_config.hls_config, + coupling_map=pass_manager_config.coupling_map, + target=pass_manager_config.target, + use_qubit_indices=True, + equivalence_library=sel, + basis_gates=pass_manager_config.basis_gates, + ), + BasisTranslator(sel, pass_manager_config.basis_gates, pass_manager_config.target), + AerBackendRebuildGateSetsFromCircuit( + config=pass_manager_config, opt_lvl=optimization_level + ), + ] + ) diff --git a/releasenotes/notes/fix_aerbackend-7e9a74f8219315dc.yaml b/releasenotes/notes/fix_aerbackend-7e9a74f8219315dc.yaml new file mode 100644 index 0000000000..f2cf556195 --- /dev/null +++ b/releasenotes/notes/fix_aerbackend-7e9a74f8219315dc.yaml @@ -0,0 +1,13 @@ +--- +fixes: + - | + Fixes AerBackend issues caused by upgading to BackendV2 in 0.13.0 release + and fix test failures for Qiskit 0.45 release. + + For issue #1987, added description if backend given by from_backend does not + have description. + + For issue #1988, added building coupling map from option. + + For issue #1982, added custome pass maneger to rebuild basis gates from + input circuits to prevent unnecessary gate changes diff --git a/setup.py b/setup.py index fc35929a17..d0b8ce4d8a 100644 --- a/setup.py +++ b/setup.py @@ -91,6 +91,7 @@ if is_win_32_bit: cmake_args.append("-DCMAKE_GENERATOR_PLATFORM=Win32") + setup( name=PACKAGE_NAME, version=VERSION, @@ -112,4 +113,9 @@ cmake_args=cmake_args, keywords="qiskit, simulator, quantum computing, backend", zip_safe=False, + entry_points={ + "qiskit.transpiler.translation": [ + "aer_backend_plugin = qiskit_aer.backends.plugin.aer_backend_plugin:AerBackendPlugin", + ] + }, )