diff --git a/qiskit_experiments/calibration_management/base_calibration_experiment.py b/qiskit_experiments/calibration_management/base_calibration_experiment.py index d453f0b31b..9a6a6118e9 100644 --- a/qiskit_experiments/calibration_management/base_calibration_experiment.py +++ b/qiskit_experiments/calibration_management/base_calibration_experiment.py @@ -29,7 +29,7 @@ SetLayout, ) -from qiskit_experiments.calibration_management.calibrations import Calibrations +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.calibration_management.update_library import BaseUpdater from qiskit_experiments.framework.base_analysis import BaseAnalysis from qiskit_experiments.framework.base_experiment import BaseExperiment @@ -112,7 +112,7 @@ def __init_subclass__(cls, **kwargs): # pylint: disable=super-init-not-called def __init__( self, - calibrations: Calibrations, + calibrations: BaseCalibrations, *args, schedule_name: Optional[str] = None, cal_parameter_name: Optional[str] = None, @@ -144,7 +144,7 @@ def __init__( self.auto_update = auto_update @property - def calibrations(self) -> Calibrations: + def calibrations(self) -> BaseCalibrations: """Return the calibrations.""" return self._cals diff --git a/qiskit_experiments/calibration_management/base_calibrations.py b/qiskit_experiments/calibration_management/base_calibrations.py new file mode 100644 index 0000000000..82b4277186 --- /dev/null +++ b/qiskit_experiments/calibration_management/base_calibrations.py @@ -0,0 +1,114 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +"""Base calibrations class.""" + +from abc import ABC, abstractmethod +from typing import Dict, Tuple, Union + +from qiskit.circuit import Parameter +from qiskit.pulse import ScheduleBlock + +from qiskit_experiments.calibration_management.parameter_value import ParameterValue +from qiskit_experiments.calibration_management.calibration_key_types import ( + ParameterKey, + ParameterValueType, +) + + +class BaseCalibrations(ABC): + """An abstract base calibration class that defines the methods needed by cal. experiments. + + A calibration experiment uses an instance of `BaseCalibrations` that defines where + to get parameter values from and where to save them. This class also defines a method from + which to retrieve pulse-schedules. + """ + + @abstractmethod + def add_parameter_value( + self, + value: Union[int, float, complex, ParameterValue], + param: Union[Parameter, str], + qubits: Union[int, Tuple[int, ...]] = None, + schedule: Union[ScheduleBlock, str] = None, + ): + """Add a parameter value to the stored parameters. + + This parameter value may be applied to several channels, for instance, all + DRAG pulses may have the same standard deviation. + + Args: + value: The value of the parameter to add. If an int, float, or complex is given + then the timestamp of the parameter value will automatically be generated + and set to the current local time of the user. + param: The parameter or its name for which to add the measured value. + qubits: The qubits to which this parameter applies. + schedule: The schedule or its name for which to add the measured parameter value. + """ + + @abstractmethod + def get_parameter_value( + self, + param: Union[Parameter, str], + qubits: Union[int, Tuple[int, ...]], + schedule: Union[ScheduleBlock, str, None] = None, + ) -> Union[int, float, complex]: + """Retrieves the value of a parameter. + + Parameters may be linked. :meth:`get_parameter_value` does the following steps: + + Args: + param: The parameter or the name of the parameter for which to get the parameter value. + qubits: The qubits for which to get the value of the parameter. + schedule: The schedule or its name for which to get the parameter value. + + Returns: + value: The value of the parameter. + """ + + @abstractmethod + def get_schedule( + self, + name: str, + qubits: Union[int, Tuple[int, ...]], + assign_params: Dict[Union[str, ParameterKey], ParameterValueType] = None, + ) -> ScheduleBlock: + """Get the template schedule with parameters assigned to values. + + All the parameters in the template schedule block will be assigned to the values managed + by the calibrations unless they are specified in assign_params. In this case the value in + assign_params will override the value stored by the calibrations. A parameter value in + assign_params may also be a :class:`ParameterExpression`. + + .. code-block:: python + + # Get an xp schedule with a parametric amplitude + sched = cals.get_schedule("xp", 3, assign_params={"amp": Parameter("amp")}) + + # Get an echoed-cross-resonance schedule between qubits (0, 2) where the xp echo gates + # are referenced schedules but leave their amplitudes as parameters. + assign_dict = {("amp", (0,), "xp"): Parameter("my_amp")} + sched = cals.get_schedule("cr", (0, 2), assign_params=assign_dict) + + Args: + name: The name of the schedule to get. + qubits: The qubits for which to get the schedule. + assign_params: The parameters to assign manually. Each parameter is specified by a + ParameterKey which is a named tuple of the form (parameter name, qubits, + schedule name). Each entry in assign_params can also be a string corresponding + to the name of the parameter. In this case, the schedule name and qubits of the + corresponding ParameterKey will be the name and qubits given as arguments to + get_schedule. + + Returns: + schedule: A copy of the template schedule with all parameters assigned. + """ diff --git a/qiskit_experiments/calibration_management/calibrations.py b/qiskit_experiments/calibration_management/calibrations.py index 0ee98c707b..4ac8105f53 100644 --- a/qiskit_experiments/calibration_management/calibrations.py +++ b/qiskit_experiments/calibration_management/calibrations.py @@ -38,6 +38,7 @@ from qiskit.utils.deprecation import deprecate_func, deprecate_arg from qiskit_experiments.exceptions import CalibrationError +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.calibration_management.basis_gate_library import BasisGateLibrary from qiskit_experiments.calibration_management.parameter_value import ParameterValue from qiskit_experiments.calibration_management.control_channel_map import ControlChannelMap @@ -55,7 +56,7 @@ from qiskit_experiments.framework import BackendData, ExperimentEncoder, ExperimentDecoder -class Calibrations: +class Calibrations(BaseCalibrations): """ A class to manage schedules with calibrated parameter values. Schedules are intended to be fully parameterized, including the index of the channels. See diff --git a/qiskit_experiments/calibration_management/update_library.py b/qiskit_experiments/calibration_management/update_library.py index ff6009432c..f0a0d7d095 100644 --- a/qiskit_experiments/calibration_management/update_library.py +++ b/qiskit_experiments/calibration_management/update_library.py @@ -20,7 +20,7 @@ from qiskit.pulse import ScheduleBlock from qiskit_experiments.framework.experiment_data import ExperimentData -from qiskit_experiments.calibration_management.calibrations import Calibrations +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.calibration_management.parameter_value import ParameterValue from qiskit_experiments.calibration_management.calibration_key_types import ParameterValueType from qiskit_experiments.exceptions import CalibrationError @@ -60,7 +60,7 @@ def _time_stamp(exp_data: ExperimentData) -> datetime: @classmethod def add_parameter_value( cls, - cal: Calibrations, + cal: BaseCalibrations, exp_data: ExperimentData, value: ParameterValueType, param: Union[Parameter, str], @@ -94,7 +94,7 @@ def add_parameter_value( @classmethod def update( cls, - calibrations: Calibrations, + calibrations: BaseCalibrations, exp_data: ExperimentData, parameter: str, schedule: Optional[Union[ScheduleBlock, str]], @@ -146,7 +146,7 @@ class Frequency(BaseUpdater): @classmethod def update( cls, - calibrations: Calibrations, + calibrations: BaseCalibrations, exp_data: ExperimentData, result_index: Optional[int] = None, parameter: str = "drive_freq", diff --git a/qiskit_experiments/library/calibration/fine_amplitude.py b/qiskit_experiments/library/calibration/fine_amplitude.py index 8e3ff17972..ccf0ab6034 100644 --- a/qiskit_experiments/library/calibration/fine_amplitude.py +++ b/qiskit_experiments/library/calibration/fine_amplitude.py @@ -18,10 +18,8 @@ from qiskit.circuit import Gate, QuantumCircuit from qiskit.providers.backend import Backend -from qiskit_experiments.calibration_management import ( - BaseCalibrationExperiment, - Calibrations, -) +from qiskit_experiments.calibration_management import BaseCalibrationExperiment +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.library.characterization import FineAmplitude from qiskit_experiments.framework import ExperimentData, Options from qiskit_experiments.calibration_management.update_library import BaseUpdater @@ -41,7 +39,7 @@ class FineAmplitudeCal(BaseCalibrationExperiment, FineAmplitude): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, schedule_name: str, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "amp", @@ -161,7 +159,7 @@ class FineXAmplitudeCal(FineAmplitudeCal): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, schedule_name: str, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "amp", @@ -209,7 +207,7 @@ class FineSXAmplitudeCal(FineAmplitudeCal): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, schedule_name: str, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "amp", diff --git a/qiskit_experiments/library/calibration/fine_drag_cal.py b/qiskit_experiments/library/calibration/fine_drag_cal.py index 7c52e2d6bd..39e7118259 100644 --- a/qiskit_experiments/library/calibration/fine_drag_cal.py +++ b/qiskit_experiments/library/calibration/fine_drag_cal.py @@ -21,10 +21,8 @@ from qiskit_experiments.exceptions import CalibrationError from qiskit_experiments.framework import ExperimentData, Options -from qiskit_experiments.calibration_management import ( - BaseCalibrationExperiment, - Calibrations, -) +from qiskit_experiments.calibration_management import BaseCalibrationExperiment +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.calibration_management.update_library import BaseUpdater from qiskit_experiments.library.characterization.fine_drag import FineDrag @@ -35,7 +33,7 @@ class FineDragCal(BaseCalibrationExperiment, FineDrag): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, schedule_name: str, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "β", @@ -153,7 +151,7 @@ class FineXDragCal(FineDragCal): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "β", auto_update: bool = True, @@ -185,7 +183,7 @@ class FineSXDragCal(FineDragCal): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "β", auto_update: bool = True, diff --git a/qiskit_experiments/library/calibration/fine_frequency_cal.py b/qiskit_experiments/library/calibration/fine_frequency_cal.py index 5fe3251f89..fe61161689 100644 --- a/qiskit_experiments/library/calibration/fine_frequency_cal.py +++ b/qiskit_experiments/library/calibration/fine_frequency_cal.py @@ -20,10 +20,8 @@ from qiskit_experiments.framework import ExperimentData from qiskit_experiments.calibration_management.update_library import BaseUpdater -from qiskit_experiments.calibration_management import ( - BaseCalibrationExperiment, - Calibrations, -) +from qiskit_experiments.calibration_management import BaseCalibrationExperiment +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.library.characterization.fine_frequency import FineFrequency @@ -33,7 +31,7 @@ class FineFrequencyCal(BaseCalibrationExperiment, FineFrequency): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "drive_freq", delay_duration: Optional[int] = None, diff --git a/qiskit_experiments/library/calibration/frequency_cal.py b/qiskit_experiments/library/calibration/frequency_cal.py index 5f683b217d..0f2d9372e6 100644 --- a/qiskit_experiments/library/calibration/frequency_cal.py +++ b/qiskit_experiments/library/calibration/frequency_cal.py @@ -19,7 +19,7 @@ from qiskit_experiments.framework import ExperimentData from qiskit_experiments.library.characterization.ramsey_xy import RamseyXY -from qiskit_experiments.calibration_management.calibrations import Calibrations +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.calibration_management.update_library import BaseUpdater from qiskit_experiments.calibration_management.base_calibration_experiment import ( BaseCalibrationExperiment, @@ -32,7 +32,7 @@ class FrequencyCal(BaseCalibrationExperiment, RamseyXY): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "drive_freq", delays: Optional[List] = None, diff --git a/qiskit_experiments/library/calibration/half_angle_cal.py b/qiskit_experiments/library/calibration/half_angle_cal.py index e56ca7f7df..bba906de68 100644 --- a/qiskit_experiments/library/calibration/half_angle_cal.py +++ b/qiskit_experiments/library/calibration/half_angle_cal.py @@ -19,10 +19,8 @@ from qiskit_experiments.framework import ExperimentData from qiskit_experiments.exceptions import CalibrationError -from qiskit_experiments.calibration_management import ( - BaseCalibrationExperiment, - Calibrations, -) +from qiskit_experiments.calibration_management import BaseCalibrationExperiment +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.library.characterization import HalfAngle from qiskit_experiments.calibration_management.update_library import BaseUpdater @@ -33,7 +31,7 @@ class HalfAngleCal(BaseCalibrationExperiment, HalfAngle): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, backend: Optional[Backend] = None, schedule_name: str = "sx", cal_parameter_name: Optional[str] = "angle", @@ -98,7 +96,7 @@ def __init__( ) def _metadata(self) -> Dict[str, any]: - """Add metadata to the experiment data making it more self contained. + """Add metadata to the experiment data making it more self-contained. The following keys are added to the experiment's metadata: cal_param_value: The value of the pulse amplitude. This value together with diff --git a/qiskit_experiments/library/calibration/rough_amplitude_cal.py b/qiskit_experiments/library/calibration/rough_amplitude_cal.py index c6bea05d53..bcd13f2cb0 100644 --- a/qiskit_experiments/library/calibration/rough_amplitude_cal.py +++ b/qiskit_experiments/library/calibration/rough_amplitude_cal.py @@ -21,7 +21,9 @@ from qiskit.providers.backend import Backend from qiskit_experiments.framework import ExperimentData -from qiskit_experiments.calibration_management import BaseCalibrationExperiment, Calibrations +from qiskit_experiments.calibration_management import BaseCalibrationExperiment +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations +from qiskit_experiments.exceptions import CalibrationError from qiskit_experiments.library.characterization import Rabi from qiskit_experiments.calibration_management.update_library import BaseUpdater @@ -36,7 +38,7 @@ class RoughAmplitudeCal(BaseCalibrationExperiment, Rabi): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, schedule_name: str = "x", amplitudes: Iterable[float] = None, cal_parameter_name: Optional[str] = "amp", @@ -194,7 +196,7 @@ class RoughXSXAmplitudeCal(RoughAmplitudeCal): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, amplitudes: Iterable[float] = None, backend: Optional[Backend] = None, ): @@ -227,7 +229,7 @@ class EFRoughXSXAmplitudeCal(RoughAmplitudeCal): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, amplitudes: Iterable[float] = None, backend: Optional[Backend] = None, ef_pulse_label: str = "12", @@ -281,6 +283,8 @@ def _attach_calibrations(self, circuit: QuantumCircuit): # Attach the x calibration as well if it is in self._cals. We allow for # it not to be present in case a user wants to rely on the default x # calibration and only calibrate the pulses between levels 1 and 2. - if self._cals.has_template("x", self.physical_qubits): + try: schedule = self._cals.get_schedule("x", self.physical_qubits) circuit.add_calibration("x", self.physical_qubits, schedule) + except CalibrationError: + pass diff --git a/qiskit_experiments/library/calibration/rough_drag_cal.py b/qiskit_experiments/library/calibration/rough_drag_cal.py index 53c0efcb63..63edbc2cb7 100644 --- a/qiskit_experiments/library/calibration/rough_drag_cal.py +++ b/qiskit_experiments/library/calibration/rough_drag_cal.py @@ -18,10 +18,8 @@ from qiskit.providers.backend import Backend from qiskit_experiments.framework import ExperimentData -from qiskit_experiments.calibration_management import ( - BaseCalibrationExperiment, - Calibrations, -) +from qiskit_experiments.calibration_management import BaseCalibrationExperiment +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.calibration_management.update_library import BaseUpdater from qiskit_experiments.library.characterization.drag import RoughDrag @@ -37,7 +35,7 @@ class RoughDragCal(BaseCalibrationExperiment, RoughDrag): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, backend: Optional[Backend] = None, schedule_name: str = "x", betas: Iterable[float] = None, diff --git a/qiskit_experiments/library/calibration/rough_frequency.py b/qiskit_experiments/library/calibration/rough_frequency.py index a40f7dcb80..3168737766 100644 --- a/qiskit_experiments/library/calibration/rough_frequency.py +++ b/qiskit_experiments/library/calibration/rough_frequency.py @@ -20,7 +20,7 @@ from qiskit_experiments.library.characterization.qubit_spectroscopy import QubitSpectroscopy from qiskit_experiments.library.characterization.ef_spectroscopy import EFSpectroscopy from qiskit_experiments.calibration_management.update_library import Frequency -from qiskit_experiments.calibration_management.calibrations import Calibrations +from qiskit_experiments.calibration_management.base_calibrations import BaseCalibrations from qiskit_experiments.calibration_management.base_calibration_experiment import ( BaseCalibrationExperiment, ) @@ -33,7 +33,7 @@ class RoughFrequencyCal(BaseCalibrationExperiment, QubitSpectroscopy): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, frequencies: Iterable[float], backend: Optional[Backend] = None, auto_update: bool = True, @@ -86,7 +86,7 @@ class RoughEFFrequencyCal(BaseCalibrationExperiment, EFSpectroscopy): def __init__( self, physical_qubits: Sequence[int], - calibrations: Calibrations, + calibrations: BaseCalibrations, frequencies: Iterable[float], backend: Optional[Backend] = None, auto_update: bool = True, diff --git a/releasenotes/notes/base_calibrations-220741e03e5c7279.yaml b/releasenotes/notes/base_calibrations-220741e03e5c7279.yaml new file mode 100644 index 0000000000..ca869efa76 --- /dev/null +++ b/releasenotes/notes/base_calibrations-220741e03e5c7279.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + A :class:`.BaseCalibrations` class is introduced as an abstract base class + of Qiskit Experiments calibrations. All calibration subclasses must inherit + from the abstract base class and implement all defined methods. + All calibration experiment classes can rely on the methods defined in + :class:`.BaseCalibrations`. However, custom methods implemented in a + particular subclass may not necessarily be implemented in general and should + not be used in calibration experiments. diff --git a/test/calibration/test_base_calibration_experiment.py b/test/calibration/test_base_calibration_experiment.py index 8227e89eee..2e45a3cf0e 100644 --- a/test/calibration/test_base_calibration_experiment.py +++ b/test/calibration/test_base_calibration_experiment.py @@ -18,10 +18,8 @@ from qiskit.circuit import Parameter from qiskit.pulse import Play, Constant, DriveChannel, ScheduleBlock -from qiskit_experiments.calibration_management.base_calibration_experiment import ( - BaseCalibrationExperiment, - Calibrations, -) +from qiskit_experiments.calibration_management import BaseCalibrationExperiment +from qiskit_experiments.calibration_management import Calibrations from qiskit_experiments.framework.composite import ParallelExperiment, BatchExperiment from qiskit_experiments.library import QubitSpectroscopy from qiskit_experiments.test.fake_backend import FakeBackend