Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9bb60ae
* Implemented method to setup single-qubit calibrations.
eggerdj Jun 17, 2021
6b83656
Merge branch 'main' into calibrations_setup
eggerdj Jun 21, 2021
9b16df9
Merge branch 'main' into calibrations_setup
eggerdj Jul 21, 2021
f973582
* Refactored cals setup to library style.
eggerdj Jul 21, 2021
262f2a4
* Fix sigma in test.
eggerdj Jul 21, 2021
319de19
Merge branch 'main' into calibrations_setup
eggerdj Jul 21, 2021
894794a
Update qiskit_experiments/calibration_management/basis_gate_library.py
eggerdj Jul 21, 2021
97524de
Merge branch 'main' into calibrations_setup
eggerdj Jul 21, 2021
d10d155
* Made library options kwargs.
eggerdj Jul 21, 2021
59bde3e
* Added error raising.
eggerdj Jul 21, 2021
b33f73b
* Added basis gates property.
eggerdj Jul 21, 2021
58acc43
* Renamed transmon library.
eggerdj Jul 21, 2021
c5962fe
* Docs.
eggerdj Jul 21, 2021
4ae1763
* Added better Drag optionality.
eggerdj Jul 21, 2021
654d017
* Added y and sy gates to library.
eggerdj Jul 21, 2021
1dadebe
* Add check to make sure we do not add default values for gates that …
eggerdj Jul 21, 2021
577394b
Update qiskit_experiments/calibration_management/basis_gate_library.py
eggerdj Jul 22, 2021
84ee237
Merge branch 'main' into calibrations_setup
eggerdj Jul 22, 2021
529d3b1
* Docs.
eggerdj Jul 22, 2021
977122c
Merge branch 'calibrations_setup' of github.com:eggerdj/qiskit-experi…
eggerdj Jul 22, 2021
824bb44
* Update the way the library is passed to BackendCalibrations.
eggerdj Jul 22, 2021
332f8a4
* Lint.
eggerdj Jul 22, 2021
fd4b286
Merge branch 'main' into calibrations_setup
eggerdj Jul 22, 2021
8282ee0
* Added option to link parameters.
eggerdj Jul 22, 2021
86c3e9a
Merge branch 'calibrations_setup' of github.com:eggerdj/qiskit-experi…
eggerdj Jul 22, 2021
d3c0b69
* Partial generation of basis gates.
eggerdj Jul 22, 2021
4ca85e4
* Added test.
eggerdj Jul 22, 2021
58fad06
Merge branch 'main' into calibrations_setup
eggerdj Jul 22, 2021
a457f1a
* Fixed test issue and added in operator.
eggerdj Jul 22, 2021
3ac1e08
Merge branch 'calibrations_setup' of github.com:eggerdj/qiskit-experi…
eggerdj Jul 22, 2021
9e478b2
Merge branch 'main' into calibrations_setup
eggerdj Jul 23, 2021
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
42 changes: 40 additions & 2 deletions qiskit_experiments/calibration_management/backend_calibrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from qiskit.circuit import Parameter
from qiskit_experiments.calibration_management.calibrations import Calibrations, ParameterKey
from qiskit_experiments.exceptions import CalibrationError
from qiskit_experiments.calibration_management.basis_gate_library import BasisGateLibrary


class FrequencyElement(Enum):
Expand All @@ -43,8 +44,33 @@ class BackendCalibrations(Calibrations):
__qubit_freq_parameter__ = "qubit_lo_freq"
__readout_freq_parameter__ = "meas_lo_freq"

def __init__(self, backend: Backend):
"""Setup an instance to manage the calibrations of a backend."""
def __init__(
self,
backend: Backend,
library: BasisGateLibrary = None,
):
"""Setup an instance to manage the calibrations of a backend.

BackendCalibrations can be initialized from a basis gate library, i.e. a subclass of
:class:`BasisGateLibrary`. As example consider the following code:

.. code-block:: python

cals = BackendCalibrations(
backend,
library=FixedFrequencyTransmon(
basis_gates=["x", "sx"],
default_values={duration: 320}
)
)

Args:
backend: A backend instance from which to extract the qubit and readout frequencies
(which will be added as first guesses for the corresponding parameters) as well
as the coupling map.
library: A library class that will be instantiated with the library options to then
get template schedules to register as well as default parameter values.
"""
if hasattr(backend.configuration(), "control_channels"):
control_channels = backend.configuration().control_channels
else:
Expand All @@ -67,6 +93,18 @@ def __init__(self, backend: Backend):
for meas, freq in enumerate(backend.defaults().meas_freq_est):
self.add_parameter_value(freq, self.meas_freq, meas)

if library is not None:

# Add the basis gates
for gate in library.basis_gates:
self.add_schedule(library[gate])

# Add the default values
for param_conf in library.default_values():
schedule_name = param_conf[-1]
if schedule_name in library.basis_gates:
self.add_parameter_value(*param_conf)

def _get_frequencies(
self,
element: FrequencyElement,
Expand Down
207 changes: 207 additions & 0 deletions qiskit_experiments/calibration_management/basis_gate_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# 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.

"""
A collections of libraries to setup Calibrations.

Note that the set of available libraries will be extended in future releases.
"""

from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Tuple

from qiskit.circuit import Parameter
import qiskit.pulse as pulse
from qiskit.pulse import ScheduleBlock

from qiskit_experiments.calibration_management.calibration_key_types import ParameterValueType
from qiskit_experiments.exceptions import CalibrationError


class BasisGateLibrary(ABC):
"""A base class for libraries of basis gates to make it easier to setup Calibrations."""

# Location where default parameter values are stored. These may be updated at construction.
__default_values__ = dict()

# The basis gates that this library generates.
__supported_gates__ = None

def __init__(
self, basis_gates: Optional[List[str]] = None, default_values: Optional[Dict] = None
):
"""Setup the library.

Args:
basis_gates: The basis gates to generate.
default_values: A dictionary to override library default parameter values.

Raises:
CalibrationError: If on of the given basis gates is not supported by the library.
"""
self._schedules = dict()

# Update the default values.
self._default_values = dict(self.__default_values__)
if default_values is not None:
self._default_values.update(default_values)

if basis_gates is not None:
for gate in basis_gates:
if gate not in self.__supported_gates__:
raise CalibrationError(
f"Gate {gate} is not supported by {self.__class__.__name__}. "
f"Supported gates are: {self.__supported_gates__}."
)

self._basis_gates = basis_gates or self.__supported_gates__

def __getitem__(self, name: str) -> ScheduleBlock:
"""Return the schedule."""
if name not in self._schedules:
raise CalibrationError(f"Gate {name} is not contained in {self.__class__.__name__}.")

return self._schedules[name]

def __contains__(self, name: str) -> bool:
"""Check if the basis gate is in the library."""
return name in self._schedules

@property
def basis_gates(self) -> List[str]:
"""Return the basis gates supported by the library."""
return list(name for name in self._schedules)

@abstractmethod
def default_values(self) -> List[Tuple[ParameterValueType, Parameter, Tuple, ScheduleBlock]]:
"""Return the default values for the parameters.

Returns
A list of tuples is returned. These tuples are structured so that instances of
:class:`Calibrations` can call :meth:`add_parameter_value` on the tuples.
"""


class FixedFrequencyTransmon(BasisGateLibrary):
"""A library of gates for fixed-frequency superconducting qubit architectures.

Note that for now this library supports single-qubit gates and will be extended
in the future.
"""

__default_values__ = {"duration": 160, "amp": 0.5, "β": 0.0}

__supported_gates__ = ["x", "y", "sx", "sy"]

def __init__(
self,
basis_gates: Optional[List[str]] = None,
default_values: Optional[Dict] = None,
use_drag: bool = True,
link_parameters: bool = True,
):
"""Setup the schedules.

Args:
basis_gates: The basis gates to generate.
default_values: Default values for the parameters this dictionary can contain
the following keys: "duration", "amp", "β", and "σ". If "σ" is not provided
this library will take one fourth of the pulse duration as default value.
use_drag: If set to False then Gaussian pulses will be used instead of DRAG
pulses.
link_parameters: if set to True then the amplitude and DRAG parameters of the
X and Y gates will be linked as well as those of the SX and SY gates.
"""
super().__init__(basis_gates, default_values)
self._link_parameters = link_parameters

dur = Parameter("duration")
sigma = Parameter("σ")

# Generate the pulse parameters
def _beta(use_drag):
return Parameter("β") if use_drag else None

x_amp, x_beta = Parameter("amp"), _beta(use_drag)

if self._link_parameters:
y_amp, y_beta = 1.0j * x_amp, x_beta
else:
y_amp, y_beta = Parameter("amp"), _beta(use_drag)

sx_amp, sx_beta = Parameter("amp"), _beta(use_drag)

if self._link_parameters:
sy_amp, sy_beta = 1.0j * sx_amp, sx_beta
else:
sy_amp, sy_beta = Parameter("amp"), _beta(use_drag)

# Create the schedules for the gates
sched_x = self._single_qubit_schedule("x", dur, x_amp, sigma, x_beta)
sched_y = self._single_qubit_schedule("y", dur, y_amp, sigma, y_beta)
sched_sx = self._single_qubit_schedule("sx", dur, sx_amp, sigma, sx_beta)
sched_sy = self._single_qubit_schedule("sy", dur, sy_amp, sigma, sy_beta)

for sched in [sched_x, sched_y, sched_sx, sched_sy]:
if sched.name in self._basis_gates:
self._schedules[sched.name] = sched

@staticmethod
def _single_qubit_schedule(
name: str,
dur: Parameter,
amp: Parameter,
sigma: Parameter,
beta: Optional[Parameter] = None,
) -> ScheduleBlock:
"""Build a single qubit pulse."""

chan = pulse.DriveChannel(Parameter("ch0"))

if beta is not None:
with pulse.build(name=name) as sched:
pulse.play(pulse.Drag(duration=dur, amp=amp, sigma=sigma, beta=beta), chan)
else:
with pulse.build(name=name) as sched:
pulse.play(pulse.Gaussian(duration=dur, amp=amp, sigma=sigma), chan)

return sched

def default_values(self) -> List[Tuple[ParameterValueType, Parameter, Tuple, ScheduleBlock]]:
"""Return the default values for the parameters.

Returns
A list of tuples is returned. These tuples are structured so that instances of
:class:`Calibrations` can call :meth:`add_parameter_value` on the tuples.
"""
defaults = []
for name in self.basis_gates:
schedule = self._schedules[name]
for param in schedule.parameters:
if "ch" not in param.name:
if "y" in name and self._link_parameters:
continue

if param.name == "σ" and "σ" not in self._default_values:
value = self._default_values["duration"] / 4
else:
value = self._default_values[param.name]

if name in {"sx", "sy"} and param.name == "amp":
value /= 2.0

if "y" in name and param.name == "amp":
value *= 1.0j

defaults.append((value, param.name, tuple(), name))

return defaults
5 changes: 4 additions & 1 deletion qiskit_experiments/calibration_management/update_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@ def update(
rate = 2 * np.pi * freq

for angle, param, schedule in angles_schedules:
value = np.round(angle / rate, decimals=8)
qubits = exp_data.data(0)["metadata"]["qubits"]
prev_amp = calibrations.get_parameter_value(param, qubits, schedule, group=group)

value = np.round(angle / rate, decimals=8) * np.exp(1.0j * np.angle(prev_amp))

cls._add_parameter_value(calibrations, exp_data, value, param, schedule, group)

Expand Down
25 changes: 25 additions & 0 deletions test/calibration/test_backend_calibrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@

"""Class to test the backend calibrations."""

import qiskit.pulse as pulse
from qiskit.test import QiskitTestCase
from qiskit.test.mock import FakeArmonk

from qiskit_experiments.calibration_management import BackendCalibrations
from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon


class TestBackendCalibrations(QiskitTestCase):
Expand All @@ -26,3 +29,25 @@ def test_run_options(self):

self.assertEqual(cals.get_meas_frequencies(), [6993370669.000001])
self.assertEqual(cals.get_qubit_frequencies(), [4971852852.405576])

def test_setup_withLibrary(self):
"""Test that we can setup with a library."""

cals = BackendCalibrations(
FakeArmonk(),
library=FixedFrequencyTransmon(
basis_gates=["x", "sx"], default_values={"duration": 320}
),
)

# Check the x gate
with pulse.build(name="x") as expected:
pulse.play(pulse.Drag(duration=320, amp=0.5, sigma=80, beta=0), pulse.DriveChannel(0))

self.assertEqual(cals.get_schedule("x", (0,)), expected)

# Check the sx gate
with pulse.build(name="sx") as expected:
pulse.play(pulse.Drag(duration=320, amp=0.25, sigma=80, beta=0), pulse.DriveChannel(0))

self.assertEqual(cals.get_schedule("sx", (0,)), expected)
Loading