diff --git a/.github/workflows/unit-tests-latest-qiskit.yml b/.github/workflows/unit-tests-latest-qiskit.yml index b98c5c9cd5..f461d61ac9 100644 --- a/.github/workflows/unit-tests-latest-qiskit.yml +++ b/.github/workflows/unit-tests-latest-qiskit.yml @@ -39,7 +39,7 @@ jobs: shell: bash - name: Install dependencies and Aer run: | - python -m pip install -U setuptools wheel + python -m pip install -U setuptools setuptools-rust wheel python -m pip install -U \ -c constraints.txt \ -r requirements-dev.txt \ diff --git a/qiskit_aer/backends/aer_compiler.py b/qiskit_aer/backends/aer_compiler.py index e11e5c4c9a..544b4a6ed6 100644 --- a/qiskit_aer/backends/aer_compiler.py +++ b/qiskit_aer/backends/aer_compiler.py @@ -42,7 +42,6 @@ from qiskit.transpiler.passes import Decompose -from qiskit.qobj import QobjExperimentHeader from qiskit_aer.aererror import AerError from qiskit_aer.noise import NoiseModel @@ -62,7 +61,7 @@ AerConfig, ) -from .backend_utils import circuit_optypes +from .backend_utils import circuit_optypes, CircuitHeader from ..library.control_flow_instructions import AerMark, AerJump, AerStore @@ -682,7 +681,7 @@ def assemble_circuit(circuit: QuantumCircuit, basis_gates=None): for inst in circuit.data ) - header = QobjExperimentHeader( + header = CircuitHeader( n_qubits=num_qubits, qreg_sizes=qreg_sizes, memory_slots=num_memory, diff --git a/qiskit_aer/backends/aer_simulator.py b/qiskit_aer/backends/aer_simulator.py index a833574978..5d263ccaf7 100644 --- a/qiskit_aer/backends/aer_simulator.py +++ b/qiskit_aer/backends/aer_simulator.py @@ -15,14 +15,15 @@ import copy import logging +from warnings import warn from qiskit.providers import convert_to_target from qiskit.providers.options import Options -from qiskit.providers.models import QasmBackendConfiguration from qiskit.providers.backend import BackendV2, BackendV1 -from qiskit.transpiler.target import target_to_backend_properties from ..version import __version__ from .aerbackend import AerBackend, AerError +from .backendconfiguration import AerBackendConfiguration +from .backendproperties import target_to_backend_properties from .backend_utils import ( cpp_execute_circuits, cpp_execute_qobj, @@ -685,7 +686,6 @@ class AerSimulator(AerBackend): "simulator": True, "local": True, "conditional": True, - "open_pulse": False, "memory": True, "max_shots": int(1e6), "description": "A C++ QasmQobj simulator with noise", @@ -728,7 +728,7 @@ def __init__( # Default configuration if configuration is None: - configuration = QasmBackendConfiguration.from_dict(AerSimulator._DEFAULT_CONFIGURATION) + configuration = AerBackendConfiguration.from_dict(AerSimulator._DEFAULT_CONFIGURATION) # set backend name from method and device in option if "from" not in configuration.backend_name: @@ -846,29 +846,30 @@ def from_backend(cls, backend, **options): else: description = backend.description - configuration = QasmBackendConfiguration( + configuration = AerBackendConfiguration( backend_name=f"aer_simulator_from({backend.name})", backend_version=backend.backend_version, n_qubits=backend.num_qubits, basis_gates=backend.operation_names, gates=[], - local=True, - simulator=True, - conditional=True, - open_pulse=False, - memory=False, max_shots=int(1e6), - coupling_map=( - None if backend.coupling_map is None else list(backend.coupling_map.get_edges()) - ), + coupling_map=list(backend.coupling_map.get_edges()), max_experiments=backend.max_circuits, description=description, ) properties = target_to_backend_properties(backend.target) target = backend.target elif isinstance(backend, BackendV1): + # BackendV1 will be removed in Qiskit 2.0, so we will remove this soon + warn( + " from_backend using V1 based backend is deprecated as of Aer 0.15" + " and will be removed no sooner than 3 months from that release" + " date. Please use backends based on V2.", + DeprecationWarning, + stacklevel=2, + ) # Get configuration and properties from backend - configuration = copy.copy(backend.configuration()) + configuration = backend.configuration() properties = copy.copy(backend.properties()) # Customize configuration name diff --git a/qiskit_aer/backends/aerbackend.py b/qiskit_aer/backends/aerbackend.py index 8d4cb22618..e6d1db8f25 100644 --- a/qiskit_aer/backends/aerbackend.py +++ b/qiskit_aer/backends/aerbackend.py @@ -24,12 +24,12 @@ from qiskit.circuit import QuantumCircuit, ParameterExpression, Delay from qiskit.compiler import assemble from qiskit.providers import BackendV2 as Backend -from qiskit.providers import convert_to_target from qiskit.providers.models import BackendStatus from qiskit.pulse import Schedule, ScheduleBlock from qiskit.qobj import QasmQobj, PulseQobj from qiskit.result import Result from qiskit.transpiler import CouplingMap +from qiskit.transpiler.target import Target from ..aererror import AerError from ..jobs import AerJob, AerJobSet, split_qobj from ..noise.noise_model import NoiseModel, QuantumErrorLocation @@ -58,8 +58,8 @@ def __init__( not available. Args: - configuration (BackendConfiguration): backend configuration. - properties (BackendProperties or None): Optional, backend properties. + configuration (AerBackendConfiguration): backend configuration. + properties (AerBackendProperties or None): Optional, backend properties. provider (Provider): Optional, provider responsible for this backend. target (Target): initial target for backend backend_options (dict or None): Optional set custom backend options. @@ -68,8 +68,6 @@ def __init__( AerError: if there is no name in the configuration """ # Init configuration and provider in Backend - configuration.simulator = True - configuration.local = True super().__init__( provider=provider, name=configuration.backend_name, @@ -77,15 +75,19 @@ def __init__( backend_version=configuration.backend_version, ) - # Initialize backend properties + # Initialize backend configuration self._properties = properties self._configuration = configuration - # Custom option values for config, properties + # Custom option values for config self._options_configuration = {} self._options_properties = {} self._target = target self._mapping = NAME_MAPPING + if target is not None: + self._from_backend = True + else: + self._from_backend = False # Set options from backend_options dictionary if backend_options is not None: @@ -145,13 +147,12 @@ def _convert_binds(self, circuits, parameter_binds, idx_maps=None): return parameterizations # pylint: disable=arguments-renamed - def run(self, circuits, validate=False, parameter_binds=None, **run_options): + def run(self, circuits, parameter_binds=None, **run_options): """Run circuits on the backend. Args: circuits (QuantumCircuit or list): The QuantumCircuit (or list of QuantumCircuit objects) to run - validate (bool): validate the Qobj before running (default: False). parameter_binds (list): A list of parameter binding dictionaries. See additional information (default: None). run_options (kwargs): additional run time backend options. @@ -160,7 +161,7 @@ def run(self, circuits, validate=False, parameter_binds=None, **run_options): AerJob: The simulation job. Raises: - TypeError: If ``parameter_binds`` is specified with a qobj input or + TypeError: If ``parameter_binds`` is specified with an input or has a length mismatch with the number of circuits. Additional Information: @@ -185,67 +186,7 @@ def run(self, circuits, validate=False, parameter_binds=None, **run_options): if isinstance(circuits, (QuantumCircuit, Schedule, ScheduleBlock)): circuits = [circuits] - if isinstance(circuits, (QasmQobj, PulseQobj)): - warnings.warn( - "Using a qobj for run() is deprecated as of qiskit-aer 0.14" - " and will be removed no sooner than 3 months from that release" - " date. Transpiled circuits should now be passed directly using" - " `backend.run(circuits, **run_options).", - DeprecationWarning, - stacklevel=2, - ) - if parameter_binds: - raise TypeError("Parameter binds can't be used with an input qobj") - # A work around to support both qobj options and run options until - # qobj is deprecated is to copy all the set qobj.config fields into - # run_options that don't override existing fields. This means set - # run_options fields will take precidence over the value for those - # fields that are set via assemble. - if not run_options: - run_options = circuits.config.__dict__ - else: - run_options = copy.copy(run_options) - for key, value in circuits.config.__dict__.items(): - if key not in run_options and value is not None: - run_options[key] = value - if "parameter_binds" in run_options: - parameter_binds = run_options.pop("parameter_binds") - return self._run_qobj(circuits, validate, parameter_binds, **run_options) - - only_circuits = True - only_pulse = True - for circ in circuits: - only_circuits &= isinstance(circ, QuantumCircuit) - only_pulse &= isinstance(circ, (ScheduleBlock, Schedule)) - - if only_circuits and not only_pulse: - if validate: - raise TypeError( - "bad input to run() function;" - "`validation` argument is only effective for input qobj" - ) - - executor = run_options.get("executor", None) - if executor is None and "executor" in self.options.__dict__: - executor = self.options.__dict__.get("executor", None) - if executor: - # This path remains for DASK execution to split a qobj insttance - # into sub-qobj instances. This will be replaced with _run_circuits path - # in the near releases - return self._run_qobj(circuits, validate, parameter_binds, **run_options) - else: - return self._run_circuits(circuits, parameter_binds, **run_options) - elif not only_circuits and only_pulse: - return self._run_qobj(circuits, validate, parameter_binds, **run_options) - elif not only_circuits and not only_pulse: - raise TypeError( - "bad input to run() function;" - "circuits and schedules cannot be mixed in a single run" - ) - else: - raise TypeError( - "bad input to run() function; circuits must be either circuits or schedules" - ) + return self._run_circuits(circuits, parameter_binds, **run_options) def _run_circuits(self, circuits, parameter_binds, **run_options): """Run circuits by generating native circuits.""" @@ -342,17 +283,201 @@ def max_circuits(self): @property def target(self): - if self._target is not None: + if self._from_backend: return self._target - tgt = convert_to_target(self.configuration(), self.properties(), None, NAME_MAPPING) + # make target for AerBackend + + # importing packages where they are needed, to avoid cyclic-import. + # pylint: disable=cyclic-import + from qiskit.transpiler.target import InstructionProperties + from qiskit.circuit.controlflow import ForLoopOp, IfElseOp, SwitchCaseOp, WhileLoopOp + from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping + from qiskit.circuit.parameter import Parameter + from qiskit.circuit.gate import Gate + from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES + from qiskit.providers.backend import QubitProperties + + required = ["measure", "delay"] + + configuration = self.configuration() + properties = self.properties() + + # Load Qiskit object representation + qiskit_inst_mapping = get_standard_gate_name_mapping() + qiskit_inst_mapping.update(NAME_MAPPING) + + qiskit_control_flow_mapping = { + "if_else": IfElseOp, + "while_loop": WhileLoopOp, + "for_loop": ForLoopOp, + "switch_case": SwitchCaseOp, + } + in_data = {"num_qubits": configuration.num_qubits} + + # Parse global configuration properties + if hasattr(configuration, "dt"): + in_data["dt"] = configuration.dt + if hasattr(configuration, "timing_constraints"): + in_data.update(configuration.timing_constraints) + + # Create instruction property placeholder from backend configuration + basis_gates = set(getattr(configuration, "basis_gates", [])) + supported_instructions = set(getattr(configuration, "supported_instructions", [])) + gate_configs = {gate.name: gate for gate in configuration.gates} + all_instructions = set.union( + basis_gates, set(required), supported_instructions.intersection(CONTROL_FLOW_OP_NAMES) + ) + inst_name_map = {} # type: Dict[str, Instruction] + + faulty_ops = set() + faulty_qubits = set() + unsupported_instructions = [] + + # Create name to Qiskit instruction object repr mapping + for name in all_instructions: + if name in qiskit_control_flow_mapping: + continue + if name in qiskit_inst_mapping: + inst_name_map[name] = qiskit_inst_mapping[name] + elif name in gate_configs: + this_config = gate_configs[name] + params = list(map(Parameter, getattr(this_config, "parameters", []))) + coupling_map = getattr(this_config, "coupling_map", []) + inst_name_map[name] = Gate( + name=name, + num_qubits=len(coupling_map[0]) if coupling_map else 0, + params=params, + ) + else: + warnings.warn( + f"No gate definition for {name} can be found and is being excluded " + "from the generated target.", + RuntimeWarning, + ) + unsupported_instructions.append(name) + + for name in unsupported_instructions: + all_instructions.remove(name) + + # Create inst properties placeholder + # Without any assignment, properties value is None, + # which defines a global instruction that can be applied to any qubit(s). + # The None value behaves differently from an empty dictionary. + # See API doc of Target.add_instruction for details. + prop_name_map = dict.fromkeys(all_instructions) + for name in all_instructions: + if name in gate_configs: + if coupling_map := getattr(gate_configs[name], "coupling_map", None): + # Respect operational qubits that gate configuration defines + # This ties instruction to particular qubits even without properties information. + # Note that each instruction is considered to be ideal unless + # its spec (e.g. error, duration) is bound by the properties object. + prop_name_map[name] = dict.fromkeys(map(tuple, coupling_map)) + + # Populate instruction properties + if properties: + + def _get_value(prop_dict, prop_name): + if ndval := prop_dict.get(prop_name, None): + return ndval[0] + return None + + # is_qubit_operational is a bit of expensive operation so precache the value + faulty_qubits = { + q for q in range(configuration.num_qubits) if not properties.is_qubit_operational(q) + } + + qubit_properties = [] + for qi in range(0, configuration.num_qubits): + # TODO faulty qubit handling might be needed since + # faulty qubit reporting qubit properties doesn't make sense. + try: + prop_dict = properties.qubit_property(qubit=qi) + except KeyError: + continue + qubit_properties.append( + QubitProperties( + t1=prop_dict.get("T1", (None, None))[0], + t2=prop_dict.get("T2", (None, None))[0], + frequency=prop_dict.get("frequency", (None, None))[0], + ) + ) + in_data["qubit_properties"] = qubit_properties + + for name in all_instructions: + for qubits, params in properties.gate_property(name).items(): + if set.intersection( + faulty_qubits, qubits + ) or not properties.is_gate_operational(name, qubits): + try: + # Qubits might be pre-defined by the gate config + # However properties objects says the qubits is non-operational + del prop_name_map[name][qubits] + except KeyError: + pass + faulty_ops.add((name, qubits)) + continue + if prop_name_map[name] is None: + prop_name_map[name] = {} + prop_name_map[name][qubits] = InstructionProperties( + error=_get_value(params, "gate_error"), + duration=_get_value(params, "gate_length"), + ) + if isinstance(prop_name_map[name], dict) and any( + v is None for v in prop_name_map[name].values() + ): + # Properties provides gate properties only for subset of qubits + # Associated qubit set might be defined by the gate config here + logger.info( + "Gate properties of instruction %s are not provided for every qubits. " + "This gate is ideal for some qubits and the rest is with finite error. " + "Created backend target may confuse error-aware circuit optimization.", + name, + ) + + # Measure instruction property is stored in qubit property + prop_name_map["measure"] = {} + + for qubit_idx in range(configuration.num_qubits): + if qubit_idx in faulty_qubits: + continue + qubit_prop = properties.qubit_property(qubit_idx) + prop_name_map["measure"][(qubit_idx,)] = InstructionProperties( + error=_get_value(qubit_prop, "readout_error"), + duration=_get_value(qubit_prop, "readout_length"), + ) + + for op in required: + # Map required ops to each operational qubit + if prop_name_map[op] is None: + prop_name_map[op] = { + (q,): None for q in range(configuration.num_qubits) if q not in faulty_qubits + } + + # Add parsed properties to target + target = Target(**in_data) + for inst_name in all_instructions: + if inst_name in qiskit_control_flow_mapping: + # Control flow operator doesn't have gate property. + target.add_instruction( + instruction=qiskit_control_flow_mapping[inst_name], + name=inst_name, + ) + else: + target.add_instruction( + instruction=inst_name_map[inst_name], + properties=prop_name_map.get(inst_name, None), + name=inst_name, + ) + if self._coupling_map is not None: - tgt._coupling_graph = self._coupling_map.graph.copy() - return tgt + target._coupling_graph = self._coupling_map.graph.copy() + return target def set_max_qubits(self, max_qubits): """Set maximun number of qubits to be used for this backend.""" - if self._target is None: + if not self._from_backend: self._configuration.n_qubits = max_qubits self._set_configuration_option("n_qubits", max_qubits) diff --git a/qiskit_aer/backends/backend_utils.py b/qiskit_aer/backends/backend_utils.py index a2bd628c0e..a01958b7e5 100644 --- a/qiskit_aer/backends/backend_utils.py +++ b/qiskit_aer/backends/backend_utils.py @@ -17,6 +17,8 @@ import os from math import log2 +from types import SimpleNamespace + import psutil from qiskit.circuit import QuantumCircuit from qiskit.qobj import QasmQobjInstruction @@ -559,3 +561,44 @@ def circuit_optypes(circuit): optypes.update(type(instruction.operation).mro()) optypes.discard(object) return optypes + + +class CircuitHeader(SimpleNamespace): + """A class used to represent a dictionary header in circuit objects.""" + + def __init__(self, **kwargs): + """Instantiate a new circuit dict field object. + + Args: + kwargs: arbitrary keyword arguments that can be accessed as + attributes of the object. + """ + self.__dict__.update(kwargs) + + def to_dict(self): + """Return a dictionary format representation of the circuit. + + Returns: + dict: The dictionary form of the CircuitHeader. + """ + return self.__dict__ + + @classmethod + def from_dict(cls, data): + """Create a new header object from a dictionary. + + Args: + data (dict): A dictionary representing the header to create. It + will be in the same format as output by :func:`to_dict`. + + Returns: + CircuitHeader: The CircuitHeader from the input dictionary. + """ + + return cls(**data) + + def __eq__(self, other): + if isinstance(other, self.__class__): + if self.__dict__ == other.__dict__: + return True + return False diff --git a/qiskit_aer/backends/backendconfiguration.py b/qiskit_aer/backends/backendconfiguration.py new file mode 100644 index 0000000000..93f6aed97f --- /dev/null +++ b/qiskit_aer/backends/backendconfiguration.py @@ -0,0 +1,394 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019, 2024 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Aer backend configuration +""" +import copy + + +class GateConfig: + """Class representing a Gate Configuration + + Attributes: + name: the gate name as it will be referred to in OpenQASM. + parameters: variable names for the gate parameters (if any). + qasm_def: definition of this gate in terms of OpenQASM 2 primitives U + and CX. + """ + + def __init__( + self, + name, + parameters, + qasm_def, + coupling_map=None, + latency_map=None, + conditional=None, + description=None, + ): + """Initialize a GateConfig object + + Args: + name (str): the gate name as it will be referred to in OpenQASM. + parameters (list): variable names for the gate parameters (if any) + as a list of strings. + qasm_def (str): definition of this gate in terms of OpenQASM 2 primitives U and CX. + coupling_map (list): An optional coupling map for the gate. In + the form of a list of lists of integers representing the qubit + groupings which are coupled by this gate. + latency_map (list): An optional map of latency for the gate. In the + the form of a list of lists of integers of either 0 or 1 + representing an array of dimension + len(coupling_map) X n_registers that specifies the register + latency (1: fast, 0: slow) conditional operations on the gate + conditional (bool): Optionally specify whether this gate supports + conditional operations (true/false). If this is not specified, + then the gate inherits the conditional property of the backend. + description (str): Description of the gate operation + """ + + self.name = name + self.parameters = parameters + self.qasm_def = qasm_def + # coupling_map with length 0 is invalid + if coupling_map: + self.coupling_map = coupling_map + # latency_map with length 0 is invalid + if latency_map: + self.latency_map = latency_map + if conditional is not None: + self.conditional = conditional + if description is not None: + self.description = description + + @classmethod + def from_dict(cls, data): + """Create a new GateConfig object from a dictionary. + + Args: + data (dict): A dictionary representing the GateConfig to create. + It will be in the same format as output by + :func:`to_dict`. + + Returns: + GateConfig: The GateConfig from the input dictionary. + """ + return cls(**data) + + def to_dict(self): + """Return a dictionary format representation of the GateConfig. + + Returns: + dict: The dictionary form of the GateConfig. + """ + out_dict = { + "name": self.name, + "parameters": self.parameters, + "qasm_def": self.qasm_def, + } + if hasattr(self, "coupling_map"): + out_dict["coupling_map"] = self.coupling_map + if hasattr(self, "latency_map"): + out_dict["latency_map"] = self.latency_map + if hasattr(self, "conditional"): + out_dict["conditional"] = self.conditional + if hasattr(self, "description"): + out_dict["description"] = self.description + return out_dict + + def __eq__(self, other): + if isinstance(other, GateConfig): + if self.to_dict() == other.to_dict(): + return True + return False + + def __repr__(self): + out_str = f"GateConfig({self.name}, {self.parameters}, {self.qasm_def}" + for i in ["coupling_map", "latency_map", "conditional", "description"]: + if hasattr(self, i): + out_str += ", " + repr(getattr(self, i)) + out_str += ")" + return out_str + + +class AerBackendConfiguration: + """Class representing an Aer Backend Configuration. + + Attributes: + backend_name: backend name. + backend_version: backend version in the form X.Y.Z. + n_qubits: number of qubits. + basis_gates: list of basis gates names on the backend. + gates: list of basis gates on the backend. + max_shots: maximum number of shots supported. + """ + + _data = {} + + def __init__( + self, + backend_name, + backend_version, + n_qubits, + basis_gates, + gates, + max_shots, + coupling_map, + supported_instructions=None, + dynamic_reprate_enabled=False, + rep_delay_range=None, + default_rep_delay=None, + max_experiments=None, + sample_name=None, + n_registers=None, + register_map=None, + configurable=None, + credits_required=None, + online_date=None, + display_name=None, + description=None, + tags=None, + dt=None, + dtm=None, + processor_type=None, + **kwargs, + ): + """Initialize a AerBackendConfiguration Object + + Args: + backend_name (str): The backend name + backend_version (str): The backend version in the form X.Y.Z + n_qubits (int): the number of qubits for the backend + basis_gates (list): The list of strings for the basis gates of the + backends + gates (list): The list of GateConfig objects for the basis gates of + the backend + max_shots (int): The maximum number of shots allowed on the backend + coupling_map (list): The coupling map for the device + supported_instructions (List[str]): Instructions supported by the backend. + dynamic_reprate_enabled (bool): whether delay between programs can be set dynamically + (ie via ``rep_delay``). Defaults to False. + rep_delay_range (List[float]): 2d list defining supported range of repetition + delays for backend in μs. First entry is lower end of the range, second entry is + higher end of the range. Optional, but will be specified when + ``dynamic_reprate_enabled=True``. + default_rep_delay (float): Value of ``rep_delay`` if not specified by user and + ``dynamic_reprate_enabled=True``. + max_experiments (int): The maximum number of experiments per job + sample_name (str): Sample name for the backend + n_registers (int): Number of register slots available for feedback + (if conditional is True) + register_map (list): An array of dimension n_qubits X + n_registers that specifies whether a qubit can store a + measurement in a certain register slot. + configurable (bool): True if the backend is configurable, if the + backend is a simulator + credits_required (bool): True if backend requires credits to run a + job. + online_date (datetime.datetime): The date that the device went online + display_name (str): Alternate name field for the backend + description (str): A description for the backend + tags (list): A list of string tags to describe the backend + dt (float): Qubit drive channel timestep in nanoseconds. + dtm (float): Measurement drive channel timestep in nanoseconds. + processor_type (dict): Processor type for this backend. A dictionary of the + form ``{"family": , "revision": , segment: }`` such as + ``{"family": "Canary", "revision": "1.0", segment: "A"}``. + + - family: Processor family of this backend. + - revision: Revision version of this processor. + - segment: Segment this processor belongs to within a larger chip. + + **kwargs: optional fields + """ + self._data = {} + + self.backend_name = backend_name + self.backend_version = backend_version + self.n_qubits = n_qubits + self.basis_gates = basis_gates + self.gates = gates + self.local = True + self.simulator = True + self.conditional = True + self.memory = True + self.max_shots = max_shots + self.coupling_map = coupling_map + if supported_instructions: + self.supported_instructions = supported_instructions + + self.dynamic_reprate_enabled = dynamic_reprate_enabled + if rep_delay_range: + self.rep_delay_range = [_rd * 1e-6 for _rd in rep_delay_range] # convert to sec + if default_rep_delay is not None: + self.default_rep_delay = default_rep_delay * 1e-6 # convert to sec + + # max_experiments must be >=1 + if max_experiments: + self.max_experiments = max_experiments + if sample_name is not None: + self.sample_name = sample_name + # n_registers must be >=1 + if n_registers: + self.n_registers = 1 + # register_map must have at least 1 entry + if register_map: + self.register_map = register_map + if configurable is not None: + self.configurable = configurable + if credits_required is not None: + self.credits_required = credits_required + if online_date is not None: + self.online_date = online_date + if display_name is not None: + self.display_name = display_name + if description is not None: + self.description = description + if tags is not None: + self.tags = tags + # Add pulse properties here because some backends do not + # fit within the Qasm / Pulse backend partitioning in Qiskit + if dt is not None: + self.dt = dt * 1e-9 + if dtm is not None: + self.dtm = dtm * 1e-9 + if processor_type is not None: + self.processor_type = processor_type + + # convert lo range from GHz to Hz + if "qubit_lo_range" in kwargs: + kwargs["qubit_lo_range"] = [ + [min_range * 1e9, max_range * 1e9] + for (min_range, max_range) in kwargs["qubit_lo_range"] + ] + + if "meas_lo_range" in kwargs: + kwargs["meas_lo_range"] = [ + [min_range * 1e9, max_range * 1e9] + for (min_range, max_range) in kwargs["meas_lo_range"] + ] + + # convert rep_times from μs to sec + if "rep_times" in kwargs: + kwargs["rep_times"] = [_rt * 1e-6 for _rt in kwargs["rep_times"]] + + self._data.update(kwargs) + + def __getattr__(self, name): + try: + return self._data[name] + except KeyError as ex: + raise AttributeError(f"Attribute {name} is not defined") from ex + + @classmethod + def from_dict(cls, data): + """Create a new GateConfig object from a dictionary. + + Args: + data (dict): A dictionary representing the GateConfig to create. + It will be in the same format as output by + :func:`to_dict`. + Returns: + GateConfig: The GateConfig from the input dictionary. + """ + in_data = copy.copy(data) + gates = [GateConfig.from_dict(x) for x in in_data.pop("gates")] + in_data["gates"] = gates + return cls(**in_data) + + def to_dict(self): + """Return a dictionary format representation of the GateConfig. + + Returns: + dict: The dictionary form of the GateConfig. + """ + out_dict = { + "backend_name": self.backend_name, + "backend_version": self.backend_version, + "n_qubits": self.n_qubits, + "basis_gates": self.basis_gates, + "gates": [x.to_dict() for x in self.gates], + "local": self.local, + "simulator": self.simulator, + "conditional": self.conditional, + "memory": self.memory, + "max_shots": self.max_shots, + "coupling_map": self.coupling_map, + "dynamic_reprate_enabled": self.dynamic_reprate_enabled, + } + + if hasattr(self, "supported_instructions"): + out_dict["supported_instructions"] = self.supported_instructions + + if hasattr(self, "rep_delay_range"): + out_dict["rep_delay_range"] = [_rd * 1e6 for _rd in self.rep_delay_range] + if hasattr(self, "default_rep_delay"): + out_dict["default_rep_delay"] = self.default_rep_delay * 1e6 + + for kwarg in [ + "max_experiments", + "sample_name", + "n_registers", + "register_map", + "configurable", + "credits_required", + "online_date", + "display_name", + "description", + "tags", + "dt", + "dtm", + "processor_type", + ]: + if hasattr(self, kwarg): + out_dict[kwarg] = getattr(self, kwarg) + + out_dict.update(self._data) + + if "dt" in out_dict: + out_dict["dt"] *= 1e9 + if "dtm" in out_dict: + out_dict["dtm"] *= 1e9 + + # Use GHz in dict + if "qubit_lo_range" in out_dict: + out_dict["qubit_lo_range"] = [ + [min_range * 1e-9, max_range * 1e-9] + for (min_range, max_range) in out_dict["qubit_lo_range"] + ] + + if "meas_lo_range" in out_dict: + out_dict["meas_lo_range"] = [ + [min_range * 1e-9, max_range * 1e-9] + for (min_range, max_range) in out_dict["meas_lo_range"] + ] + + return out_dict + + @property + def num_qubits(self): + """Returns the number of qubits. + + In future, `n_qubits` should be replaced in favor of `num_qubits` for consistent use + throughout Qiskit. Until this is properly refactored, this property serves as intermediate + solution. + """ + return self.n_qubits + + def __eq__(self, other): + if isinstance(other, AerBackendConfiguration): + if self.to_dict() == other.to_dict(): + return True + return False + + def __contains__(self, item): + return item in self.__dict__ diff --git a/qiskit_aer/backends/backendproperties.py b/qiskit_aer/backends/backendproperties.py new file mode 100644 index 0000000000..e026412509 --- /dev/null +++ b/qiskit_aer/backends/backendproperties.py @@ -0,0 +1,591 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019, 2024 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Aer backend properties +""" +import copy +import datetime +import warnings +from typing import Any, Iterable, Tuple, Union, Dict +import dateutil.parser +from qiskit.providers.exceptions import BackendPropertyError +from qiskit.utils.units import apply_prefix +from qiskit.transpiler.target import Target + + +PropertyT = Tuple[Any, datetime.datetime] + + +class Nduv: + """Class representing name-date-unit-value + + Attributes: + date: date. + name: name. + unit: unit. + value: value. + """ + + def __init__(self, date, name, unit, value): + """Initialize a new name-date-unit-value object + + Args: + date (datetime.datetime): Date field + name (str): Name field + unit (str): Nduv unit + value (float): The value of the Nduv + """ + self.date = date + self.name = name + self.unit = unit + self.value = value + + @classmethod + def from_dict(cls, data): + """Create a new Nduv object from a dictionary. + + Args: + data (dict): A dictionary representing the Nduv to create. + It will be in the same format as output by + :func:`to_dict`. + + Returns: + Nduv: The Nduv from the input dictionary. + """ + return cls(**data) + + def to_dict(self): + """Return a dictionary format representation of the object. + + Returns: + dict: The dictionary form of the Nduv. + """ + out_dict = { + "date": self.date, + "name": self.name, + "unit": self.unit, + "value": self.value, + } + return out_dict + + def __eq__(self, other): + if isinstance(other, Nduv): + if self.to_dict() == other.to_dict(): + return True + return False + + def __repr__(self): + return f"Nduv({repr(self.date)}, {self.name}, {self.unit}, {self.value})" + + +class GateProperties: + """Class representing a gate's properties + + Attributes: + qubits: qubits. + gate: gate. + parameters: parameters. + """ + + _data = {} + + def __init__(self, qubits, gate, parameters, **kwargs): + """Initialize a new :class:`GateProperties` object + + Args: + qubits (list): A list of integers representing qubits + gate (str): The gates name + parameters (list): List of :class:`Nduv` objects for the + name-date-unit-value for the gate + kwargs: Optional additional fields + """ + self._data = {} + self.qubits = qubits + self.gate = gate + self.parameters = parameters + self._data.update(kwargs) + + def __getattr__(self, name): + try: + return self._data[name] + except KeyError as ex: + raise AttributeError(f"Attribute {name} is not defined") from ex + + @classmethod + def from_dict(cls, data): + """Create a new Gate object from a dictionary. + + Args: + data (dict): A dictionary representing the Gate to create. + It will be in the same format as output by + :func:`to_dict`. + + Returns: + GateProperties: The Nduv from the input dictionary. + """ + in_data = {} + for key, value in data.items(): + if key == "parameters": + in_data[key] = list(map(Nduv.from_dict, value)) + else: + in_data[key] = value + return cls(**in_data) + + def to_dict(self): + """Return a dictionary format representation of the BackendStatus. + + Returns: + dict: The dictionary form of the Gate. + """ + out_dict = {} + out_dict["qubits"] = self.qubits + out_dict["gate"] = self.gate + out_dict["parameters"] = [x.to_dict() for x in self.parameters] + out_dict.update(self._data) + return out_dict + + def __eq__(self, other): + if isinstance(other, GateProperties): + if self.to_dict() == other.to_dict(): + return True + return False + + +class AerBackendProperties: + """Class representing Aer backend properties + + This holds backend properties measured by the provider. All properties + which are provided optionally. These properties may describe qubits, gates, + or other general properties of the backend. + """ + + _data = {} + + def __init__( + self, backend_name, backend_version, last_update_date, qubits, gates, general, **kwargs + ): + """Initialize a BackendProperties instance. + + Args: + backend_name (str): Backend name. + backend_version (str): Backend version in the form X.Y.Z. + last_update_date (datetime.datetime or str): Last date/time that a property was + updated. If specified as a ``str``, it must be in ISO format. + qubits (list): System qubit parameters as a list of lists of + :class:`Nduv` objects + gates (list): System gate parameters as a list of :class:`GateProperties` + objects + general (list): General parameters as a list of :class:`Nduv` + objects + kwargs: optional additional fields + """ + self._data = {} + self.backend_name = backend_name + self.backend_version = backend_version + if isinstance(last_update_date, str): + last_update_date = dateutil.parser.isoparse(last_update_date) + self.last_update_date = last_update_date + self.general = general + self.qubits = qubits + self.gates = gates + + self._qubits = {} + for qubit, props in enumerate(qubits): + formatted_props = {} + for prop in props: + value = self._apply_prefix(prop.value, prop.unit) + formatted_props[prop.name] = (value, prop.date) + self._qubits[qubit] = formatted_props + + self._gates = {} + for gate in gates: + if gate.gate not in self._gates: + self._gates[gate.gate] = {} + formatted_props = {} + for param in gate.parameters: + value = self._apply_prefix(param.value, param.unit) + formatted_props[param.name] = (value, param.date) + self._gates[gate.gate][tuple(gate.qubits)] = formatted_props + self._data.update(kwargs) + + def __getattr__(self, name): + try: + return self._data[name] + except KeyError as ex: + raise AttributeError(f"Attribute {name} is not defined") from ex + + @classmethod + def from_dict(cls, data): + """Create a new BackendProperties object from a dictionary. + + Args: + data (dict): A dictionary representing the BackendProperties to create. It will be in + the same format as output by :meth:`to_dict`. + + Returns: + BackendProperties: The BackendProperties from the input dictionary. + """ + in_data = copy.copy(data) + backend_name = in_data.pop("backend_name") + backend_version = in_data.pop("backend_version") + last_update_date = in_data.pop("last_update_date") + qubits = [] + for qubit in in_data.pop("qubits"): + nduvs = [] + for nduv in qubit: + nduvs.append(Nduv.from_dict(nduv)) + qubits.append(nduvs) + gates = [GateProperties.from_dict(x) for x in in_data.pop("gates")] + general = [Nduv.from_dict(x) for x in in_data.pop("general")] + + return cls( + backend_name, backend_version, last_update_date, qubits, gates, general, **in_data + ) + + def to_dict(self): + """Return a dictionary format representation of the BackendProperties. + + Returns: + dict: The dictionary form of the BackendProperties. + """ + out_dict = { + "backend_name": self.backend_name, + "backend_version": self.backend_version, + "last_update_date": self.last_update_date, + } + out_dict["qubits"] = [] + for qubit in self.qubits: + qubit_props = [] + for item in qubit: + qubit_props.append(item.to_dict()) + out_dict["qubits"].append(qubit_props) + out_dict["gates"] = [x.to_dict() for x in self.gates] + out_dict["general"] = [x.to_dict() for x in self.general] + out_dict.update(self._data) + return out_dict + + def __eq__(self, other): + if isinstance(other, AerBackendProperties): + if self.to_dict() == other.to_dict(): + return True + return False + + def gate_property( + self, + gate: str, + qubits: Union[int, Iterable[int]] = None, + name: str = None, + ) -> Union[ + Dict[Tuple[int, ...], Dict[str, PropertyT]], + Dict[str, PropertyT], + PropertyT, + ]: + """ + Return the property of the given gate. + + Args: + gate: Name of the gate. + qubits: The qubit to find the property for. + name: Optionally used to specify which gate property to return. + + Returns: + Gate property as a tuple of the value and the time it was measured. + + Raises: + BackendPropertyError: If the property is not found or name is + specified but qubit is not. + """ + try: + result = self._gates[gate] + if qubits is not None: + if isinstance(qubits, int): + qubits = (qubits,) + result = result[tuple(qubits)] + if name: + result = result[name] + elif name: + raise BackendPropertyError(f"Provide qubits to get {name} of {gate}") + except KeyError as ex: + raise BackendPropertyError(f"Could not find the desired property for {gate}") from ex + return result + + def faulty_qubits(self): + """Return a list of faulty qubits.""" + faulty = [] + for qubit in self._qubits: + if not self.is_qubit_operational(qubit): + faulty.append(qubit) + return faulty + + def faulty_gates(self): + """Return a list of faulty gates.""" + faulty = [] + for gate in self.gates: + if not self.is_gate_operational(gate.gate, gate.qubits): + faulty.append(gate) + return faulty + + def is_gate_operational(self, gate: str, qubits: Union[int, Iterable[int]] = None) -> bool: + """ + Return the operational status of the given gate. + + Args: + gate: Name of the gate. + qubits: The qubit to find the operational status for. + + Returns: + bool: Operational status of the given gate. True if the gate is operational, + False otherwise. + """ + properties = self.gate_property(gate, qubits) + if "operational" in properties: + return bool(properties["operational"][0]) + return True # if property operational not existent, then True. + + def gate_error(self, gate: str, qubits: Union[int, Iterable[int]]) -> float: + """ + Return gate error estimates from backend properties. + + Args: + gate: The gate for which to get the error. + qubits: The specific qubits for the gate. + + Returns: + Gate error of the given gate and qubit(s). + """ + return self.gate_property(gate, qubits, "gate_error")[0] # Throw away datetime at index 1 + + def gate_length(self, gate: str, qubits: Union[int, Iterable[int]]) -> float: + """ + Return the duration of the gate in units of seconds. + + Args: + gate: The gate for which to get the duration. + qubits: The specific qubits for the gate. + + Returns: + Gate length of the given gate and qubit(s). + """ + return self.gate_property(gate, qubits, "gate_length")[0] # Throw away datetime at index 1 + + def qubit_property( + self, + qubit: int, + name: str = None, + ) -> Union[ + Dict[str, PropertyT], + PropertyT, + ]: + """ + Return the property of the given qubit. + + Args: + qubit: The property to look for. + name: Optionally used to specify within the hierarchy which property to return. + + Returns: + Qubit property as a tuple of the value and the time it was measured. + + Raises: + BackendPropertyError: If the property is not found. + """ + try: + result = self._qubits[qubit] + if name is not None: + result = result[name] + except KeyError as ex: + formatted_name = "y '" + name + "'" if name else "ies" + raise BackendPropertyError( + f"Couldn't find the propert{formatted_name} for qubit {qubit}." + ) from ex + return result + + def t1(self, qubit: int) -> float: # pylint: disable=invalid-name + """ + Return the T1 time of the given qubit. + + Args: + qubit: Qubit for which to return the T1 time of. + + Returns: + T1 time of the given qubit. + """ + return self.qubit_property(qubit, "T1")[0] # Throw away datetime at index 1 + + def t2(self, qubit: int) -> float: # pylint: disable=invalid-name + """ + Return the T2 time of the given qubit. + + Args: + qubit: Qubit for which to return the T2 time of. + + Returns: + T2 time of the given qubit. + """ + return self.qubit_property(qubit, "T2")[0] # Throw away datetime at index 1 + + def frequency(self, qubit: int) -> float: + """ + Return the frequency of the given qubit. + + Args: + qubit: Qubit for which to return frequency of. + + Returns: + Frequency of the given qubit. + """ + return self.qubit_property(qubit, "frequency")[0] # Throw away datetime at index 1 + + def readout_error(self, qubit: int) -> float: + """ + Return the readout error of the given qubit. + + Args: + qubit: Qubit for which to return the readout error of. + + Return: + Readout error of the given qubit. + """ + return self.qubit_property(qubit, "readout_error")[0] # Throw away datetime at index 1 + + def readout_length(self, qubit: int) -> float: + """ + Return the readout length [sec] of the given qubit. + + Args: + qubit: Qubit for which to return the readout length of. + + Return: + Readout length of the given qubit. + """ + return self.qubit_property(qubit, "readout_length")[0] # Throw away datetime at index 1 + + def is_qubit_operational(self, qubit: int) -> bool: + """ + Return the operational status of the given qubit. + + Args: + qubit: Qubit for which to return operational status of. + + Returns: + Operational status of the given qubit. + """ + properties = self.qubit_property(qubit) + if "operational" in properties: + return bool(properties["operational"][0]) + return True # if property operational not existent, then True. + + def _apply_prefix(self, value: float, unit: str) -> float: + """ + Given a SI unit prefix and value, apply the prefix to convert to + standard SI unit. + + Args: + value: The number to apply prefix to. + unit: String prefix. + + Returns: + Converted value. + + Raises: + BackendPropertyError: If the units aren't recognized. + """ + try: + return apply_prefix(value, unit) + except Exception as ex: + raise BackendPropertyError(f"Could not understand units: {unit}") from ex + + +def target_to_backend_properties(target: Target): + """Convert a :class:`qiskit.transpiler.Target` into a legacy :class:`~.AerBackendProperties`""" + + properties_dict: dict[str, Any] = { + "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 getattr(props, "duration", None) is not None: + property_list.append( + { + "date": datetime.datetime.now(datetime.timezone.utc), + "name": "gate_length", + "unit": "s", + "value": props.duration, + } + ) + if getattr(props, "error", None) is not None: + property_list.append( + { + "date": datetime.datetime.now(datetime.timezone.utc), + "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: dict[int, Any] = {} + if target.num_qubits is not None: + qubit_props = {x: None for x in range(target.num_qubits)} + for qargs, props in qargs_list.items(): + if qargs is None: + continue + qubit = qargs[0] + props_list = [] + if getattr(props, "error", None) is not None: + props_list.append( + { + "date": datetime.datetime.now(datetime.timezone.utc), + "name": "readout_error", + "unit": "", + "value": props.error, + } + ) + if getattr(props, "duration", None) is not None: + props_list.append( + { + "date": datetime.datetime.now(datetime.timezone.utc), + "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 + with warnings.catch_warnings(): + # This raises BackendProperties internally + warnings.filterwarnings("ignore", category=DeprecationWarning) + return AerBackendProperties.from_dict(properties_dict) + else: + return None diff --git a/qiskit_aer/backends/name_mapping.py b/qiskit_aer/backends/name_mapping.py index 5af58e3b07..b9dde39b7b 100644 --- a/qiskit_aer/backends/name_mapping.py +++ b/qiskit_aer/backends/name_mapping.py @@ -16,6 +16,7 @@ """ from qiskit.circuit import ControlledGate, Parameter from qiskit.circuit.reset import Reset +from qiskit.circuit.store import Store from qiskit.circuit.library import ( U2Gate, RGate, @@ -301,4 +302,5 @@ def __init__(self, num_ctrl_qubits, ctrl_state=None): "save_probs_ket": SaveProbabilitiesDict, "save_probs": SaveProbabilities, "reset": Reset(), + "store": Store, } diff --git a/qiskit_aer/backends/qasm_simulator.py b/qiskit_aer/backends/qasm_simulator.py index 174d201da1..9839a15d5d 100644 --- a/qiskit_aer/backends/qasm_simulator.py +++ b/qiskit_aer/backends/qasm_simulator.py @@ -16,13 +16,15 @@ import copy import logging from warnings import warn +from qiskit.providers import convert_to_target from qiskit.providers.options import Options -from qiskit.providers.models import QasmBackendConfiguration -from qiskit.providers.backend import BackendV2 +from qiskit.providers.backend import BackendV2, BackendV1 from ..version import __version__ from ..aererror import AerError from .aerbackend import AerBackend +from .backendconfiguration import AerBackendConfiguration +from .backendproperties import target_to_backend_properties from .backend_utils import ( cpp_execute_qobj, cpp_execute_circuits, @@ -35,6 +37,7 @@ # pylint: disable=import-error, no-name-in-module, abstract-method from .controller_wrappers import aer_controller_execute +from .name_mapping import NAME_MAPPING logger = logging.getLogger(__name__) @@ -400,7 +403,6 @@ class QasmSimulator(AerBackend): "simulator": True, "local": True, "conditional": True, - "open_pulse": False, "memory": True, "max_shots": int(1e6), "description": "A C++ QasmQobj simulator with noise", @@ -450,7 +452,7 @@ def __init__(self, configuration=None, properties=None, provider=None, **backend # Default configuration if configuration is None: - configuration = QasmBackendConfiguration.from_dict(QasmSimulator._DEFAULT_CONFIGURATION) + configuration = AerBackendConfiguration.from_dict(QasmSimulator._DEFAULT_CONFIGURATION) else: configuration.open_pulse = False @@ -459,7 +461,7 @@ def __init__(self, configuration=None, properties=None, provider=None, **backend self._cached_basis_gates = self._DEFAULT_BASIS_GATES super().__init__( - configuration, properties=properties, provider=provider, backend_options=backend_options + configuration, properties, provider=provider, backend_options=backend_options ) def __repr__(self): @@ -534,27 +536,59 @@ def _default_options(cls): def from_backend(cls, backend, **options): """Initialize simulator from backend.""" if isinstance(backend, BackendV2): - raise AerError("QasmSimulator.from_backend does not currently support V2 Backends.") - # pylint: disable=import-outside-toplevel - # Avoid cyclic import - from ..noise.noise_model import NoiseModel - - # Get configuration and properties from backend - configuration = copy.copy(backend.configuration()) - properties = copy.copy(backend.properties()) + if backend.description is None: + description = "created by AerSimulator.from_backend" + else: + description = backend.description + + configuration = AerBackendConfiguration( + backend_name=f"aer_simulator_from({backend.name})", + backend_version=backend.backend_version, + n_qubits=backend.num_qubits, + basis_gates=backend.operation_names, + gates=[], + max_shots=int(1e6), + coupling_map=list(backend.coupling_map.get_edges()), + max_experiments=backend.max_circuits, + description=description, + ) + properties = target_to_backend_properties(backend.target) + target = backend.target + elif isinstance(backend, BackendV1): + # BackendV1 will be removed in Qiskit 2.0, so we will remove this soon + warn( + " from_backend using V1 based backend is deprecated as of Aer 0.15" + " and will be removed no sooner than 3 months from that release" + " date. Please use backends based on V2.", + DeprecationWarning, + stacklevel=2, + ) + # Get configuration and properties from backend + configuration = backend.configuration() + properties = copy.copy(backend.properties()) - # Customize configuration name - name = configuration.backend_name - configuration.backend_name = f"qasm_simulator({name})" + # Customize configuration name + name = configuration.backend_name + configuration.backend_name = f"aer_simulator_from({name})" + target = convert_to_target(configuration, properties, None, NAME_MAPPING) + else: + raise TypeError( + "The backend argument requires a BackendV2 or BackendV1 object, " + f"not a {type(backend)} object" + ) # Use automatic noise model if none is provided if "noise_model" not in options: + # pylint: disable=import-outside-toplevel + # Avoid cyclic import + from ..noise.noise_model import NoiseModel + noise_model = NoiseModel.from_backend(backend) if not noise_model.is_ideal(): 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 configuration(self): diff --git a/qiskit_aer/backends/statevector_simulator.py b/qiskit_aer/backends/statevector_simulator.py index 63fe03cdc3..af6fe35c6c 100644 --- a/qiskit_aer/backends/statevector_simulator.py +++ b/qiskit_aer/backends/statevector_simulator.py @@ -19,11 +19,11 @@ import psutil from qiskit.providers.options import Options -from qiskit.providers.models import QasmBackendConfiguration from ..aererror import AerError from ..version import __version__ from .aerbackend import AerBackend +from .backendconfiguration import AerBackendConfiguration from .backend_utils import ( cpp_execute_qobj, available_devices, @@ -258,7 +258,7 @@ def __init__(self, configuration=None, properties=None, provider=None, **backend StatevectorSimulator._AVAILABLE_DEVICES = available_devices(self._controller) if configuration is None: - configuration = QasmBackendConfiguration.from_dict( + configuration = AerBackendConfiguration.from_dict( StatevectorSimulator._DEFAULT_CONFIGURATION ) else: @@ -360,7 +360,7 @@ def _validate(self, qobj): raise AerError(f"{name} does not support noise.") n_qubits = qobj.config.n_qubits - max_qubits = self.configuration().n_qubits + max_qubits = self.configuration()["n_qubits"] if n_qubits > max_qubits: raise AerError( f"Number of qubits ({n_qubits}) is greater than max ({max_qubits}) " diff --git a/qiskit_aer/backends/unitary_simulator.py b/qiskit_aer/backends/unitary_simulator.py index ef02150fe9..46d06a06b7 100644 --- a/qiskit_aer/backends/unitary_simulator.py +++ b/qiskit_aer/backends/unitary_simulator.py @@ -20,7 +20,6 @@ import psutil from qiskit.providers.options import Options -from qiskit.providers.models import QasmBackendConfiguration from ..aererror import AerError from ..version import __version__ @@ -36,6 +35,7 @@ add_final_save_op, map_legacy_method_config, ) +from .backendconfiguration import AerBackendConfiguration # pylint: disable=import-error, no-name-in-module, abstract-method from .controller_wrappers import aer_controller_execute @@ -244,7 +244,7 @@ def __init__(self, configuration=None, properties=None, provider=None, **backend UnitarySimulator._AVAILABLE_DEVICES = available_devices(self._controller) if configuration is None: - configuration = QasmBackendConfiguration.from_dict( + configuration = AerBackendConfiguration.from_dict( UnitarySimulator._DEFAULT_CONFIGURATION ) else: @@ -346,7 +346,7 @@ def _validate(self, qobj): raise AerError(f"{name} does not support noise.") n_qubits = qobj.config.n_qubits - max_qubits = self.configuration().n_qubits + max_qubits = self.configuration()["n_qubits"] if n_qubits > max_qubits: raise AerError( f"Number of qubits ({n_qubits}) is greater than " diff --git a/qiskit_aer/noise/errors/quantum_error.py b/qiskit_aer/noise/errors/quantum_error.py index 9d3d72e1fb..fac98c0bd7 100644 --- a/qiskit_aer/noise/errors/quantum_error.py +++ b/qiskit_aer/noise/errors/quantum_error.py @@ -312,9 +312,16 @@ def to_dict(self): for circ in self._circs: circ_inst = [] for inst in circ.data: - qobj_inst = inst.operation.assemble() - qobj_inst.qubits = [circ.find_bit(q).index for q in inst.qubits] - circ_inst.append(qobj_inst.to_dict()) + inst_dict = {} + inst_dict["name"] = inst.operation.name + inst_dict["qubits"] = [circ.find_bit(q).index for q in inst.qubits] + if inst.operation.params: + inst_dict["params"] = inst.operation.params + if inst.operation.label: + inst_dict["label"] = inst.operation.label + if inst.operation.condition: + inst_dict["condition"] = inst.operation.condition + circ_inst.append(inst_dict) instructions.append(circ_inst) # Construct error dict error = { diff --git a/qiskit_aer/noise/noise_model.py b/qiskit_aer/noise/noise_model.py index 6ff1f91760..d040cb18cf 100644 --- a/qiskit_aer/noise/noise_model.py +++ b/qiskit_aer/noise/noise_model.py @@ -381,6 +381,14 @@ def from_backend( ) dt = backend.dt elif backend_interface_version <= 1: + # BackendV1 will be removed in Qiskit 2.0, so we will remove this soon + warn( + " from_backend using V1 based backend is deprecated as of Aer 0.15" + " and will be removed no sooner than 3 months from that release" + " date. Please use backends based on V2.", + DeprecationWarning, + stacklevel=2, + ) properties = backend.properties() configuration = backend.configuration() basis_gates = configuration.basis_gates diff --git a/releasenotes/notes/fix_for_qiskit1.2-4924037bafe82ecc.yaml b/releasenotes/notes/fix_for_qiskit1.2-4924037bafe82ecc.yaml new file mode 100644 index 0000000000..d455ea2c3f --- /dev/null +++ b/releasenotes/notes/fix_for_qiskit1.2-4924037bafe82ecc.yaml @@ -0,0 +1,13 @@ +--- +fixes: + - | + - Clone configuration and properties from Qiskit to Aer #2197 + - Deprecation of V1 backends passed to `AerSimulator.from_backend()` + will be removed in Qiskit 2.0 + - target.qubit_properties is now rust function, so list can not be + updated by [] operator directly + - remove QobjExperimentHeader in AerCompiler this is from #2187 + - remove using qobj in AerBackend.run this will be removed by #2187 + - replace some V1 based fake backends to GenericBackendV2 + - Remove transpile for test using `UnitaryGate` or `DiagonalGate` + maybe issue in QIskit https://github.com/Qiskit/qiskit/issues/12953 diff --git a/src/framework/pybind_json.hpp b/src/framework/pybind_json.hpp index 108e1b34ec..0f23e7d0f2 100644 --- a/src/framework/pybind_json.hpp +++ b/src/framework/pybind_json.hpp @@ -221,10 +221,9 @@ json_t JSON::iterable_to_json_list(const py::handle &obj) { void std::to_json(json_t &js, const py::handle &obj) { static py::object PyNoiseModel = py::module::import("qiskit_aer.noise.noise_model").attr("NoiseModel"); - static py::object PyQasmQobj = - py::module::import("qiskit.qobj.qasm_qobj").attr("QasmQobj"); - static py::object PyQasmQobjHeader = - py::module::import("qiskit.qobj.common").attr("QobjExperimentHeader"); + static py::object PyCircuitHeader = + py::module::import("qiskit_aer.backends.backend_utils") + .attr("CircuitHeader"); if (py::isinstance(obj)) { js = obj.cast(); } else if (py::isinstance(obj)) { @@ -249,9 +248,7 @@ void std::to_json(json_t &js, const py::handle &obj) { return; } else if (py::isinstance(obj, PyNoiseModel)) { std::to_json(js, obj.attr("to_dict")()); - } else if (py::isinstance(obj, PyQasmQobj)) { - std::to_json(js, obj.attr("to_dict")()); - } else if (py::isinstance(obj, PyQasmQobjHeader)) { + } else if (py::isinstance(obj, PyCircuitHeader)) { std::to_json(js, obj.attr("to_dict")()); } else { auto type_str = std::string(py::str(obj.get_type())); diff --git a/test/terra/backends/aer_simulator/test_circuit.py b/test/terra/backends/aer_simulator/test_circuit.py index cd4c0f7806..42367b67ab 100644 --- a/test/terra/backends/aer_simulator/test_circuit.py +++ b/test/terra/backends/aer_simulator/test_circuit.py @@ -193,29 +193,6 @@ def test_metadata_protected(self): deepcopy(job.result()) - def test_run_qobj(self): - """Test qobj run""" - - qubits = QuantumRegister(3) - clbits = ClassicalRegister(3) - - circuit = QuantumCircuit(qubits, clbits) - circuit.h(qubits[0]) - circuit.cx(qubits[0], qubits[1]) - circuit.cx(qubits[0], qubits[2]) - - for q, c in zip(qubits, clbits): - circuit.measure(q, c) - - backend = self.backend() - - shots = 1000 - with self.assertWarns(DeprecationWarning): - result = backend.run(assemble(circuit), shots=shots).result() - - self.assertSuccess(result) - self.compare_counts(result, [circuit], [{"0x0": 500, "0x7": 500}], delta=0.05 * shots) - def test_numpy_integer_shots(self): """Test implicit cast of shot option from np.int_ to int.""" diff --git a/test/terra/backends/aer_simulator/test_executors.py b/test/terra/backends/aer_simulator/test_executors.py index 28fc99d57c..6f5f2fa336 100644 --- a/test/terra/backends/aer_simulator/test_executors.py +++ b/test/terra/backends/aer_simulator/test_executors.py @@ -26,20 +26,10 @@ from qiskit.quantum_info import Statevector from qiskit_aer.noise.noise_model import AerJSONEncoder from test.terra.reference import ref_kraus_noise -from qiskit_aer.jobs import AerJob, AerJobSet +from qiskit_aer.jobs import AerJob from test.terra.backends.simulator_test_case import SimulatorTestCase, supported_methods -DASK = False - -try: - from dask.distributed import LocalCluster, Client - - DASK = True -except ImportError: - DASK = False - - def run_random_circuits(backend, shots=None, **run_options): """Test random circuits on different executor fictures""" job_size = 10 @@ -107,58 +97,6 @@ def backend(self, **options): return super().backend(executor=self._test_executor, **options) -@ddt -class TestDaskExecutor(CBFixture): - """Tests of Dask executor""" - - @classmethod - def setUpClass(cls): - super().setUpClass() - if DASK: - cls._test_executor = Client(address=LocalCluster(n_workers=1, processes=True)) - - def setUp(self): - super().setUp() - if not DASK: - self.skipTest("Dask not installed, skipping ClusterBackend-dask tests") - - @supported_methods(["statevector"], [None, 1, 2, 3]) - def test_random_circuits_job(self, method, device, max_job_size): - """Test random circuits with custom executor.""" - shots = 4000 - backend = self.backend(method=method, device=device, max_job_size=max_job_size) - result, circuits, targets = run_random_circuits(backend, shots=shots) - self.assertSuccess(result) - self.compare_counts(result, circuits, targets, hex_counts=False, delta=0.05 * shots) - - @supported_methods(["statevector"], [None, 1, 1, 1], [None, 100, 500, 1000]) - def test_noise_circuits_job(self, method, device, max_job_size, max_shot_size): - """Test random circuits with custom executor.""" - shots = 4000 - backend = self.backend( - method=method, device=device, max_job_size=max_job_size, max_shot_size=max_shot_size - ) - - circuits = ref_kraus_noise.kraus_gate_error_circuits() - noise_models = ref_kraus_noise.kraus_gate_error_noise_models() - targets = ref_kraus_noise.kraus_gate_error_counts(shots) - - for circuit, noise_model, target in zip(circuits, noise_models, targets): - backend.set_options(noise_model=noise_model) - result = backend.run(circuit, shots=shots).result() - self.assertSuccess(result) - self.compare_counts(result, [circuit], [target], delta=0.05 * shots) - - @supported_methods(["statevector"], [None, 1, 2, 3]) - def test_result_time_val(self, method, device, max_job_size): - """Test random circuits with custom executor.""" - shots = 4000 - backend = self.backend(method=method, device=device, max_job_size=max_job_size) - result, _, _ = run_random_circuits(backend, shots=shots) - self.assertSuccess(result) - self.assertGreaterEqual(result.time_taken, 0) - - @ddt class TestThreadPoolExecutor(CBFixture): """Tests of ThreadPool executor""" diff --git a/test/terra/backends/aer_simulator/test_measure.py b/test/terra/backends/aer_simulator/test_measure.py index 4d401d802b..5b6f0a8c66 100644 --- a/test/terra/backends/aer_simulator/test_measure.py +++ b/test/terra/backends/aer_simulator/test_measure.py @@ -358,7 +358,6 @@ def test_mps_measure_subset_alg_qv(self): circuits.append(circuit) circuits[0].measure([0, 2, 4], [0, 2, 4]) circuits[1].measure([4, 1], [4, 1]) - circuits = transpile(circuits, backend) for circuit in circuits: result1 = backend.run( diff --git a/test/terra/backends/aer_simulator/test_options.py b/test/terra/backends/aer_simulator/test_options.py index a387e6cd63..49c9e3cec5 100644 --- a/test/terra/backends/aer_simulator/test_options.py +++ b/test/terra/backends/aer_simulator/test_options.py @@ -22,10 +22,7 @@ from qiskit.quantum_info.random import random_unitary from qiskit.quantum_info import state_fidelity -if qiskit.__version__.startswith("0."): - from qiskit.providers.fake_provider import FakeAlmaden as Fake20QV1 -else: - from qiskit.providers.fake_provider import Fake20QV1 +from qiskit.providers.fake_provider import GenericBackendV2 from qiskit_aer import AerSimulator @@ -305,8 +302,9 @@ def test_statevector_memory(self): def test_num_qubits(self, method): """Test number of qubits is correctly checked""" - num_qubits = Fake20QV1().configuration().num_qubits - backend = AerSimulator.from_backend(Fake20QV1(), method=method) + num_qubits = 20 + fake_backend = GenericBackendV2(num_qubits=num_qubits) + backend = AerSimulator.from_backend(fake_backend, method=method) self.assertGreaterEqual(backend.configuration().num_qubits, num_qubits) def test_mps_svd_method(self): diff --git a/test/terra/backends/aer_simulator/test_truncate.py b/test/terra/backends/aer_simulator/test_truncate.py index 136623b9e5..8701a2222a 100644 --- a/test/terra/backends/aer_simulator/test_truncate.py +++ b/test/terra/backends/aer_simulator/test_truncate.py @@ -13,10 +13,7 @@ import qiskit from qiskit import transpile, QuantumCircuit -if qiskit.__version__.startswith("0."): - from qiskit.providers.fake_provider import FakeQuito as Fake5QV1 -else: - from qiskit.providers.fake_provider import Fake5QV1 +from qiskit.providers.fake_provider import GenericBackendV2 from qiskit_aer.noise import NoiseModel from test.terra.backends.simulator_test_case import SimulatorTestCase, supported_methods @@ -53,7 +50,7 @@ def create_circuit_for_truncate(self): return circuit def device_backend(self): - return Fake5QV1() + return GenericBackendV2(num_qubits=5) def test_truncate_ideal_sparse_circuit(self): """Test qubit truncation for large circuit with unused qubits.""" @@ -94,7 +91,7 @@ def test_truncate_default(self): result = backend.run(circuit, shots=1).result() metadata = result.results[0].metadata self.assertEqual(metadata["num_qubits"], 2) - self.assertEqual(metadata["active_input_qubits"], [0, 1]) + self.assertEqual(len(metadata["active_input_qubits"]), 2) def test_truncate_non_measured_qubits(self): """Test truncation of non-measured uncoupled qubits.""" diff --git a/test/terra/backends/aer_simulator/test_unitary_gate.py b/test/terra/backends/aer_simulator/test_unitary_gate.py index 9e92410ae4..4e67b8d470 100644 --- a/test/terra/backends/aer_simulator/test_unitary_gate.py +++ b/test/terra/backends/aer_simulator/test_unitary_gate.py @@ -47,7 +47,6 @@ def test_unitary_gate(self, method, device): shots = 100 circuits = ref_unitary_gate.unitary_gate_circuits_deterministic(final_measure=True) targets = ref_unitary_gate.unitary_gate_counts_deterministic(shots) - circuits = transpile(circuits, backend) result = backend.run(circuits, shots=shots).result() self.assertSuccess(result) self.compare_counts(result, circuits, targets, delta=0) @@ -61,7 +60,6 @@ def test_random_unitary_gate(self, method, device): final_measure=True ) targets = ref_unitary_gate.unitary_random_gate_counts_nondeterministic(shots) - circuits = transpile(circuits, backend) result = backend.run(circuits, shots=shots).result() self.assertSuccess(result) self.compare_counts(result, circuits, targets, delta=0.05 * shots) @@ -78,8 +76,7 @@ def test_random_unitary_gate_with_permutations(self, method, device, perm): circuit.unitary(unitary_matrix, perm) circuit.barrier(range(n)) circuit.measure(range(n), range(n)) - circuits = transpile(circuit, backend) - result = backend.run(circuits, shots=shots).result() + result = backend.run(circuit, shots=shots).result() state = Statevector.from_label(n * "0").evolve(unitary_matrix, perm) state.seed(11111) @@ -99,7 +96,6 @@ def test_diagonal_gate(self, method, device): shots = 100 circuits = ref_diagonal_gate.diagonal_gate_circuits_deterministic(final_measure=True) targets = ref_diagonal_gate.diagonal_gate_counts_deterministic(shots) - circuits = transpile(circuits, backend) result = backend.run(circuits, shots=shots).result() self.assertSuccess(result) self.compare_counts(result, circuits, targets, delta=0) diff --git a/test/terra/backends/aer_simulator/test_wrapper_statevector_simulator.py b/test/terra/backends/aer_simulator/test_wrapper_statevector_simulator.py index 48f6eb29ef..c3f60d50ab 100644 --- a/test/terra/backends/aer_simulator/test_wrapper_statevector_simulator.py +++ b/test/terra/backends/aer_simulator/test_wrapper_statevector_simulator.py @@ -123,7 +123,6 @@ def test_conditional_unitary_1bit(self, device): circuits = ref_conditionals.conditional_circuits_1bit( final_measure=False, conditional_type="unitary" ) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_conditionals.conditional_statevector_1bit() self.assertSuccess(result) @@ -147,7 +146,6 @@ def test_conditional_unitary_2bit(self, device): circuits = ref_conditionals.conditional_circuits_2bit( final_measure=False, conditional_type="unitary" ) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_conditionals.conditional_statevector_2bit() self.assertSuccess(result) @@ -175,7 +173,6 @@ def test_conditional_unitary_64bit(self, device): circuits = ref_conditionals.conditional_circuits_nbit( 64, cases, final_measure=False, conditional_type="unitary" ) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_conditionals.conditional_statevector_nbit(cases) self.assertSuccess(result) @@ -203,7 +200,6 @@ def test_conditional_unitary_132bit(self, device): circuits = ref_conditionals.conditional_circuits_nbit( 132, cases, final_measure=False, conditional_type="unitary" ) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_conditionals.conditional_statevector_nbit(cases) self.assertSuccess(result) @@ -217,7 +213,6 @@ def test_unitary_gate(self, device): """Test simulation with unitary gate circuit instructions.""" backend = self.backend(device=device) circuits = ref_unitary_gate.unitary_gate_circuits_deterministic(final_measure=False) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_unitary_gate.unitary_gate_statevector_deterministic() self.assertSuccess(result) @@ -228,7 +223,6 @@ def test_unitary_gate_circuit_run(self, device): """Test simulation with unitary gate circuit instructions.""" backend = self.backend(device=device) circuits = ref_unitary_gate.unitary_gate_circuits_deterministic(final_measure=False) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_unitary_gate.unitary_gate_statevector_deterministic() self.assertSuccess(result) @@ -239,7 +233,6 @@ def test_diagonal_gate(self, device): """Test simulation with diagonal gate circuit instructions.""" backend = self.backend(device=device) circuits = ref_diagonal_gate.diagonal_gate_circuits_deterministic(final_measure=False) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_diagonal_gate.diagonal_gate_statevector_deterministic() self.assertSuccess(result) diff --git a/test/terra/backends/aer_simulator/test_wrapper_unitary_simulator.py b/test/terra/backends/aer_simulator/test_wrapper_unitary_simulator.py index c3100d45b8..6a2f8bac7b 100644 --- a/test/terra/backends/aer_simulator/test_wrapper_unitary_simulator.py +++ b/test/terra/backends/aer_simulator/test_wrapper_unitary_simulator.py @@ -39,7 +39,6 @@ def test_unitary_gate(self, device): """Test simulation with unitary gate circuit instructions.""" backend = self.backend(device=device) circuits = ref_unitary_gate.unitary_gate_circuits_deterministic(final_measure=False) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_unitary_gate.unitary_gate_unitary_deterministic() self.assertSuccess(result) @@ -50,7 +49,6 @@ def test_unitary_gate_circuit_run(self, device): """Test simulation with unitary gate circuit instructions.""" backend = self.backend(device=device) circuits = ref_unitary_gate.unitary_gate_circuits_deterministic(final_measure=False) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_unitary_gate.unitary_gate_unitary_deterministic() self.assertSuccess(result) @@ -61,7 +59,6 @@ def test_diagonal_gate(self, device): """Test simulation with diagonal gate circuit instructions.""" backend = self.backend(device=device) circuits = ref_diagonal_gate.diagonal_gate_circuits_deterministic(final_measure=False) - circuits = transpile(circuits, backend, optimization_level=1) result = backend.run(circuits, shots=1).result() targets = ref_diagonal_gate.diagonal_gate_unitary_deterministic() self.assertSuccess(result) diff --git a/test/terra/backends/simulator_test_case.py b/test/terra/backends/simulator_test_case.py index 2173c2c413..e04ec8dcbc 100644 --- a/test/terra/backends/simulator_test_case.py +++ b/test/terra/backends/simulator_test_case.py @@ -19,9 +19,6 @@ from qiskit_aer import AerSimulator from test.terra.common import QiskitAerTestCase from qiskit.circuit import QuantumCircuit -from qiskit.compiler import assemble -from qiskit_aer.backends.backend_utils import cpp_execute_qobj -from qiskit_aer.backends.controller_wrappers import aer_controller_execute class SimulatorTestCase(QiskitAerTestCase): @@ -117,16 +114,12 @@ def check_cuStateVec(devices): if "GPU" in devices: dummy_circ = QuantumCircuit(1) dummy_circ.id(0) - qobj = assemble( - dummy_circ, - optimization_level=0, - shots=1, - method="statevector", - device="GPU", - cuStateVec_enable=True, - ) # run dummy circuit to check if Aer is built with cuStateVec - result = cpp_execute_qobj(aer_controller_execute(), qobj) - return result.get("success", False) + sim = AerSimulator() + result = sim.run( + dummy_circ, shots=1, method="statevector", device="GPU", cuStateVec_enable=True + ).result() + success = getattr(result, "success", False) + return success else: return False diff --git a/test/terra/backends/test_parameterized_qobj.py b/test/terra/backends/test_parameterized_qobj.py index 326f1beb2c..c7476ac6ae 100644 --- a/test/terra/backends/test_parameterized_qobj.py +++ b/test/terra/backends/test_parameterized_qobj.py @@ -40,89 +40,6 @@ class TestParameterizedQobj(common.QiskitAerTestCase): BACKEND_OPTS = {"seed_simulator": 2113} - @staticmethod - def parameterized_qobj( - backend, - shots=1000, - measure=True, - snapshot=False, - save_state=False, - ): - """Return ParameterizedQobj for settings.""" - pershot = shots == 1 - pcirc1, param1 = save_expval_circuit_parameterized( - pershot=pershot, - measure=measure, - snapshot=snapshot, - ) - circuits2to4 = save_expval_circuits( - pauli=True, - skip_measure=(not measure), - pershot=pershot, - ) - pcirc2, param2 = save_expval_circuit_parameterized( - pershot=pershot, - measure=measure, - snapshot=snapshot, - ) - circuits = [pcirc1] + circuits2to4 + [pcirc2] - if save_state: - for circuit in circuits: - circuit.save_statevector(pershot=pershot) - params = [param1, [], [], [], param2] - qobj = assemble(circuits, backend=backend, shots=shots, parameterizations=params) - return qobj - - @unittest.skipIf(qiskit.__version__.startswith("1.2"), "skip Qiskit 1.2 tentatively") - def test_parameterized_qobj_qasm_save_expval(self): - """Test parameterized qobj with Expectation Value snapshot and qasm simulator.""" - shots = 1000 - labels = save_expval_labels() * 3 - counts_targets = save_expval_counts(shots) * 3 - value_targets = save_expval_pre_meas_values() * 3 - - backend = AerSimulator() - qobj = self.parameterized_qobj(backend=backend, shots=1000, measure=True, snapshot=True) - self.assertIn("parameterizations", qobj.to_dict()["config"]) - with self.assertWarns(DeprecationWarning): - job = backend.run(qobj, **self.BACKEND_OPTS) - result = job.result() - success = getattr(result, "success", False) - num_circs = len(result.to_dict()["results"]) - self.assertTrue(success) - self.compare_counts(result, range(num_circs), counts_targets, delta=0.1 * shots) - # Check snapshots - for j, target in enumerate(value_targets): - data = result.data(j) - for label in labels: - self.assertAlmostEqual(data[label], target[label], delta=1e-7) - - @unittest.skipIf(qiskit.__version__.startswith("1.2"), "skip Qiskit 1.2 tentatively") - def test_parameterized_qobj_statevector(self): - """Test parameterized qobj with Expectation Value snapshot and qasm simulator.""" - statevec_targets = save_expval_final_statevecs() * 3 - - backend = AerSimulator(method="statevector") - qobj = self.parameterized_qobj( - backend=backend, - measure=False, - snapshot=False, - save_state=True, - ) - self.assertIn("parameterizations", qobj.to_dict()["config"]) - with self.assertWarns(DeprecationWarning): - job = backend.run(qobj, **self.BACKEND_OPTS) - result = job.result() - success = getattr(result, "success", False) - num_circs = len(result.to_dict()["results"]) - self.assertTrue(success) - - for j in range(num_circs): - statevector = result.get_statevector(j) - np.testing.assert_array_almost_equal( - statevector, statevec_targets[j].data, decimal=7 - ) - def test_run_path(self): """Test parameterized circuit path via backed.run()""" shots = 1000 diff --git a/test/terra/backends/test_runtime_parameterization.py b/test/terra/backends/test_runtime_parameterization.py index a461b300e0..7c811cb9ad 100644 --- a/test/terra/backends/test_runtime_parameterization.py +++ b/test/terra/backends/test_runtime_parameterization.py @@ -57,91 +57,6 @@ class TestRuntimeParameterization(SimulatorTestCase): "runtime_parameter_bind_enable": True, } - @staticmethod - def runtime_parameterization( - backend, - shots=1000, - measure=True, - snapshot=False, - save_state=False, - ): - """Return ParameterizedQobj for settings.""" - pershot = shots == 1 - pcirc1, param1 = save_expval_circuit_parameterized( - pershot=pershot, - measure=measure, - snapshot=snapshot, - ) - circuits2to4 = save_expval_circuits( - pauli=True, - skip_measure=(not measure), - pershot=pershot, - ) - pcirc2, param2 = save_expval_circuit_parameterized( - pershot=pershot, - measure=measure, - snapshot=snapshot, - ) - circuits = [pcirc1] + circuits2to4 + [pcirc2] - if save_state: - for circuit in circuits: - circuit.save_statevector(pershot=pershot) - params = [param1, [], [], [], param2] - qobj = assemble(circuits, backend=backend, shots=shots, parameterizations=params) - return qobj - - @unittest.skipIf(qiskit.__version__.startswith("1.2"), "skip Qiskit 1.2 tentatively") - def test_runtime_parameterization_qasm_save_expval(self): - """Test parameterized qobj with Expectation Value snapshot and qasm simulator.""" - shots = 1000 - labels = save_expval_labels() * 3 - counts_targets = save_expval_counts(shots) * 3 - value_targets = save_expval_pre_meas_values() * 3 - - backend = AerSimulator() - qobj = self.runtime_parameterization( - backend=backend, shots=1000, measure=True, snapshot=True - ) - self.assertIn("parameterizations", qobj.to_dict()["config"]) - with self.assertWarns(DeprecationWarning): - job = backend.run(qobj, **self.BACKEND_OPTS) - result = job.result() - success = getattr(result, "success", False) - num_circs = len(result.to_dict()["results"]) - self.assertTrue(success) - self.compare_counts(result, range(num_circs), counts_targets, delta=0.1 * shots) - # Check snapshots - for j, target in enumerate(value_targets): - data = result.data(j) - for label in labels: - self.assertAlmostEqual(data[label], target[label], delta=1e-7) - - @unittest.skipIf(qiskit.__version__.startswith("1.2"), "skip Qiskit 1.2 tentatively") - def test_runtime_parameterization_statevector(self): - """Test parameterized qobj with Expectation Value snapshot and qasm simulator.""" - statevec_targets = save_expval_final_statevecs() * 3 - - backend = AerSimulator(method="statevector") - qobj = self.runtime_parameterization( - backend=backend, - measure=False, - snapshot=False, - save_state=True, - ) - self.assertIn("parameterizations", qobj.to_dict()["config"]) - with self.assertWarns(DeprecationWarning): - job = backend.run(qobj, **self.BACKEND_OPTS) - result = job.result() - success = getattr(result, "success", False) - num_circs = len(result.to_dict()["results"]) - self.assertTrue(success) - - for j in range(num_circs): - statevector = result.get_statevector(j) - np.testing.assert_array_almost_equal( - statevector, statevec_targets[j].data, decimal=7 - ) - @supported_methods(SUPPORTED_METHODS) def test_run_path(self, method, device): """Test parameterized circuit path via backed.run()""" diff --git a/test/terra/extensions/test_wrappers.py b/test/terra/extensions/test_wrappers.py deleted file mode 100644 index cc7e62b50a..0000000000 --- a/test/terra/extensions/test_wrappers.py +++ /dev/null @@ -1,69 +0,0 @@ -# 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. - - -import unittest -import copy -import pickle -from multiprocessing import Pool - -from qiskit import transpile, QuantumCircuit -from qiskit_aer.backends import AerSimulator -from qiskit_aer.backends.controller_wrappers import aer_controller_execute -from qiskit_aer.backends.backend_utils import LIBRARY_DIR -from test.terra.common import QiskitAerTestCase - - -class TestControllerExecuteWrappers(QiskitAerTestCase): - """Basic functionality tests for pybind-generated wrappers""" - - CFUNCS = [aer_controller_execute()] - - def test_deepcopy(self): - """Test that the functors are deepcopy-able.""" - for cfunc in self.CFUNCS: - cahpy = copy.deepcopy(cfunc) - - def test_pickleable(self): - """Test that the functors are pickle-able (directly).""" - for cfunc in self.CFUNCS: - bites = pickle.dumps(cfunc) - cahpy = pickle.loads(bites) - - def _create_qobj(self, backend, noise_model=None): - num_qubits = 2 - circuit = QuantumCircuit(num_qubits) - circuit.x(list(range(num_qubits))) - circuit = transpile(circuit, backend) - opts = {"max_parallel_threads": 1, "library_dir": LIBRARY_DIR, "noise_model": noise_model} - qobj = backend._assemble(circuit, **opts) - return qobj - - def _map_and_test(self, cfunc, qobj): - n = 2 - with Pool(processes=1) as p: - rs = p.map(cfunc, [copy.deepcopy(qobj) for _ in range(n)]) - - self.assertEqual(len(rs), n) - for r in rs: - self.assertTrue(r["success"]) - - def test_mappable_qasm(self): - """Test that the qasm controller can be mapped.""" - cfunc = aer_controller_execute() - sim = AerSimulator() - fqobj = self._create_qobj(sim) - self._map_and_test(cfunc, fqobj) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/terra/noise/test_device_models.py b/test/terra/noise/test_device_models.py index 43d90fda91..ab2946c167 100644 --- a/test/terra/noise/test_device_models.py +++ b/test/terra/noise/test_device_models.py @@ -85,8 +85,11 @@ def test_basic_device_gate_errors_from_target_with_non_operational_qubits(self): target = target_7q() # tweak target to have non-operational qubits faulty_qubits = (1, 2) + q_prop = target.qubit_properties for q in faulty_qubits: - target.qubit_properties[q] = QubitProperties(t1=None, t2=None, frequency=0) + q_prop[q] = QubitProperties(t1=None, t2=None, frequency=0) + target.qubit_properties = q_prop + # build gate errors with only relaxation errors i.e. without depolarizing errors gate_errors = basic_device_gate_errors(target=target, gate_error=False) errors_on_sx = {qubits: error for name, qubits, error in gate_errors if name == "sx"} @@ -128,7 +131,9 @@ def test_non_zero_temperature(self): """Test if non-zero excited_state_population is obtained when positive temperature is supplied. See https://github.com/Qiskit/qiskit-aer/issues/1937 for the details.""" t1, t2, frequency, duration = 1e-4, 1e-4, 5e9, 5e-8 - target = Target(qubit_properties=[QubitProperties(t1=t1, t2=t2, frequency=frequency)]) + target = Target( + num_qubits=1, qubit_properties=[QubitProperties(t1=t1, t2=t2, frequency=frequency)] + ) target.add_instruction(library.XGate(), {(0,): InstructionProperties(duration=duration)}) errors = basic_device_gate_errors(target=target, gate_error=False, temperature=100) _, _, x_error = errors[0] diff --git a/test/terra/noise/test_noise_model.py b/test/terra/noise/test_noise_model.py index 297e38d188..850e025192 100644 --- a/test/terra/noise/test_noise_model.py +++ b/test/terra/noise/test_noise_model.py @@ -37,9 +37,6 @@ if qiskit.__version__.startswith("0."): from qiskit.providers.fake_provider import ( - FakeBackend, - FakeAlmaden as Fake20QV1, - FakeMumbai as Fake27QPulseV1, FakeLagosV2, ) @@ -49,9 +46,6 @@ def fake_7q_v2(): else: from qiskit.providers.fake_provider import ( - FakeBackend, - Fake20QV1, - Fake27QPulseV1, GenericBackendV2, ) @@ -213,30 +207,6 @@ def test_noise_models_not_equal(self): model2 = NoiseModel(basis_gates=["u3", "cx"]) model2.add_all_qubit_quantum_error(error, ["u3"], False) - def test_noise_model_from_backend_20(self): - circ = QuantumCircuit(2) - circ.x(0) - circ.x(1) - circ.measure_all() - - backend = Fake20QV1() - noise_model = NoiseModel.from_backend(backend) - circ = transpile(circ, backend, optimization_level=0) - result = AerSimulator().run(circ, noise_model=noise_model).result() - self.assertTrue(result.success) - - def test_noise_model_from_backend_27_pulse(self): - circ = QuantumCircuit(2) - circ.x(0) - circ.x(1) - circ.measure_all() - - backend = Fake27QPulseV1() - noise_model = NoiseModel.from_backend(backend) - circ = transpile(circ, backend, optimization_level=0) - result = AerSimulator().run(circ, noise_model=noise_model).result() - self.assertTrue(result.success) - def test_noise_model_from_backend_v2(self): circ = QuantumCircuit(2) circ.x(0) @@ -269,67 +239,6 @@ def test_noise_model_from_backend_v2_with_non_operational_qubits(self): result = AerSimulator().run(circ, noise_model=noise_model).result() self.assertTrue(result.success) - def test_noise_model_from_invalid_t2_backend(self): - """Test if silently truncate invalid T2 values when creating a noise model from backend""" - from qiskit.providers.models.backendproperties import BackendProperties, Gate, Nduv - import datetime - - t1_ns, invalid_t2_ns = 75_1000, 200_1000 - u3_time_ns = 320 - frequency = 4919.96800692 - - class InvalidT2Fake1Q(FakeBackend): - def __init__(self): - mock_time = datetime.datetime.now() - dt = 1.3333 - configuration = BackendProperties( - backend_name="invalid_t2", - backend_version="0.0.0", - num_qubits=1, - basis_gates=["u3"], - qubits=[ - [ - Nduv(date=mock_time, name="T1", unit="µs", value=t1_ns / 1000), - Nduv(date=mock_time, name="T2", unit="µs", value=invalid_t2_ns / 1000), - Nduv(date=mock_time, name="frequency", unit="MHz", value=frequency), - ], - ], - gates=[ - Gate( - gate="u3", - name="u3_0", - qubits=[0], - parameters=[ - Nduv(date=mock_time, name="gate_error", unit="", value=0.001), - Nduv( - date=mock_time, name="gate_length", unit="ns", value=u3_time_ns - ), - ], - ), - ], - last_update_date=mock_time, - general=[], - ) - super().__init__(configuration) - - def defaults(self): - """defaults == configuration""" - return self._configuration - - def properties(self): - """properties == configuration""" - return self._configuration - - backend = InvalidT2Fake1Q() - noise_model = NoiseModel.from_backend(backend, gate_error=False) - expected = thermal_relaxation_error( - t1=t1_ns, - t2=2 * t1_ns, - time=u3_time_ns, - excited_state_population=_excited_population(frequency, temperature=0), - ) - self.assertEqual(expected, noise_model._local_quantum_errors["u3"][(0,)]) - def test_create_noise_model_without_user_warnings(self): """Test if never issue user warnings when creating a noise model from backend. See issue#1631 for the details.""" @@ -359,19 +268,6 @@ def run(self, run_input, **options): user_warnings = [w for w in warns if issubclass(w.category, UserWarning)] self.assertEqual(len(user_warnings), 0) - def test_noise_model_from_backend_properties(self): - circ = QuantumCircuit(2) - circ.x(0) - circ.x(1) - circ.measure_all() - - backend = Fake20QV1() - backend_propeties = backend.properties() - noise_model = NoiseModel.from_backend_properties(backend_propeties) - circ = transpile(circ, backend, optimization_level=0) - result = AerSimulator().run(circ, noise_model=noise_model).result() - self.assertTrue(result.success) - def test_transform_noise(self): org_error = reset_error(0.2) new_error = pauli_error([("I", 0.5), ("Z", 0.5)]) diff --git a/test/terra/primitives/test_sampler_v2.py b/test/terra/primitives/test_sampler_v2.py index 3ae9c98fad..7889135592 100644 --- a/test/terra/primitives/test_sampler_v2.py +++ b/test/terra/primitives/test_sampler_v2.py @@ -41,7 +41,6 @@ def setUp(self): self._shots = 10000 self._seed = 123 self._options = {"default_shots": self._shots, "seed": self._seed} - self._pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) self._cases = [] hadamard = QuantumCircuit(1, 1, name="Hadamard") @@ -82,10 +81,10 @@ def _assert_allclose(self, bitarray: BitArray, target: NDArray | BitArray, rtol= def test_sampler_run(self): """Test run().""" - pm = self._pm with self.subTest("single"): bell, _, target = self._cases[1] + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) bell = pm.run(bell) sampler = SamplerV2(**self._options) job = sampler.run([bell], shots=self._shots) @@ -102,6 +101,7 @@ def test_sampler_run(self): with self.subTest("single with param"): pqc, param_vals, target = self._cases[2] sampler = SamplerV2(**self._options) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) pqc = pm.run(pqc) params = (param.name for param in pqc.parameters) job = sampler.run([(pqc, {params: param_vals})], shots=self._shots) @@ -118,6 +118,7 @@ def test_sampler_run(self): with self.subTest("multiple"): pqc, param_vals, target = self._cases[2] sampler = SamplerV2(**self._options) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) pqc = pm.run(pqc) params = (param.name for param in pqc.parameters) job = sampler.run( @@ -137,7 +138,8 @@ def test_sampler_run_multiple_times(self): """Test run() returns the same results if the same input is given.""" bell, _, _ = self._cases[1] sampler = SamplerV2(**self._options) - bell = self._pm.run(bell) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + bell = pm.run(bell) result1 = sampler.run([bell], shots=self._shots).result() meas1 = result1[0].data.meas result2 = sampler.run([bell], shots=self._shots).result() @@ -148,7 +150,8 @@ def test_sample_run_multiple_circuits(self): """Test run() with multiple circuits.""" bell, _, target = self._cases[1] sampler = SamplerV2(**self._options) - bell = self._pm.run(bell) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + bell = pm.run(bell) result = sampler.run([bell, bell, bell], shots=self._shots).result() self.assertEqual(len(result), 3) self._assert_allclose(result[0].data.meas, np.array(target)) @@ -160,7 +163,8 @@ def test_sampler_run_with_parameterized_circuits(self): pqc1, param1, target1 = self._cases[4] pqc2, param2, target2 = self._cases[5] pqc3, param3, target3 = self._cases[6] - pqc1, pqc2, pqc3 = self._pm.run([pqc1, pqc2, pqc3]) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + pqc1, pqc2, pqc3 = pm.run([pqc1, pqc2, pqc3]) sampler = SamplerV2(**self._options) result = sampler.run( @@ -178,7 +182,8 @@ def test_run_1qubit(self): qc2 = QuantumCircuit(1) qc2.x(0) qc2.measure_all() - qc, qc2 = self._pm.run([qc, qc2]) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + qc, qc2 = pm.run([qc, qc2]) sampler = SamplerV2(**self._options) result = sampler.run([qc, qc2], shots=self._shots).result() @@ -199,7 +204,8 @@ def test_run_2qubit(self): qc3 = QuantumCircuit(2) qc3.x([0, 1]) qc3.measure_all() - qc0, qc1, qc2, qc3 = self._pm.run([qc0, qc1, qc2, qc3]) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + qc0, qc1, qc2, qc3 = pm.run([qc0, qc1, qc2, qc3]) sampler = SamplerV2(**self._options) result = sampler.run([qc0, qc1, qc2, qc3], shots=self._shots).result() @@ -210,7 +216,7 @@ def test_run_2qubit(self): def test_run_single_circuit(self): """Test for single circuit case.""" sampler = SamplerV2(**self._options) - pm = self._pm + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) with self.subTest("No parameter"): circuit, _, target = self._cases[1] @@ -276,7 +282,8 @@ def test_run_reverse_meas_order(self): qc.measure(0, 2) qc.measure(1, 1) qc.measure(2, 0) - qc = self._pm.run(qc) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + qc = pm.run(qc) sampler = SamplerV2(**self._options) sampler.options.seed_simulator = self._seed @@ -295,7 +302,8 @@ def test_run_errors(self): qc1.measure_all() qc2 = RealAmplitudes(num_qubits=1, reps=1) qc2.measure_all() - qc1, qc2 = self._pm.run([qc1, qc2]) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + qc1, qc2 = pm.run([qc1, qc2]) sampler = SamplerV2(**self._options) with self.subTest("set parameter values to a non-parameterized circuit"): @@ -344,7 +352,8 @@ def test_run_empty_parameter(self): n = 5 qc = QuantumCircuit(n, n - 1) qc.measure(range(n - 1), range(n - 1)) - qc = self._pm.run(qc) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + qc = pm.run(qc) sampler = SamplerV2(**self._options) with self.subTest("one circuit"): result = sampler.run([qc], shots=self._shots).result() @@ -361,7 +370,8 @@ def test_run_numpy_params(self): """Test for numpy array as parameter values""" qc = RealAmplitudes(num_qubits=2, reps=2) qc.measure_all() - qc = self._pm.run(qc) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + qc = pm.run(qc) k = 5 params_array = np.linspace(0, 1, k * qc.num_parameters).reshape((k, qc.num_parameters)) params_list = params_array.tolist() @@ -388,7 +398,8 @@ def test_run_numpy_params(self): def test_run_with_shots_option(self): """test with shots option.""" bell, _, _ = self._cases[1] - bell = self._pm.run(bell) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + bell = pm.run(bell) shots = 100 with self.subTest("run arg"): @@ -443,7 +454,8 @@ def test_run_shots_result_size(self): qc = QuantumCircuit(n) qc.h(range(n)) qc.measure_all() - qc = self._pm.run(qc) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + qc = pm.run(qc) sampler = SamplerV2(**self._options) result = sampler.run([qc], shots=self._shots).result() self.assertEqual(len(result), 1) @@ -453,7 +465,8 @@ def test_run_shots_result_size(self): def test_primitive_job_status_done(self): """test primitive job's status""" bell, _, _ = self._cases[1] - bell = self._pm.run(bell) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + bell = pm.run(bell) sampler = SamplerV2(**self._options) job = sampler.run([bell], shots=self._shots) _ = job.result() @@ -461,7 +474,7 @@ def test_primitive_job_status_done(self): def test_circuit_with_unitary(self): """Test for circuit with unitary gate.""" - pm = self._pm + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) with self.subTest("identity"): gate = UnitaryGate(np.eye(2)) @@ -491,7 +504,7 @@ def test_circuit_with_unitary(self): def test_circuit_with_multiple_cregs(self): """Test for circuit with multiple classical registers.""" - pm = self._pm + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) cases = [] # case 1 @@ -575,17 +588,21 @@ def test_circuit_with_aliased_cregs(self): c2 = ClassicalRegister(1, "c2") qc = QuantumCircuit(q, c1, c2) - qc.ry(np.pi / 4, 2) - qc.cx(2, 1) - qc.cx(0, 1) - qc.h(0) - qc.measure(0, c1) - qc.measure(1, c2) qc.z(2).c_if(c1, 1) qc.x(2).c_if(c2, 1) qc2 = QuantumCircuit(5, 5) qc2.compose(qc, [0, 2, 3], [2, 4], inplace=True) - cregs = [creg.name for creg in qc2.cregs] + # Note: qc2 has aliased cregs, c0 -> c[2] and c1 -> c[4]. + # copy_empty_like copies the aliased cregs of qc2 to qc3. + qc3 = QuantumCircuit.copy_empty_like(qc2) + qc3.ry(np.pi / 4, 2) + qc3.cx(2, 1) + qc3.cx(0, 1) + qc3.h(0) + qc3.measure(0, 2) + qc3.measure(1, 4) + self.assertEqual(len(qc3.cregs), 3) + cregs = [creg.name for creg in qc3.cregs] target = { cregs[0]: {0: 4255, 4: 4297, 16: 720, 20: 726}, cregs[1]: {0: 5000, 1: 5000}, @@ -593,8 +610,7 @@ def test_circuit_with_aliased_cregs(self): } sampler = SamplerV2(**self._options) - qc2 = self._pm.run(qc2) - result = sampler.run([qc2], shots=self._shots).result() + result = sampler.run([qc3], shots=self._shots).result() self.assertEqual(len(result), 1) data = result[0].data self.assertEqual(len(data), 3) @@ -629,7 +645,8 @@ def test_empty_creg(self): def test_diff_shots(self): """Test of pubs with different shots""" bell, _, target = self._cases[1] - bell = self._pm.run(bell) + pm = generate_preset_pass_manager(optimization_level=0, backend=AerSimulator()) + bell = pm.run(bell) sampler = SamplerV2(**self._options) shots2 = self._shots + 2 target2 = {k: v + 1 for k, v in target.items()}