Skip to content

Commit

Permalink
Store simulator Backend in FakeBackend instance (#7912) (#7953)
Browse files Browse the repository at this point in the history
* Initialize simulator at FakeBackend inititialization

This commit updates the FakeBackend and FakeBackendV2 classes
(FakeLegacyBackend is being removed in #7886) to only build a noise
model from the stored properties one. This is done by initializing the
internal simulator object only once at storing it as an instance
attribute. The noise model is then constructed once at object
initialization and used to update the backend at the same time.

Fixes #7500
Fixes #7911

* Lazy load simulator backend objects and properties

This commit fixes another regression introduced in the previous commit
around the import and __init__ performance of fake backends. By parsing
the properties payload and creating a noise model we significantly
slowed down the initialization of fake backend objects. This commit
fixes this by ensuring we are lazy loading the noise model and simulator
object creation as well as not parsing the properties (or defaults)
payloads until we actually need them.

* Use qubit properties in target for backendv2

* Filter all noise model warnings

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
(cherry picked from commit 57f16ab)

Co-authored-by: Matthew Treinish <[email protected]>
  • Loading branch information
mergify[bot] and mtreinish authored Apr 19, 2022
1 parent 095d2a4 commit 850795b
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 89 deletions.
163 changes: 81 additions & 82 deletions qiskit/test/mock/fake_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
import json
import os

from typing import List, Union
from typing import List

from qiskit import circuit
from qiskit.providers.models import BackendProperties
from qiskit.providers import BackendV2, BackendV1, BaseBackend, QubitProperties
from qiskit.providers import BackendV2, BackendV1, BaseBackend
from qiskit import pulse
from qiskit.exceptions import QiskitError
from qiskit.test.mock import fake_job
Expand All @@ -34,10 +34,7 @@
decode_backend_properties,
decode_pulse_defaults,
)
from qiskit.test.mock.utils.backend_converter import (
convert_to_target,
qubit_props_from_props,
)
from qiskit.test.mock.utils.backend_converter import convert_to_target
from qiskit.utils import optionals as _optionals
from qiskit.providers import basicaer
from qiskit.transpiler import Target
Expand Down Expand Up @@ -76,43 +73,52 @@ class FakeBackendV2(BackendV2):
def __init__(self):
"""FakeBackendV2 initializer."""
self._conf_dict = self._get_conf_dict_from_json()
self._props_dict = self._set_props_dict_from_json()
self._defs_dict = self._set_defs_dict_from_json()
self._props_dict = None
self._defs_dict = None
super().__init__(
provider=None,
name=self._conf_dict.get("backend_name"),
description=self._conf_dict.get("description"),
online_date=self._conf_dict.get("online_date"),
backend_version=self._conf_dict.get("backend_version"),
)
self._target = convert_to_target(
conf_dict=self._conf_dict,
props_dict=self._props_dict,
defs_dict=self._defs_dict,
)
self._qubit_properties = qubit_props_from_props(self._props_dict)
self._target = None
self.sim = None

def _setup_sim(self):
if _optionals.HAS_AER:
from qiskit.providers import aer

def _get_conf_dict_from_json(self) -> dict:
self.sim = aer.AerSimulator()
if self._props_dict:
noise_model = self._get_noise_model_from_backend_v2()
self.sim.set_options(noise_model=noise_model)
# Update fake backend default too to avoid overwriting
# it when run() is called
self.set_options(noise_model=noise_model)

else:
self.sim = basicaer.QasmSimulatorPy()

def _get_conf_dict_from_json(self):
if not self.conf_filename:
return None
conf_dict = self._load_json(self.conf_filename)
decode_backend_configuration(conf_dict)
conf_dict["backend_name"] = self.backend_name
return conf_dict

def _set_props_dict_from_json(self) -> dict:
if not self.props_filename:
return None
props_dict = self._load_json(self.props_filename)
decode_backend_properties(props_dict)
return props_dict
def _set_props_dict_from_json(self):
if self.props_filename:
props_dict = self._load_json(self.props_filename)
decode_backend_properties(props_dict)
self._props_dict = props_dict

def _set_defs_dict_from_json(self) -> dict:
if not self.defs_filename:
return None
defs_dict = self._load_json(self.defs_filename)
decode_pulse_defaults(defs_dict)
return defs_dict
def _set_defs_dict_from_json(self):
if self.defs_filename:
defs_dict = self._load_json(self.defs_filename)
decode_pulse_defaults(defs_dict)
self._defs_dict = defs_dict

def _load_json(self, filename: str) -> dict:
with open(os.path.join(self.dirname, filename)) as f_json:
Expand All @@ -125,6 +131,18 @@ def target(self) -> Target:
:rtype: Target
"""
if self._target is None:
self._get_conf_dict_from_json()
if self._props_dict is None:
self._set_props_dict_from_json()
if self._defs_dict is None:
self._set_defs_dict_from_json()
self._target = convert_to_target(
conf_dict=self._conf_dict,
props_dict=self._props_dict,
defs_dict=self._defs_dict,
)

return self._target

@property
Expand All @@ -147,7 +165,7 @@ def _default_options(cls):
if _optionals.HAS_AER:
from qiskit.providers import aer

return aer.QasmSimulator._default_options()
return aer.AerSimulator._default_options()
else:
return basicaer.QasmSimulatorPy._default_options()

Expand All @@ -171,28 +189,6 @@ def meas_map(self) -> List[List[int]]:
"""
return self._conf_dict.get("meas_map")

def qubit_properties(
self, qubit: Union[int, List[int]]
) -> Union[QubitProperties, List[QubitProperties]]:
"""Return QubitProperties for a given qubit.
Args:
qubit: The qubit to get the
:class:`~qiskit.provider.QubitProperties` object for. This can
be a single integer for 1 qubit or a list of qubits and a list
of :class:`~qiskit.provider.QubitProperties` objects will be
returned in the same order
Returns:
qubit_properties: The :class:`~qiskit.provider.QubitProperties`
object for the specified qubit. If a list of qubits is provided a
list will be returned. If properties are missing for a qubit this
can be ``None``.
"""
if isinstance(qubit, int): # type: ignore[unreachable]
return self._qubit_properties.get(qubit)
if isinstance(qubit, List):
return [self._qubit_properties.get(q) for q in qubit]
return None

def run(self, run_input, **options):
"""Run on the fake backend using a simulator.
Expand Down Expand Up @@ -243,22 +239,14 @@ def run(self, run_input, **options):
"QuantumCircuit, Schedule, or a list of either" % circuits
)
if pulse_job: # pulse job
raise QiskitError("Pulse simulation is currently not supported for V2 backends.")
raise QiskitError("Pulse simulation is currently not supported for V2 fake backends.")
# circuit job
if _optionals.HAS_AER:
from qiskit.providers import aer

sim = aer.Aer.get_backend("qasm_simulator")
sim._options = self._options
if self._props_dict:
noise_model = self._get_noise_model_from_backend_v2()
job = sim.run(circuits, noise_model=noise_model, **options)
else: # simulate without noise
job = sim.run(circuits, **options)
else:
if not _optionals.HAS_AER:
warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning)
sim = basicaer.BasicAer.get_backend("qasm_simulator")
job = sim.run(circuits, **options)
if self.sim is None:
self._setup_sim()
self.sim._options = self._options
job = self.sim.run(circuits, **options)
return job

def _get_noise_model_from_backend_v2(
Expand Down Expand Up @@ -287,6 +275,9 @@ def _get_noise_model_from_backend_v2(
)
from qiskit.providers.aer.noise.passes import RelaxationNoisePass

if self._props_dict is None:
self._set_props_dict_from_json()

properties = BackendProperties.from_dict(self._props_dict)
basis_gates = self.operation_names
num_qubits = self.num_qubits
Expand All @@ -303,7 +294,6 @@ def _get_noise_model_from_backend_v2(
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore",
category=DeprecationWarning,
module="qiskit.providers.aer.noise.device.models",
)
gate_errors = basic_device_gate_errors(
Expand Down Expand Up @@ -357,6 +347,22 @@ def __init__(self, configuration, time_alive=10):
super().__init__(configuration)
self.time_alive = time_alive
self._credentials = _Credentials()
self.sim = None

def _setup_sim(self):
if _optionals.HAS_AER:
from qiskit.providers import aer
from qiskit.providers.aer.noise import NoiseModel

self.sim = aer.AerSimulator()
if self.properties():
noise_model = NoiseModel.from_backend(self, warnings=False)
self.sim.set_options(noise_model=noise_model)
# Update fake backend default options too to avoid overwriting
# it when run() is called
self.set_options(noise_model=noise_model)
else:
self.sim = basicaer.QasmSimulatorPy()

def properties(self):
"""Return backend properties"""
Expand Down Expand Up @@ -436,30 +442,23 @@ def run(self, run_input, **kwargs):
"Invalid input object %s, must be either a "
"QuantumCircuit, Schedule, or a list of either" % circuits
)
if _optionals.HAS_AER:
from qiskit.providers import aer

if pulse_job:
if pulse_job:
if _optionals.HAS_AER:
from qiskit.providers import aer
from qiskit.providers.aer.pulse import PulseSystemModel

system_model = PulseSystemModel.from_backend(self)
sim = aer.Aer.get_backend("pulse_simulator")
job = sim.run(circuits, system_model=system_model, **kwargs)
else:
sim = aer.Aer.get_backend("qasm_simulator")
if self.properties():
from qiskit.providers.aer.noise import NoiseModel

noise_model = NoiseModel.from_backend(self, warnings=False)
job = sim.run(circuits, noise_model=noise_model, **kwargs)
else:
job = sim.run(circuits, **kwargs)
else:
if pulse_job:
raise QiskitError("Unable to run pulse schedules without qiskit-aer installed")
warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning)
sim = basicaer.BasicAer.get_backend("qasm_simulator")
job = sim.run(circuits, **kwargs)
else:
if self.sim is None:
self._setup_sim()
if not _optionals.HAS_AER:
warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning)
self.sim._options = self._options
job = self.sim.run(circuits, **kwargs)
return job


Expand Down
15 changes: 8 additions & 7 deletions qiskit/test/mock/utils/backend_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict
"reset": Reset(),
}
custom_gates = {}
target = Target()
qubit_props = None
if props_dict:
qubit_props = qubit_props_from_props(props_dict)
target = Target(qubit_properties=qubit_props)
# Parse from properties if it exsits
if props_dict is not None:
# Parse instructions
Expand Down Expand Up @@ -123,12 +126,11 @@ def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict
return target


def qubit_props_from_props(properties: dict) -> dict:
def qubit_props_from_props(properties: dict) -> list:
"""Returns a dictionary of `qiskit.providers.backend.QubitProperties` using
a backend properties dictionary created by loading props.json payload.
"""
count = 0
qubit_props_dict = {}
qubit_props = []
for qubit in properties["qubits"]:
qubit_properties = {}
for prop_dict in qubit:
Expand All @@ -138,6 +140,5 @@ def qubit_props_from_props(properties: dict) -> dict:
qubit_properties["t2"] = apply_prefix(prop_dict["value"], prop_dict["unit"])
elif prop_dict["name"] == "frequency":
qubit_properties["frequency"] = apply_prefix(prop_dict["value"], prop_dict["unit"])
qubit_props_dict[count] = QubitProperties(**qubit_properties)
count += 1
return qubit_props_dict
qubit_props.append(QubitProperties(**qubit_properties))
return qubit_props

0 comments on commit 850795b

Please sign in to comment.