From 89dd9442a65b646936e18ead02dfeefb8dbd2730 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 4 Mar 2022 07:17:05 -0500 Subject: [PATCH 1/3] Add qubit_properties to Target class This commit adds a new attribute to the target class for defining qubit properties. This was originally not included in the target because nothing in the transpiler supports using any qubit properties. However, in a future PR I'm planning to modify the default heuristic layout score used by VF2Layout to factor in T1 and/or T2 in addition to the measurement error rate it's using now. To do this though we need to ensure that we have these values available via the target. This commit also is the first time we've had to expand the set of information that can optionally be pecified in a target. I opted to just make a straight addition here rather than versioning it. There should be no backwards compatibility issue for users or provider maintainers on upgrade because not specifying a qubit properties payload will not change any behavior the Target and BackendV2 objects will behave exactly as before. I was thinking we'd only use a version if we needed to make a backwards incompatible change in the future. That way we can support both versions of the class a the same time while we migrated to a newer interface. But for simple additions like this that don't effect the behavior or cause any issues with compatibility we can simply add them to the class. --- qiskit/providers/backend.py | 17 ++++++++++-- qiskit/test/mock/fake_backend_v2.py | 35 ++++++++++++------------ qiskit/transpiler/__init__.py | 1 + qiskit/transpiler/target.py | 31 +++++++++++++++++++++ test/python/providers/test_backend_v2.py | 19 ++++++++++++- 5 files changed, 82 insertions(+), 21 deletions(-) diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index a706c2e62666..acd59b0690fb 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -474,12 +474,25 @@ def qubit_properties( 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:`~.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``. - raises: + Raises: NotImplementedError: if the backend doesn't support querying the qubit properties """ - raise NotImplementedError + # Since the target didn't always have a qubit properties attribute + # to ensure the behavior here is backwards compatible with earlier + # BacekendV2 implementations where this would raise a NotImplemented + # error. + if self.target.qubit_properties is None: + raise NotImplementedError + if isinstance(qubit, int): + return self.target.get_qubit_properties(qubit) + return [self.target.get_qubit_properties(q) for q in qubit] def drive_channel(self, qubit: int): """Return the drive channel for the given qubit. diff --git a/qiskit/test/mock/fake_backend_v2.py b/qiskit/test/mock/fake_backend_v2.py index 31b8698adcf2..6ec566ddc7cc 100644 --- a/qiskit/test/mock/fake_backend_v2.py +++ b/qiskit/test/mock/fake_backend_v2.py @@ -46,7 +46,11 @@ def __init__(self): online_date=datetime.datetime.utcnow(), backend_version="0.0.1", ) - self._target = Target() + self._qubit_properties = [ + QubitProperties(t1=63.48783e-6, t2=112.23246e-6, frequency=5.17538e9), + QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), + ] + self._target = Target(qubit_properties=self._qubit_properties) self._theta = Parameter("theta") self._phi = Parameter("phi") self._lam = Parameter("lambda") @@ -79,10 +83,6 @@ def __init__(self): } self._target.add_instruction(ECRGate(), ecr_props) self.options.set_validator("shots", (1, 4096)) - self._qubit_properties = { - 0: QubitProperties(t1=63.48783e-6, t2=112.23246e-6, frequency=5.17538e9), - 1: QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), - } @property def target(self): @@ -99,6 +99,10 @@ def _default_options(cls): def run(self, run_input, **options): raise NotImplementedError + +class FakeBackendV2LegacyQubitProps(FakeBackendV2): + """Fake backend that doesn't use qubit properties via the target.""" + def qubit_properties(self, qubit): if isinstance(qubit, int): return self._qubit_properties[qubit] @@ -116,7 +120,14 @@ def __init__(self, bidirectional=True): online_date=datetime.datetime.utcnow(), backend_version="0.0.1", ) - self._target = Target() + qubit_properties = [ + QubitProperties(t1=63.48783e-6, t2=112.23246e-6, frequency=5.17538e9), + QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), + QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), + QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), + QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), + ] + self._target = Target(qubit_properties=qubit_properties) self._theta = Parameter("theta") self._phi = Parameter("phi") self._lam = Parameter("lambda") @@ -153,13 +164,6 @@ def __init__(self, bidirectional=True): ecr_props[(3, 2)] = InstructionProperties(duration=5.52e-9, error=0.0000232115) self._target.add_instruction(ECRGate(), ecr_props) self.options.set_validator("shots", (1, 4096)) - self._qubit_properties = { - 0: QubitProperties(t1=63.48783e-6, t2=112.23246e-6, frequency=5.17538e9), - 1: QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), - 2: QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), - 3: QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), - 4: QubitProperties(t1=73.09352e-6, t2=126.83382e-6, frequency=5.26722e9), - } @property def target(self): @@ -176,11 +180,6 @@ def _default_options(cls): def run(self, run_input, **options): raise NotImplementedError - def qubit_properties(self, qubit): - if isinstance(qubit, int): - return self._qubit_properties[qubit] - return [self._qubit_properties[i] for i in qubit] - class FakeBackendSimple(BackendV2): """A fake simple backend that wraps BasicAer to implement run().""" diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 2ad8cf09f9a4..0699aa42dfc5 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -435,3 +435,4 @@ from .instruction_durations import InstructionDurations from .target import Target from .target import InstructionProperties +from .target import QubitProperties diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 13dcf56f0db8..208faca6ae79 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -27,6 +27,10 @@ from qiskit.transpiler.instruction_durations import InstructionDurations from qiskit.transpiler.timing_constraints import TimingConstraints +# import QubitProperties here to provide convenience alias for building a +# full target +from qiskit.providers.backend import QubitProperties # pylint: disable=unused-import + logger = logging.getLogger(__name__) @@ -169,6 +173,7 @@ class Target(Mapping): "aquire_alignment", "_non_global_basis", "_non_global_strict_basis", + "qubit_properties", ) def __init__( @@ -180,6 +185,7 @@ def __init__( min_length=1, pulse_alignment=1, aquire_alignment=1, + qubit_properties=None, ): """ Create a new Target object @@ -208,6 +214,17 @@ def __init__( resolution of measure instruction starting time. Measure instruction should start at time which is a multiple of the alignment value. + qubit_properties (list): A list of :class:`~.QubitProperties` + objects defining the characteristics of each qubit on the + target device. If specified the length of this list must match + the number of qubits in the target, where the index in the list + matches the qubit number the properties are defined for. If some + qubits don't have properties available you can set that entry to + ``None`` + Raises: + ValueError: If both ``num_qubits`` and ``qubit_properties`` are both + defined and the value of ``num_qubits`` differs from the length of + ``qubit_properties``. """ self.num_qubits = num_qubits # A mapping of gate name -> gate instance @@ -227,6 +244,20 @@ def __init__( self.aquire_alignment = aquire_alignment self._non_global_basis = None self._non_global_strict_basis = None + if qubit_properties is not None: + if not self.num_qubits: + self.num_qubits = len(qubit_properties) + else: + if self.num_qubits != len(qubit_properties): + raise ValueError( + "The value of num_qubits specified does not match the " + "length of the input qubit_properties list" + ) + self.qubit_properties = qubit_properties + + def get_qubit_properties(self, qubit): + """Return the qubit properties for the given qubit.""" + return self.qubit_properties[qubit] def add_instruction(self, instruction, properties=None, name=None): """Add a new instruction to the :class:`~qiskit.transpiler.Target` diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index 0f9d0204ae47..e741cd3ac1bc 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -22,7 +22,12 @@ from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister from qiskit.compiler import transpile from qiskit.test.base import QiskitTestCase -from qiskit.test.mock.fake_backend_v2 import FakeBackendV2, FakeBackend5QV2 +from qiskit.test.mock.fake_backend_v2 import ( + FakeBackendV2, + FakeBackend5QV2, + FakeBackendSimple, + FakeBackendV2LegacyQubitProps, +) from qiskit.test.mock.fake_mumbai_v2 import FakeMumbaiV2 from qiskit.quantum_info import Operator @@ -51,6 +56,18 @@ def test_qubit_properties(self): self.assertEqual([126.83382e-6, 112.23246e-6], [x.t2 for x in props]) self.assertEqual([5.26722e9, 5.17538e9], [x.frequency for x in props]) + def test_legacy_qubit_properties(self): + """Test that qubit props work for backends not using properties in target.""" + props = FakeBackendV2LegacyQubitProps().qubit_properties([1, 0]) + self.assertEqual([73.09352e-6, 63.48783e-6], [x.t1 for x in props]) + self.assertEqual([126.83382e-6, 112.23246e-6], [x.t2 for x in props]) + self.assertEqual([5.26722e9, 5.17538e9], [x.frequency for x in props]) + + def test_no_qubit_properties_raises(self): + """Ensure that if no qubit properties are defined we raise correctly.""" + with self.assertRaises(NotImplementedError): + FakeBackendSimple().qubit_properties(0) + def test_option_bounds(self): """Test that option bounds are enforced.""" with self.assertRaises(ValueError) as cm: From 052fc62564e91250201d06cce79ecb9b454aa909 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 29 Mar 2022 18:14:09 -0400 Subject: [PATCH 2/3] Remove unecessary getter function from target This commit removes an unecessary getter method to access the qubit_properties attribute of the target. The attribute was already public and we could get the qubit properties directly from the list stored in the target. --- qiskit/providers/backend.py | 4 ++-- qiskit/transpiler/target.py | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index acd59b0690fb..028cf8895742 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -491,8 +491,8 @@ def qubit_properties( if self.target.qubit_properties is None: raise NotImplementedError if isinstance(qubit, int): - return self.target.get_qubit_properties(qubit) - return [self.target.get_qubit_properties(q) for q in qubit] + return self.target.qubit_properties[qubit] + return [self.target.qubit_properties[q] for q in qubit] def drive_channel(self, qubit: int): """Return the drive channel for the given qubit. diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index d06d02b4d5de..ed79704afd31 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -255,10 +255,6 @@ def __init__( ) self.qubit_properties = qubit_properties - def get_qubit_properties(self, qubit): - """Return the qubit properties for the given qubit.""" - return self.qubit_properties[qubit] - def add_instruction(self, instruction, properties=None, name=None): """Add a new instruction to the :class:`~qiskit.transpiler.Target` From d4e0fd4eec5270fbda05393804f3a7d3f21d7978 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 30 Mar 2022 10:34:49 -0400 Subject: [PATCH 3/3] Add missing release note --- ...it-properties-target-6b1fb155a46cb942.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 releasenotes/notes/qubit-properties-target-6b1fb155a46cb942.yaml diff --git a/releasenotes/notes/qubit-properties-target-6b1fb155a46cb942.yaml b/releasenotes/notes/qubit-properties-target-6b1fb155a46cb942.yaml new file mode 100644 index 000000000000..eb01b2bd1e6e --- /dev/null +++ b/releasenotes/notes/qubit-properties-target-6b1fb155a46cb942.yaml @@ -0,0 +1,21 @@ +--- +features: + - | + Added a new attribute, :attr:`~.Target.qubit_properties` to the + :class:`~.Target` class. This attribute contains a list of + :class:`~.QubitProperties` objects for each qubit in the target. + For example:: + + target.qubit_properties[2] + + will contain the :class:`~.QubitProperties` for qubit number 2 in the + target. + + For :class:`~.BackendV2` authors, if you were previously defining + :class:`~.QubitProperties` directly on your :class:`~.BackendV2` + implementation by overriding :meth:`.BackendV2.qubit_properties` this + will still work fine. However, if you do move the definition to the + underlying :class:`~.Target` object and remove the specialized + :meth:`.BackendV2.qubit_properties` implementation which will enable + using qubit properties in the transpiler and also maintain API compatibility + with your previous implementation