Skip to content
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

Add qubit_properties to Target class #7736

Merged
merged 6 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions qiskit/providers/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.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.
Expand Down
35 changes: 17 additions & 18 deletions qiskit/test/mock/fake_backend_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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):
Expand All @@ -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]
Expand All @@ -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")
Expand Down Expand Up @@ -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):
Expand All @@ -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()."""
Comment on lines 184 to 185
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not introduced by this PR but I think it would be good to explain in the docstring the difference between FakeBackendV2 and FakeBackendSimple.

Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,3 +437,4 @@
from .instruction_durations import InstructionDurations
from .target import Target
from .target import InstructionProperties
from .target import QubitProperties
27 changes: 27 additions & 0 deletions qiskit/transpiler/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Comment on lines +30 to +33
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand the motivation of this line. Can you explain a bit more?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a convenience thing since QubitProperties is now optionally part of a target I thought it would be good to have a single import for everything needed to make a taget. This is just to enable someone creating a Target object to do:

from qiskit.transpiler.target import Target, InstructionProperties, QubitProperties

instead of:

from qiskit.transpiler.target import Target, InstructionProperties
from qiskit.providers.backend import QubitProperties

(although the latter will still work even with this.)

But, it is a bit weird to do this, so I'm not really tied to it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes a lot of sense. It's much more convenient this way.

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -169,6 +173,7 @@ class Target(Mapping):
"aquire_alignment",
"_non_global_basis",
"_non_global_strict_basis",
"qubit_properties",
)

def __init__(
Expand All @@ -180,6 +185,7 @@ def __init__(
min_length=1,
pulse_alignment=1,
aquire_alignment=1,
qubit_properties=None,
):
"""
Create a new Target object
Expand Down Expand Up @@ -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
Expand All @@ -227,6 +244,16 @@ 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 add_instruction(self, instruction, properties=None, name=None):
"""Add a new instruction to the :class:`~qiskit.transpiler.Target`
Expand Down
19 changes: 18 additions & 1 deletion test/python/providers/test_backend_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@
from qiskit.compiler.transpiler import _parse_inst_map
from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap
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

Expand Down Expand Up @@ -53,6 +58,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:
Expand Down