-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Backend v2 basic compatibility #843
Changes from 50 commits
b2c9c07
bd6129a
82bdd4f
3d8cb82
bc9eaf7
f00a03f
0b8ddb1
ee37de3
7df5715
c321e62
72a0520
bbfd6cb
d6746f2
1353d5e
ff1cd58
c61d2af
742bce5
c759b0a
5357394
e347bce
0050f08
457f317
4ef8b0e
531d79b
e4d4b4f
8bf2834
ce5e957
b91d413
a7484fb
a3f4ccf
696029c
6d37794
aede51c
637a3e2
700f4aa
3c3ae0e
a88310b
70e72da
a991a5f
ddfe164
7c03484
ce7d7c9
ac145da
4e859db
9d55456
bdce7ad
ba81c63
d227163
27ab9f3
9936c57
4032962
dec211d
5a04917
0225eeb
11c3461
683ab07
1ce5d4d
8416303
a30eeb3
5100fe7
2dfed83
b91e74b
1fa5284
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2021. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
""" | ||
Backend data access helper class | ||
|
||
Since `BackendV1` and `BackendV2` do not share the same interface, this | ||
class unifies data access for various data fields. | ||
""" | ||
from qiskit.providers.models import PulseBackendConfiguration | ||
from qiskit.providers import BackendV1, BackendV2 | ||
from qiskit.providers.fake_provider import fake_backend, FakeBackendV2, FakeBackend | ||
|
||
|
||
class BackendData: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is really nice to have this compatibility layer for transitioning to BackendV2. I wonder what is the life cycle of this class? Does it provide enough utility that we keep it after BackendV1 is deprecated? If not, should we try to keep the methods here as close to BackendV2 as possible so that in the future we could swap BackendData usage out for direct access of the BackendV2 instance? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My hope is that after deprecating |
||
"""Class for providing joint interface for accessing backend data""" | ||
|
||
def __init__(self, backend): | ||
"""Inits the backend and verifies version""" | ||
self._backend = backend | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think backend can be discarded after parsing all necessary data. I like You can write a dispatcher to do it cleanly. Actually functools provides a method dispatcher but available only > 3.8. Using this pattern makes this object robust to future update of backend, i.e. V3. You can write backend parser with dispatcher and save parsed values as protected members. Then you can provide property methods to indicate these are kind of static values. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea of pulling out all the data in Besides There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am very hesitant to accept the parsing solution. The backend comes from an outside source, and I don't know if and when it might be changed. I think of I considered using dispatcher but for now I actually prefer to keep the code as explicit as possible - I don't think it's large enough to drive us into using more abstract code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough, but I don't understand why
this becomes a problem for parsing. Actually backend is bit weird object because it is basically "configuration" + "interface for job submission". So current code allows us to write my_expr._backend_data._backend.run(...) though it is not expected to call protected members (I meant, this is no longer "data", but this is not strong objection). |
||
self._v1 = isinstance(backend, BackendV1) | ||
self._v2 = isinstance(backend, BackendV2) | ||
if self._v2: | ||
self._parse_additional_data() | ||
|
||
def _parse_additional_data(self): | ||
# data specific parsing not done yet in qiskit-terra | ||
if self._backend._conf_dict["open_pulse"]: | ||
if "u_channel_lo" not in self._backend._conf_dict: | ||
self._backend._conf_dict["u_channel_lo"] = [] # to avoid terra bug | ||
self._pulse_conf = PulseBackendConfiguration.from_dict(self._backend._conf_dict) | ||
|
||
@property | ||
def name(self): | ||
"""Returns the backend name""" | ||
if self._v1: | ||
return self._backend.name() | ||
elif self._v2: | ||
return self._backend.name | ||
return str(self._backend) | ||
|
||
def control_channel(self, qubits): | ||
"""Returns the backend control channels""" | ||
if self._v1: | ||
return self._backend.configuration().control(qubits) | ||
elif self._v2: | ||
try: | ||
return self._backend.control_channel(qubits) | ||
except (AttributeError, NotImplementedError): | ||
return self._pulse_conf.control_channels[qubits] | ||
return None | ||
|
||
@property | ||
def granularity(self): | ||
"""Returns the backend's time constraint granularity""" | ||
try: | ||
if self._v1: | ||
return self._backend.configuration().timing_constraints.get("granularity", 1) | ||
elif self._v2: | ||
return self._backend.target.granularity | ||
except AttributeError: | ||
return 1 | ||
return 1 | ||
|
||
@property | ||
def min_length(self): | ||
"""Returns the backend's time constraint minimum duration""" | ||
try: | ||
if self._v1: | ||
return self._backend.configuration().timing_constraints.get("min_length", 0) | ||
elif self._v2: | ||
return self._backend.target.min_length | ||
except AttributeError: | ||
return 0 | ||
return 0 | ||
|
||
@property | ||
def pulse_alignment(self): | ||
"""Returns the backend's time constraint pulse alignment""" | ||
try: | ||
if self._v1: | ||
return self._backend.configuration().timing_constraints.get("pulse_alignment", 1) | ||
elif self._v2: | ||
return self._backend.target.pulse_alignment | ||
except AttributeError: | ||
return 1 | ||
return 1 | ||
|
||
@property | ||
def aquire_alignment(self): | ||
"""Returns the backend's time constraint acquire alignment""" | ||
try: | ||
if self._v1: | ||
return self._backend.configuration().timing_constraints.get("aquire_alignment", 1) | ||
elif self._v2: | ||
return self._backend.target.aquire_alignment | ||
except AttributeError: | ||
gadial marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return 1 | ||
return 1 | ||
|
||
@property | ||
def dt(self): | ||
"""Returns the backend's input time resolution""" | ||
if self._v1: | ||
try: | ||
return self._backend.configuration().dt | ||
except AttributeError: | ||
return None | ||
elif self._v2: | ||
return self._backend.dt | ||
return None | ||
gadial marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@property | ||
def max_circuits(self): | ||
"""Returns the backend's max experiments value""" | ||
if self._v1: | ||
return getattr(self._backend.configuration(), "max_experiments", None) | ||
elif self._v2: | ||
return self._backend.max_circuits | ||
return None | ||
|
||
@property | ||
def coupling_map(self): | ||
"""Returns the backend's coupling map""" | ||
if self._v1: | ||
return getattr(self._backend.configuration(), "coupling_map", []) | ||
elif self._v2: | ||
return list(self._backend.coupling_map.get_edges()) | ||
return [] | ||
|
||
@property | ||
def control_channels(self): | ||
"""Returns the backend's control channels""" | ||
if self._v1: | ||
return getattr(self._backend.configuration(), "control_channels", None) | ||
elif self._v2: | ||
try: | ||
return self._backend.control_channels | ||
except AttributeError: | ||
return self._pulse_conf.control_channels | ||
return None | ||
|
||
@property | ||
def version(self): | ||
"""Returns the backend's version""" | ||
if self._v1: | ||
return getattr(self._backend, "version", None) | ||
elif self._v2: | ||
return self._backend.version | ||
return None | ||
|
||
@property | ||
def provider(self): | ||
"""Returns the backend's provider""" | ||
if self._v1: | ||
return getattr(self._backend, "provider", None) | ||
elif self._v2: | ||
return self._backend.provider | ||
return None | ||
|
||
@property | ||
def drive_freqs(self): | ||
"""Returns the backend's qubit frequency estimation""" | ||
gadial marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if self._v1: | ||
return getattr(self._backend.defaults(), "qubit_freq_est", []) | ||
elif self._v2: | ||
return [property.frequency for property in self._backend.target.qubit_properties] | ||
return [] | ||
|
||
@property | ||
def meas_freqs(self): | ||
"""Returns the backend's measurement frequency estimation. | ||
Note: currently BackendV2 does not have access to this data""" | ||
gadial marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if self._v1: | ||
return getattr(self._backend.defaults(), "meas_freq_est", []) | ||
elif self._v2: | ||
# meas_freq_est is currently not part of the BackendV2 | ||
return [] | ||
return [] | ||
|
||
@property | ||
def num_qubits(self): | ||
"""Returns the backend's number of qubits""" | ||
if self._v1: | ||
return self._backend.configuration().num_qubits | ||
elif self._v2: | ||
# meas_freq_est is currently not part of the BackendV2 | ||
return self._backend.num_qubits | ||
return None | ||
|
||
@property | ||
def is_simulator(self): | ||
"""Returns True given an indication the backend is a simulator | ||
Note: for `BackendV2` we sometimes cannot be sure""" | ||
gadial marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if self._v1: | ||
if self._backend.configuration().simulator or isinstance(self._backend, FakeBackend): | ||
return True | ||
if self._v2: | ||
if isinstance(self._backend, (FakeBackendV2, fake_backend.FakeBackendV2)): | ||
return True | ||
|
||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just using a class as a namespace, so I don't see any fundamental difference from defining a set of functions. I would turn this into an actual class with constructor taking a backend, so that you can store this backend configuration as an experiment or run options, i.e. note that currently we cannot serialize backend and thus hard-coded run/transpile options in a backend object are all discarded in the loaded experiment.
Perhaps it's useful to override the base experiment
_set_backend
method to internally generate this data and set the data instance to the experiment instance. Then all experiment subclasses can easily access these configuration data, e.g.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the suggestion of making this a full class that takes a backend as a constructor argument and then making most of the methods into properties. Setting it up from
_set_backend
makes sense as well.Making it serializable is also nice, though I don't know if it is worth it to serialize and attach the backend data to each experiment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now we don't attach anything to experiment instance IIRC. These will be separately saved as an artifact.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I'll do it as the last touch on the PR, in case we get more design ideas by then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, done.