diff --git a/docs/tutorials/calibrating_armonk.ipynb b/docs/tutorials/calibrating_armonk.ipynb index af9a71cea6..2858e89e1e 100644 --- a/docs/tutorials/calibrating_armonk.ipynb +++ b/docs/tutorials/calibrating_armonk.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "e3836dba", "metadata": {}, "outputs": [], @@ -29,14 +29,14 @@ "import qiskit.pulse as pulse\n", "from qiskit.circuit import Parameter\n", "\n", - "from qiskit_experiments.calibration_management.backend_calibrations import BackendCalibrations\n", + "from qiskit_experiments.calibration_management.calibrations import Calibrations\n", "\n", "from qiskit import IBMQ, schedule" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "87e1101f", "metadata": {}, "outputs": [], @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "e3f6f6c1", "metadata": {}, "outputs": [], @@ -66,14 +66,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "ca0c1462", "metadata": {}, "outputs": [], "source": [ - "def setup_cals(backend) -> BackendCalibrations:\n", + "def setup_cals(backend) -> Calibrations:\n", " \"\"\"A function to instantiate calibrations and add a couple of template schedules.\"\"\"\n", - " cals = BackendCalibrations(backend)\n", + " cals = Calibrations.from_backend(backend)\n", "\n", " dur = Parameter(\"dur\")\n", " amp = Parameter(\"amp\")\n", @@ -97,7 +97,7 @@ " \n", " return cals\n", "\n", - "def add_parameter_guesses(cals: BackendCalibrations):\n", + "def add_parameter_guesses(cals: Calibrations):\n", " \"\"\"Add guesses for the parameter values to the calibrations.\"\"\"\n", " for sched in [\"xp\", \"x90p\"]:\n", " cals.add_parameter_value(80, \"σ\", schedule=sched)\n", @@ -116,7 +116,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "a2f9d7e5", "metadata": {}, "outputs": [], @@ -135,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "48895a9f", "metadata": {}, "outputs": [], @@ -145,13 +145,13 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "bd83e088", "metadata": {}, "outputs": [], "source": [ "library = FixedFrequencyTransmon(default_values={\"duration\": 320})\n", - "cals = BackendCalibrations(backend, library)" + "cals = Calibrations.from_backend(backend, library)" ] }, { @@ -166,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "908ff764", "metadata": {}, "outputs": [], @@ -184,7 +184,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "fa22b8a4", "metadata": {}, "outputs": [ @@ -222,112 +222,112 @@ " \n", " \n", " 0\n", - " meas_lo_freq\n", - " (0,)\n", - " None\n", - " 6.993371e+09\n", + " duration\n", + " ()\n", + " x\n", + " 3.200000e+02\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882285+0100\n", + " 2021-12-07 14:11:16.088611+0100\n", " None\n", " \n", " \n", " 1\n", - " qubit_lo_freq\n", - " (0,)\n", - " None\n", - " 4.971589e+09\n", + " β\n", + " ()\n", + " x\n", + " 0.000000e+00\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882258+0100\n", + " 2021-12-07 14:11:16.088696+0100\n", " None\n", " \n", " \n", " 2\n", - " σ\n", - " ()\n", - " x\n", - " 8.000000e+01\n", + " meas_freq\n", + " (0,)\n", + " None\n", + " 6.993371e+09\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882474+0100\n", + " 2021-12-07 14:11:16.298667+0100\n", " None\n", " \n", " \n", " 3\n", - " duration\n", + " β\n", " ()\n", " sx\n", - " 3.200000e+02\n", + " 0.000000e+00\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882528+0100\n", + " 2021-12-07 14:11:16.088738+0100\n", " None\n", " \n", " \n", " 4\n", - " β\n", + " duration\n", " ()\n", - " x\n", - " 0.000000e+00\n", + " sx\n", + " 3.200000e+02\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882483+0100\n", + " 2021-12-07 14:11:16.088757+0100\n", " None\n", " \n", " \n", " 5\n", - " amp\n", + " σ\n", " ()\n", " x\n", - " 5.000000e-01\n", + " 8.000000e+01\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882460+0100\n", + " 2021-12-07 14:11:16.088718+0100\n", " None\n", " \n", " \n", " 6\n", - " σ\n", + " amp\n", " ()\n", - " sx\n", - " 8.000000e+01\n", + " x\n", + " 5.000000e-01\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882520+0100\n", + " 2021-12-07 14:11:16.088669+0100\n", " None\n", " \n", " \n", " 7\n", - " amp\n", - " ()\n", - " sx\n", - " 2.500000e-01\n", + " drive_freq\n", + " (0,)\n", + " None\n", + " 4.971650e+09\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882499+0100\n", + " 2021-12-07 14:11:16.298640+0100\n", " None\n", " \n", " \n", " 8\n", - " β\n", + " σ\n", " ()\n", " sx\n", - " 0.000000e+00\n", + " 8.000000e+01\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882507+0100\n", + " 2021-12-07 14:11:16.088794+0100\n", " None\n", " \n", " \n", " 9\n", - " duration\n", + " amp\n", " ()\n", - " x\n", - " 3.200000e+02\n", + " sx\n", + " 2.500000e-01\n", " default\n", " True\n", - " 2021-11-03 16:40:00.882491+0100\n", + " 2021-12-07 14:11:16.088776+0100\n", " None\n", " \n", " \n", @@ -335,32 +335,32 @@ "" ], "text/plain": [ - " parameter qubits schedule value group valid \\\n", - "0 meas_lo_freq (0,) None 6.993371e+09 default True \n", - "1 qubit_lo_freq (0,) None 4.971589e+09 default True \n", - "2 σ () x 8.000000e+01 default True \n", - "3 duration () sx 3.200000e+02 default True \n", - "4 β () x 0.000000e+00 default True \n", - "5 amp () x 5.000000e-01 default True \n", - "6 σ () sx 8.000000e+01 default True \n", - "7 amp () sx 2.500000e-01 default True \n", - "8 β () sx 0.000000e+00 default True \n", - "9 duration () x 3.200000e+02 default True \n", + " parameter qubits schedule value group valid \\\n", + "0 duration () x 3.200000e+02 default True \n", + "1 β () x 0.000000e+00 default True \n", + "2 meas_freq (0,) None 6.993371e+09 default True \n", + "3 β () sx 0.000000e+00 default True \n", + "4 duration () sx 3.200000e+02 default True \n", + "5 σ () x 8.000000e+01 default True \n", + "6 amp () x 5.000000e-01 default True \n", + "7 drive_freq (0,) None 4.971650e+09 default True \n", + "8 σ () sx 8.000000e+01 default True \n", + "9 amp () sx 2.500000e-01 default True \n", "\n", " date_time exp_id \n", - "0 2021-11-03 16:40:00.882285+0100 None \n", - "1 2021-11-03 16:40:00.882258+0100 None \n", - "2 2021-11-03 16:40:00.882474+0100 None \n", - "3 2021-11-03 16:40:00.882528+0100 None \n", - "4 2021-11-03 16:40:00.882483+0100 None \n", - "5 2021-11-03 16:40:00.882460+0100 None \n", - "6 2021-11-03 16:40:00.882520+0100 None \n", - "7 2021-11-03 16:40:00.882499+0100 None \n", - "8 2021-11-03 16:40:00.882507+0100 None \n", - "9 2021-11-03 16:40:00.882491+0100 None " + "0 2021-12-07 14:11:16.088611+0100 None \n", + "1 2021-12-07 14:11:16.088696+0100 None \n", + "2 2021-12-07 14:11:16.298667+0100 None \n", + "3 2021-12-07 14:11:16.088738+0100 None \n", + "4 2021-12-07 14:11:16.088757+0100 None \n", + "5 2021-12-07 14:11:16.088718+0100 None \n", + "6 2021-12-07 14:11:16.088669+0100 None \n", + "7 2021-12-07 14:11:16.298640+0100 None \n", + "8 2021-12-07 14:11:16.088794+0100 None \n", + "9 2021-12-07 14:11:16.088776+0100 None " ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -371,7 +371,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "502aef29", "metadata": {}, "outputs": [], @@ -384,18 +384,18 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "91184061", "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -938,12 +938,12 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 14, "id": "24256b82", "metadata": {}, "outputs": [], "source": [ - "cals = BackendCalibrations(backend, library)\n", + "cals = Calibrations.from_backend(backend, library)\n", "cals.load_parameter_values(file_name=\"Armonkparameter_values.csv\")" ] }, diff --git a/qiskit_experiments/calibration_management/__init__.py b/qiskit_experiments/calibration_management/__init__.py index fceb3b33d6..8513a71a84 100644 --- a/qiskit_experiments/calibration_management/__init__.py +++ b/qiskit_experiments/calibration_management/__init__.py @@ -36,7 +36,6 @@ .. autosummary:: :toctree: ../stubs/ - BackendCalibrations Calibrations Managing Calibration Data @@ -144,6 +143,5 @@ """ from .calibrations import Calibrations -from .backend_calibrations import BackendCalibrations from .base_calibration_experiment import BaseCalibrationExperiment from .basis_gate_library import FixedFrequencyTransmon diff --git a/qiskit_experiments/calibration_management/backend_calibrations.py b/qiskit_experiments/calibration_management/backend_calibrations.py deleted file mode 100644 index e7bd42a474..0000000000 --- a/qiskit_experiments/calibration_management/backend_calibrations.py +++ /dev/null @@ -1,431 +0,0 @@ -# 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. - -"""Store and manage the results of calibration experiments in the context of a backend.""" - -from collections import defaultdict -from datetime import datetime -from enum import Enum -from typing import Dict, List, Optional, Tuple, Union -import copy - -from qiskit.providers.backend import BackendV1 as Backend -from qiskit.circuit import Parameter -from qiskit.pulse import InstructionScheduleMap, ScheduleBlock - -from qiskit_experiments.calibration_management.parameter_value import ParameterValue -from qiskit_experiments.calibration_management.calibrations import ( - Calibrations, - ParameterKey, - ParameterValueType, -) -from qiskit_experiments.exceptions import CalibrationError -from qiskit_experiments.calibration_management.basis_gate_library import BasisGateLibrary - - -class FrequencyElement(Enum): - """An extendable enum for components that have a frequency.""" - - QUBIT = "Qubit" - READOUT = "Readout" - - -class BackendCalibrations(Calibrations): - """ - A Calibrations class to enable a seamless interplay with backend objects. - This class enables users to export their calibrations into a backend object. - Additionally, it creates frequency parameters for qubits and readout resonators. - The parameters are named `qubit_lo_freq` and `meas_lo_freq` to be consistent - with the naming in backend.defaults(). These two parameters are not attached to - any schedule. - """ - - __qubit_freq_parameter__ = "qubit_lo_freq" - __readout_freq_parameter__ = "meas_lo_freq" - - def __init__( - self, - backend: Backend, - library: BasisGateLibrary = None, - num_qubits: Optional[int] = 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. - num_qubits: Number of qubits in case the backend object fails to specify this in its - configuration. - - Raises: - CalibrationError: If the backend configuration does not have num_qubits and num_qubits - is None. - """ - super().__init__(getattr(backend.configuration(), "control_channels", None)) - - # Instruction schedule map variables and support variables. - self._inst_map = InstructionScheduleMap() - self._operated_qubits = defaultdict(list) - self._update_inst_map = False # When True add_parameter_value triggers an inst. map update - - # Use the same naming convention as in backend.defaults() - self.qubit_freq = Parameter(self.__qubit_freq_parameter__) - self.meas_freq = Parameter(self.__readout_freq_parameter__) - self._register_parameter(self.qubit_freq, ()) - self._register_parameter(self.meas_freq, ()) - - num_qubits = getattr(backend.configuration(), "num_qubits", num_qubits) - if num_qubits is None: - raise CalibrationError( - "backend.configuration() does not have 'num_qubits' and None given." - ) - - self._qubits = list(range(num_qubits)) - self._backend = backend - - for qubit, freq in enumerate(backend.defaults().qubit_freq_est): - self.add_parameter_value(freq, self.qubit_freq, qubit) - - 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], num_qubits=library.num_qubits(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) - - self._update_inst_map = True - - # Push the schedules to the instruction schedule map. - self.update_inst_map() - - @property - def default_inst_map(self) -> InstructionScheduleMap: - """Return the default and up to date instruction schedule map.""" - return self._inst_map - - def get_inst_map( - self, - group: str = "default", - cutoff_date: datetime = None, - ) -> InstructionScheduleMap: - """Get a new instance of an Instruction schedule map. - - Args: - group: The calibration group from which to draw the parameters. - If not specified this defaults to the 'default' group. - cutoff_date: Retrieve the most recent parameter up until the cutoff date. Parameters - generated after the cutoff date will be ignored. If the cutoff_date is None then - all parameters are considered. This allows users to discard more recent values that - may be erroneous. - - Returns: - An instruction schedule map with parameters updated up to the desired cutoff date - and from the desired calibration group. - """ - inst_map = InstructionScheduleMap() - - self.update_inst_map(group=group, cutoff_date=cutoff_date, inst_map=inst_map) - - return inst_map - - def _get_frequencies( - self, - element: FrequencyElement, - group: str = "default", - cutoff_date: datetime = None, - ) -> List[float]: - """Internal helper method.""" - - if element == FrequencyElement.READOUT: - param = self.meas_freq.name - elif element == FrequencyElement.QUBIT: - param = self.qubit_freq.name - else: - raise CalibrationError(f"Frequency element {element} is not supported.") - - freqs = [] - for qubit in self._qubits: - schedule = None # A qubit frequency is not attached to a schedule. - if ParameterKey(param, (qubit,), schedule) in self._params: - freq = self.get_parameter_value(param, (qubit,), schedule, True, group, cutoff_date) - else: - if element == FrequencyElement.READOUT: - freq = self._backend.defaults().meas_freq_est[qubit] - elif element == FrequencyElement.QUBIT: - freq = self._backend.defaults().qubit_freq_est[qubit] - else: - raise CalibrationError(f"Frequency element {element} is not supported.") - - freqs.append(freq) - - return freqs - - def get_qubit_frequencies( - self, - group: str = "default", - cutoff_date: datetime = None, - ) -> List[float]: - """ - Get the most recent qubit frequencies. They can be passed to the run-time - options of :class:`BaseExperiment`. If no calibrated frequency value of a - qubit is found then the default value from the backend defaults is used. - Only valid parameter values are returned. - - Args: - group: The calibration group from which to draw the - parameters. If not specified, this defaults to the 'default' group. - cutoff_date: Retrieve the most recent parameter up until the cutoff date. Parameters - generated after the cutoff date will be ignored. If the cutoff_date is None then - all parameters are considered. This allows users to discard more recent values - that may be erroneous. - - Returns: - A List of qubit frequencies for all qubits of the backend. - """ - return self._get_frequencies(FrequencyElement.QUBIT, group, cutoff_date) - - def get_meas_frequencies( - self, - group: str = "default", - cutoff_date: datetime = None, - ) -> List[float]: - """ - Get the most recent measurement frequencies. They can be passed to the run-time - options of :class:`BaseExperiment`. If no calibrated frequency value of a - measurement is found then the default value from the backend defaults is used. - Only valid parameter values are returned. - - Args: - group: The calibration group from which to draw the - parameters. If not specified, this defaults to the 'default' group. - cutoff_date: Retrieve the most recent parameter up until the cutoff date. Parameters - generated after the cutoff date will be ignored. If the cutoff_date is None then - all parameters are considered. This allows users to discard more recent values - that may be erroneous. - - Returns: - A List of measurement frequencies for all qubits of the backend. - """ - return self._get_frequencies(FrequencyElement.READOUT, group, cutoff_date) - - def export_backend(self) -> Backend: - """ - Exports the calibrations to a backend object that can be used. - - Returns: - calibrated backend: A backend with the calibrations in it. - """ - backend = copy.deepcopy(self._backend) - - backend.defaults().qubit_freq_est = self.get_qubit_frequencies() - backend.defaults().meas_freq_est = self.get_meas_frequencies() - backend.default().instruction_schedule_map = self._inst_map - - return backend - - def inst_map_add( - self, - instruction_name: str, - qubits: Tuple[int], - schedule_name: Optional[str] = None, - assign_params: Optional[Dict[Union[str, ParameterKey], ParameterValueType]] = None, - ): - """Update a single instruction in the instruction schedule map. - - This method can be used to update a single instruction for the given qubits but - it can also be used by experiments that define custom gates with parameters - such as the :class:`Rabi` experiment. In a Rabi experiment there is a gate named - "Rabi" that scans a pulse with a custom amplitude. Therefore we would do - - .. code-block:: python - - cals.inst_map_add("Rabi", (0, ), "xp", assign_params={"amp": Parameter("amp")}) - - to temporarily add a pulse for the Rabi gate in the instruction schedule map. This - then allows calling :code:`transpile(circ, inst_map=cals.instruction_schedule_map)`. - - Args: - instruction_name: The name of the instruction to add to the instruction schedule map. - qubits: The qubits to which the instruction will apply. - schedule_name: The name of the schedule. If None is given then we assume that the - schedule and the instruction have the same name. - assign_params: An optional dict of parameter mappings to apply. See for instance - :meth:`get_schedule` of :class:`Calibrations`. - """ - schedule_name = schedule_name or instruction_name - - inst_map_args = None - if assign_params is not None: - inst_map_args = assign_params.keys() - - self._inst_map.add( - instruction=instruction_name, - qubits=qubits, - schedule=self.get_schedule(schedule_name, qubits, assign_params), - arguments=inst_map_args, - ) - - def update_inst_map( - self, - schedules: Optional[set] = None, - qubits: Optional[Tuple[int]] = None, - group: Optional[str] = "default", - cutoff_date: datetime = None, - inst_map: Optional[InstructionScheduleMap] = None, - ): - """Push all schedules from the Calibrations to the inst map. - - This will create instructions with the same name as the schedules. - - Args: - schedules: The name of the schedules to update. If None is given then - all schedules will be pushed to instructions. - qubits: The qubits for which to update the instruction schedule map. - If qubits is None then all possible schedules defined by the coupling - map will be updated. - group: The calibration group from which to draw the parameters. If not specified - this defaults to the 'default' group. - cutoff_date: Retrieve the most recent parameter up until the cutoff date. Parameters - generated after the cutoff date will be ignored. If the cutoff_date is None then - all parameters are considered. This allows users to discard more recent values that - may be erroneous. - inst_map: The instruction schedule map to update. If None is given then the default - instruction schedule map (i.e. self._inst_map) will be updated. - """ - inst_map = inst_map or self._inst_map - - for key in self._schedules: - sched_name = key.schedule - - if schedules is not None and sched_name not in schedules: - continue - - if qubits is not None: - inst_map.add( - instruction=sched_name, - qubits=qubits, - schedule=self.get_schedule( - sched_name, qubits, group=group, cutoff_date=cutoff_date - ), - ) - - else: - for qubits_ in self.operated_qubits[self._schedules_qubits[key]]: - try: - inst_map.add( - instruction=sched_name, - qubits=qubits_, - schedule=self.get_schedule( - sched_name, qubits_, group=group, cutoff_date=cutoff_date - ), - ) - except CalibrationError: - # get_schedule may raise an error if not all parameters have values or - # default values. In this case we ignore and continue updating inst_map. - pass - - def _parameter_inst_map_update(self, param: Parameter): - """Update all instructions in the inst map that contain the given parameter.""" - - schedules = set(key.schedule for key in self._parameter_map_r[param]) - - self.update_inst_map(schedules) - - 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. Additionally, this function - will update any instructions in the instruction schedule map that contain this - parameter. - - 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. - """ - super().add_parameter_value(value, param, qubits, schedule) - - if self._update_inst_map: - if schedule is not None: - schedule = schedule.name if isinstance(schedule, ScheduleBlock) else schedule - param_obj = self.calibration_parameter(param, qubits, schedule) - self._parameter_inst_map_update(param_obj) - - @property - def operated_qubits(self) -> Dict[int, List[int]]: - """Get a dict describing qubit couplings. - - This is an extension of the coupling map and used as a convenience to help populate - the instruction schedule map. - - Returns: - A dict where the key is the number of qubits coupled and the value is a list of - lists where the sublist shows which qubits are coupled. For example, a three qubit - system with a three qubit gate and three two-qubit gates would be represented as - - .. parsed-literal:: - - { - 1: [[0], [1], [2]], - 2: [[0, 1], [1, 2], [2, 1]], - 3: [[0, 1, 2]] - } - """ - - # Use the cached map if there is one. - if len(self._operated_qubits) != 0: - return self._operated_qubits - - # Single qubits - for qubit in self._qubits: - self._operated_qubits[1].append([qubit]) - - # Multi-qubit couplings - if self._backend.configuration().coupling_map is not None: - for coupling in self._backend.configuration().coupling_map: - self._operated_qubits[len(coupling)].append(coupling) - - return self._operated_qubits diff --git a/qiskit_experiments/calibration_management/basis_gate_library.py b/qiskit_experiments/calibration_management/basis_gate_library.py index a5f262a047..3aa7950c70 100644 --- a/qiskit_experiments/calibration_management/basis_gate_library.py +++ b/qiskit_experiments/calibration_management/basis_gate_library.py @@ -18,14 +18,14 @@ from abc import ABC, abstractmethod from collections.abc import Mapping -from typing import Any, Dict, List, Optional, Set, Tuple +from typing import Any, Dict, List, Optional, Set from warnings import warn 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.calibration_management.calibration_key_types import DefaultCalValue from qiskit_experiments.exceptions import CalibrationError @@ -116,7 +116,7 @@ def basis_gates(self) -> List[str]: return list(self._schedules) @abstractmethod - def default_values(self) -> List[Tuple[ParameterValueType, Parameter, Tuple, ScheduleBlock]]: + def default_values(self) -> List[DefaultCalValue]: """Return the default values for the parameters. Returns @@ -261,7 +261,7 @@ def _single_qubit_schedule( return sched - def default_values(self) -> List[Tuple[ParameterValueType, Parameter, Tuple, ScheduleBlock]]: + def default_values(self) -> List[DefaultCalValue]: """Return the default values for the parameters. Returns @@ -286,6 +286,6 @@ def default_values(self) -> List[Tuple[ParameterValueType, Parameter, Tuple, Sch if "y" in name and param.name == "amp": value *= 1.0j - defaults.append((value, param.name, tuple(), name)) + defaults.append(DefaultCalValue(value, param.name, tuple(), name)) return defaults diff --git a/qiskit_experiments/calibration_management/calibration_key_types.py b/qiskit_experiments/calibration_management/calibration_key_types.py index a1bdcccbc5..7d27ed1a9d 100644 --- a/qiskit_experiments/calibration_management/calibration_key_types.py +++ b/qiskit_experiments/calibration_management/calibration_key_types.py @@ -12,7 +12,7 @@ """Types used by the calibration module.""" -from typing import Union +from typing import NamedTuple, Tuple, Union from collections import namedtuple from qiskit.circuit import ParameterExpression @@ -21,3 +21,12 @@ ParameterKey = namedtuple("ParameterKey", ["parameter", "qubits", "schedule"]) ScheduleKey = namedtuple("ScheduleKey", ["schedule", "qubits"]) ParameterValueType = Union[ParameterExpression, float, int, complex] + + +class DefaultCalValue(NamedTuple): + """Defines the structure of a default value.""" + + value: Union[float, int, complex] + parameter: str + qubits: Tuple + schedule_name: str diff --git a/qiskit_experiments/calibration_management/calibrations.py b/qiskit_experiments/calibration_management/calibrations.py index 37ca3971ea..babac00ac9 100644 --- a/qiskit_experiments/calibration_management/calibrations.py +++ b/qiskit_experiments/calibration_management/calibrations.py @@ -32,10 +32,14 @@ RegisterSlot, MemorySlot, Schedule, + InstructionScheduleMap, ) from qiskit.pulse.channels import PulseChannel from qiskit.circuit import Parameter, ParameterExpression +from qiskit.providers.backend import BackendV1 as Backend + from qiskit_experiments.exceptions import CalibrationError +from qiskit_experiments.calibration_management.basis_gate_library import BasisGateLibrary from qiskit_experiments.calibration_management.parameter_value import ParameterValue from qiskit_experiments.calibration_management.calibration_key_types import ( ParameterKey, @@ -52,24 +56,72 @@ class Calibrations: ScheduleBlock are supported. """ + # The name of the parameter under which the qubit frequencies are registered. + __drive_freq_parameter__ = "drive_freq" + + # The name of the parameter under which the readout frequencies are registered. + __readout_freq_parameter__ = "meas_freq" + # The channel indices need to be parameterized following this regex. __channel_pattern__ = r"^ch\d[\.\d]*\${0,1}[\d]*$" - def __init__(self, control_config: Dict[Tuple[int, ...], List[ControlChannel]] = None): + def __init__( + self, + coupling_map: Optional[List[List[int]]] = None, + control_channel_map: Optional[Dict[Tuple[int, ...], List[ControlChannel]]] = None, + library: Optional[Union[BasisGateLibrary, List[BasisGateLibrary]]] = None, + add_parameter_defaults: bool = True, + backend_name: Optional[str] = None, + backend_version: Optional[str] = None, + ): """Initialize the calibrations. + Calibrations 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 = Calibrations( + library=FixedFrequencyTransmon( + basis_gates=["x", "sx"], + default_values={duration: 320} + ) + ) + Args: - control_config: A configuration dictionary of any control channels. The + coupling_map: The device's coupling map. Each sub-list describes connected qubits + For example, the coupling map of a fully pairwise-connected backend with three + qubits is :code:`[[0, 1], [1, 0], [1, 2], [2, 1], [2, 0], [0, 2]]`. + control_channel_map: A configuration dictionary of any control channels. The keys are tuples of qubits and the values are a list of ControlChannels that correspond to the qubits in the keys. + library: A library instance from which to get template schedules to register as well + as default parameter values. + add_parameter_defaults: A boolean to indicate weather the default parameter values of + the given library should be used to populate the calibrations. By default this + value is True but can be set to false when deserializing a calibrations object. + backend_name: The name of the backend that these calibrations are attached to. + backend_version: The version of the backend that these calibrations are attached to. + + Raises: + NotImplementedError: if a list of libraries is given. This will be implemented in + the future. """ + self._backend_name = backend_name + self._backend_version = backend_version + + if isinstance(library, list): + raise NotImplementedError( + "Passing a list of libraries from which to instantiate " + "will be supported in future releases." + ) # Mapping between qubits and their control channels. - self._controls_config = control_config if control_config else {} + self._control_channel_map = control_channel_map if control_channel_map else {} # Store the reverse mapping between control channels and qubits for ease of look-up. self._controls_config_r = {} - for qubits, channels in self._controls_config.items(): + for qubits, channels in self._control_channel_map.items(): for channel in channels: self._controls_config_r[channel] = qubits @@ -93,6 +145,276 @@ def __init__(self, control_config: Dict[Tuple[int, ...], List[ControlChannel]] = self._hash_to_counter_map = {} self._parameter_counter = 0 + self._library = None + if library is not None: + self._library = library + + # Add the basis gates + for gate in library.basis_gates: + self.add_schedule(library[gate], num_qubits=library.num_qubits(gate)) + + # Add the default values + if add_parameter_defaults: + for param_conf in library.default_values(): + self.add_parameter_value(*param_conf, update_inst_map=False) + + # Instruction schedule map variables and support variables. + self._inst_map = InstructionScheduleMap() + + # Use the same naming convention as in backend.defaults() + self.drive_freq = Parameter(self.__drive_freq_parameter__) + self.meas_freq = Parameter(self.__readout_freq_parameter__) + self._register_parameter(self.drive_freq, ()) + self._register_parameter(self.meas_freq, ()) + + # Backends with a single qubit may not have a coupling map. + num_qubits = max(max(coupling_map)) + 1 if coupling_map is not None else 1 + + self._qubits = list(range(num_qubits)) + self._coupling_map = coupling_map + self._operated_qubits = self._get_operated_qubits() + + # Push the schedules to the instruction schedule map. + self.update_inst_map() + + @classmethod + def from_backend( + cls, + backend: Backend, + library: Optional[BasisGateLibrary] = None, + add_parameter_defaults: bool = True, + ) -> "Calibrations": + """Create an instance of Calibrations from a backend. + + 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 instance from which to get template schedules to register as well + as default parameter values. + add_parameter_defaults: A boolean to indicate whether the default parameter values of + the given library should be used to populate the calibrations. By default this + value is ``True``. + + Returns: + An instance of Calibrations instantiated from a backend. + """ + if hasattr(backend, "name") and hasattr(backend.name, "__call__"): + backend_name = backend.name() + else: + backend_name = None + + cals = Calibrations( + getattr(backend.configuration(), "coupling_map", None), + getattr(backend.configuration(), "control_channels", None), + library, + add_parameter_defaults, + backend_name, + getattr(backend, "version", None), + ) + + if add_parameter_defaults: + for qubit, freq in enumerate(getattr(backend.defaults(), "qubit_freq_est", [])): + cals.add_parameter_value(freq, cals.drive_freq, qubit, update_inst_map=False) + + for meas, freq in enumerate(getattr(backend.defaults(), "meas_freq_est", [])): + cals.add_parameter_value(freq, cals.meas_freq, meas, update_inst_map=False) + + # Update the instruction schedule map after adding all parameter values. + cals.update_inst_map() + + return cals + + @property + def library(self) -> Optional[BasisGateLibrary]: + """Return the name of the library, e.g. for experiment metadata.""" + return self._library + + def _get_operated_qubits(self) -> Dict[int, List[int]]: + """Get a dict describing qubit couplings. + + This is an extension of the coupling map and used as a convenience to help populate + the instruction schedule map. + + Returns: + A dict where the key is the number of qubits coupled and the value is a list of + lists where the sublist shows which qubits are coupled. For example, a three qubit + system with a three qubit gate and three two-qubit gates would be represented as + + .. parsed-literal:: + + { + 1: [[0], [1], [2]], + 2: [[0, 1], [1, 2], [2, 1]], + 3: [[0, 1, 2]] + } + """ + operated_qubits = defaultdict(list) + + # Single qubits + for qubit in self._qubits: + operated_qubits[1].append([qubit]) + + # Multi-qubit couplings + if self._coupling_map is not None: + for coupling in self._coupling_map: + operated_qubits[len(coupling)].append(coupling) + + return operated_qubits + + @property + def default_inst_map(self) -> InstructionScheduleMap: + """Return the default and up to date instruction schedule map.""" + return self._inst_map + + def get_inst_map( + self, + group: str = "default", + cutoff_date: datetime = None, + ) -> InstructionScheduleMap: + """Get an Instruction schedule map with the calibrated pulses. + + If the group is 'default' and cutoff date is None then the automatically updated + instruction schedule map is returned. However, if these values are different then + a new instruction schedule map is populated based on the values. + + Args: + group: The calibration group from which to draw the parameters. + If not specified this defaults to the 'default' group. + cutoff_date: Retrieve the most recent parameter up until the cutoff date. Parameters + generated after the cutoff date will be ignored. If the cutoff_date is None then + all parameters are considered. This allows users to discard more recent values that + may be erroneous. + + Returns: + An instruction schedule map with parameters updated up to the desired cutoff date + and from the desired calibration group. + """ + if group == "default" and cutoff_date is None: + return self._inst_map + + inst_map = InstructionScheduleMap() + + self.update_inst_map(group=group, cutoff_date=cutoff_date, inst_map=inst_map) + + return inst_map + + def update_inst_map( + self, + schedules: Optional[Set[str]] = None, + qubits: Optional[Tuple[int, ...]] = None, + group: Optional[str] = "default", + cutoff_date: datetime = None, + inst_map: Optional[InstructionScheduleMap] = None, + ): + """Push all schedules from the Calibrations to the inst map. + + This will create instructions with the same name as the schedules. + + Args: + schedules: The name of the schedules to update. If None is given then + all schedules will be pushed to instructions. + qubits: The qubits for which to update the instruction schedule map. + If qubits is None then all possible schedules defined by the coupling + map will be updated. Note that this argument specifies a particular set of + qubits to update instructions for. For example, if qubits is :code:`(2, 3)` then + only two-qubit instructions that apply to qubits 2 and 3 will be updated. Here, + single-qubit instructions will not be updated. + group: The calibration group from which to draw the parameters. If not specified + this defaults to the 'default' group. + cutoff_date: Retrieve the most recent parameter up until the cutoff date. Parameters + generated after the cutoff date will be ignored. If the cutoff_date is None then + all parameters are considered. This allows users to discard more recent values that + may be erroneous. + inst_map: The instruction schedule map to update. If None is given then the default + instruction schedule map (i.e. self._inst_map) will be updated. + """ + inst_map = inst_map or self._inst_map + + for key in self._schedules: + sched_name = key.schedule + + if schedules is not None and sched_name not in schedules: + continue + + if qubits is not None: + self._robust_inst_map_add(inst_map, sched_name, qubits, group, cutoff_date) + else: + for qubits_ in self._operated_qubits[self._schedules_qubits[key]]: + self._robust_inst_map_add(inst_map, sched_name, qubits_, group, cutoff_date) + + def _robust_inst_map_add( + self, + inst_map: InstructionScheduleMap, + sched_name: str, + qubits: Union[int, Tuple[int, ...]], + group: str, + cutoff: datetime, + ): + """A helper method for update_inst_map. + + get_schedule may raise an error if not all parameters have values or + default values. In this case we ignore and continue updating inst_map. + + Args: + sched_name: The name of the schedule. + qubits: The qubit to which the schedule applies. + group: The calibration group. + cutoff: The cutoff date. + """ + try: + inst_map.add( + instruction=sched_name, + qubits=qubits, + schedule=self.get_schedule(sched_name, qubits, group=group, cutoff_date=cutoff), + ) + except CalibrationError: + # get_schedule may raise an error if not all parameters have values or + # default values. In this case we ignore and continue updating inst_map. + pass + + def inst_map_add( + self, + instruction_name: str, + qubits: Tuple[int], + schedule_name: Optional[str] = None, + assign_params: Optional[Dict[Union[str, ParameterKey], ParameterValueType]] = None, + ): + """Update a single instruction in the instruction schedule map. + + This method can be used to update a single instruction for the given qubits but + it can also be used by experiments that define custom gates with parameters + such as the :class:`Rabi` experiment. In a Rabi experiment there is a gate named + "Rabi" that scans a pulse with a custom amplitude. Therefore we would do + + .. code-block:: python + + cals.inst_map_add("Rabi", (0, ), "xp", assign_params={"amp": Parameter("amp")}) + + to temporarily add a pulse for the Rabi gate in the instruction schedule map. This + then allows calling :code:`transpile(circ, inst_map=cals.default_inst_map)`. + + Args: + instruction_name: The name of the instruction to add to the instruction schedule map. + qubits: The qubits to which the instruction will apply. + schedule_name: The name of the schedule. If None is given then we assume that the + schedule and the instruction have the same name. + assign_params: An optional dict of parameter mappings to apply. See for instance + :meth:`get_schedule` of :class:`Calibrations`. + """ + schedule_name = schedule_name or instruction_name + + inst_map_args = None + if assign_params is not None: + inst_map_args = assign_params.keys() + + self._inst_map.add( + instruction=instruction_name, + qubits=qubits, + schedule=self.get_schedule(schedule_name, qubits, assign_params), + arguments=inst_map_args, + ) + def add_schedule( self, schedule: ScheduleBlock, @@ -392,6 +714,7 @@ def add_parameter_value( param: Union[Parameter, str], qubits: Union[int, Tuple[int, ...]] = None, schedule: Union[ScheduleBlock, str] = None, + update_inst_map: bool = True, ): """Add a parameter value to the stored parameters. @@ -405,6 +728,7 @@ def add_parameter_value( 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. + update_inst_map: Update the instruction schedule map if True (the default). Raises: CalibrationError: If the schedule name is given but no schedule with that name @@ -425,6 +749,11 @@ def add_parameter_value( self._params[ParameterKey(param_name, qubits, sched_name)].append(value) + if update_inst_map and schedule is not None: + param_obj = self.calibration_parameter(param_name, qubits, sched_name) + schedules = set(key.schedule for key in self._parameter_map_r[param_obj]) + self.update_inst_map(schedules) + def _get_channel_index(self, qubits: Tuple[int, ...], chan: PulseChannel) -> int: """Get the index of the parameterized channel. @@ -469,7 +798,7 @@ def _get_channel_index(self, qubits: Tuple[int, ...], chan: PulseChannel) -> int indices = [int(sub_channel) for sub_channel in qubit_channels.split(".")] ch_qubits = tuple(qubits[index] for index in indices) - chs_ = self._controls_config[ch_qubits] + chs_ = self._control_channel_map[ch_qubits] control_index = 0 if len(channel_index_parts) == 2: @@ -887,6 +1216,8 @@ def parameters_table( parameters: List[str] = None, qubit_list: List[Tuple[int, ...]] = None, schedules: List[Union[ScheduleBlock, str]] = None, + most_recent_only: bool = True, + group: Optional[str] = None, ) -> Dict[str, Union[List[Dict], List[str]]]: """A convenience function to help users visualize the values of their parameter. @@ -896,6 +1227,8 @@ def parameters_table( qubit_list: The qubits that should be included in the returned table. If None is given then all channels are returned. schedules: The schedules to which to restrict the output. + most_recent_only: return only the most recent parameter values. + group: If the group is given then only the parameters from this group are returned. Returns: A dictionary with the keys "data" and "columns" that can easily @@ -906,8 +1239,6 @@ def parameters_table( if qubit_list: qubit_list = [self._to_tuple(qubits) for qubits in qubit_list] - data = [] - # Convert inputs to lists of strings if schedules is not None: schedules = {sdl.name if isinstance(sdl, ScheduleBlock) else sdl for sdl in schedules} @@ -924,14 +1255,17 @@ def parameters_table( keys.add(key) - for key in keys: - for value in self._params[key]: - value_dict = dataclasses.asdict(value) - value_dict["qubits"] = key.qubits - value_dict["parameter"] = key.parameter - value_dict["schedule"] = key.schedule - value_dict["date_time"] = value_dict["date_time"].strftime("%Y-%m-%d %H:%M:%S.%f%z") - data.append(value_dict) + data = [] + if most_recent_only: + most_recent = {k: max(self._params[k], key=lambda x: x.date_time) for k in keys} + + for key, value in most_recent.items(): + self._append_to_list(data, value, key, group) + + else: + for key in keys: + for value in self._params[key]: + self._append_to_list(data, value, key, group) columns = [ "parameter", @@ -945,12 +1279,28 @@ def parameters_table( ] return {"data": data, "columns": columns} + @staticmethod + def _append_to_list( + data: List[Dict], value: ParameterValue, key: ParameterKey, group: Optional[str] = None + ): + """Helper function to add a value to the data.""" + if group and value.group != group: + return + + value_dict = dataclasses.asdict(value) + value_dict["qubits"] = key.qubits + value_dict["parameter"] = key.parameter + value_dict["schedule"] = key.schedule + value_dict["date_time"] = value_dict["date_time"].strftime("%Y-%m-%d %H:%M:%S.%f%z") + data.append(value_dict) + def save( self, file_type: str = "csv", folder: str = None, overwrite: bool = False, file_prefix: str = "", + most_recent_only: bool = False, ): """Save the parameterized schedules and parameter value. @@ -974,6 +1324,8 @@ def save( unless overwrite is set to True. file_prefix: A prefix to add to the name of the files such as a date tag or a UUID. + most_recent_only: Save only the most recent value. This is set to False by + default so that when saving to csv all values will be saved. Raises: CalibrationError: if the files exist and overwrite is not set to True. @@ -1021,7 +1373,7 @@ def save( dict_writer.writerows(body) # Write the values of the parameters. - values = self.parameters_table()["data"] + values = self.parameters_table(most_recent_only=most_recent_only)["data"] if len(values) > 0: header_keys = values[0].keys() @@ -1031,12 +1383,7 @@ def save( dict_writer.writerows(values) # Serialize the schedules. For now we just print them. - schedules = [] - header_keys = ["name", "qubits", "schedule"] - for key, sched in self._schedules.items(): - schedules.append( - {"name": key.schedule, "qubits": key.qubits, "schedule": str(sched)} - ) + header_keys, schedules = self.schedule_information() with open(schedule_file, "w", newline="", encoding="utf-8") as output_file: dict_writer = csv.DictWriter(output_file, header_keys) @@ -1048,6 +1395,24 @@ def save( os.chdir(cwd) + def schedule_information(self) -> Tuple[List[str], List[Dict]]: + """Get the information on the schedules stored in the calibrations. + + This function serializes the schedule by simply printing them. + + Returns: + A tuple, the first element is the header row while the second is a dictionary + of the schedules in the calibrations where the key is an element of the header + and the values are the name of the schedule, the qubits to which it applies, + a string of the schedule. + """ + # Serialize the schedules. For now we just print them. + schedules = [] + for key, sched in self._schedules.items(): + schedules.append({"name": key.schedule, "qubits": key.qubits, "schedule": str(sched)}) + + return ["name", "qubits", "schedule"], schedules + def load_parameter_values(self, file_name: str = "parameter_values.csv"): """ Load parameter values from a given file into self._params. @@ -1060,17 +1425,46 @@ def load_parameter_values(self, file_name: str = "parameter_values.csv"): reader = csv.DictReader(fp, delimiter=",", quotechar='"') for row in reader: - param_val = ParameterValue( - row["value"], row["date_time"], row["valid"], row["exp_id"], row["group"] - ) + self._add_parameter_value_from_conf(**row) - if row["schedule"] == "": - schedule_name = None - else: - schedule_name = row["schedule"] + self.update_inst_map() + + def _add_parameter_value_from_conf( + self, + value: Union[str, int, float, complex], + date_time: str, + valid: Union[str, bool], + exp_id: str, + group: str, + schedule: Union[str, None], + parameter: str, + qubits: Union[str, int, Tuple[int, ...]], + ): + """Add a parameter value from a parameter configuration. + + The intended usage is :code:`add_parameter_from_conf(**param_conf)`. Entries such + as ``value`` or ``date_time`` are converted to the proper type. + + Args: + value: The value of the parameter. + date_time: The datetime string. + valid: Whether or not the parameter is valid. + exp_id: The id of the experiment that created the parameter value. + group: The calibration group to which the parameter belongs. + schedule: The schedule to which the parameter belongs. The empty string + "" is converted to None. + parameter: The name of the parameter. + qubits: The qubits on which the parameter acts. + """ + param_val = ParameterValue(value, date_time, valid, exp_id, group) + + if schedule == "": + schedule_name = None + else: + schedule_name = schedule - key = ParameterKey(row["parameter"], self._to_tuple(row["qubits"]), schedule_name) - self.add_parameter_value(param_val, *key) + key = ParameterKey(parameter, self._to_tuple(qubits), schedule_name) + self.add_parameter_value(param_val, *key, update_inst_map=False) @classmethod def load(cls, files: List[str]) -> "Calibrations": diff --git a/qiskit_experiments/calibration_management/update_library.py b/qiskit_experiments/calibration_management/update_library.py index 0311f528fb..26d3e3c37e 100644 --- a/qiskit_experiments/calibration_management/update_library.py +++ b/qiskit_experiments/calibration_management/update_library.py @@ -20,7 +20,6 @@ from qiskit.pulse import ScheduleBlock from qiskit_experiments.framework.experiment_data import ExperimentData -from qiskit_experiments.calibration_management.backend_calibrations import BackendCalibrations from qiskit_experiments.calibration_management.calibrations import Calibrations from qiskit_experiments.calibration_management.parameter_value import ParameterValue from qiskit_experiments.calibration_management.calibration_key_types import ParameterValueType @@ -42,7 +41,7 @@ def __init__(self): Frequency.update(calibrations, spectroscopy_data) - Here, calibrations is an instance of :class:`BackendCalibrations` and spectroscopy_data + Here, calibrations is an instance of :class:`Calibrations` and spectroscopy_data is the result of a :class:`QubitSpectroscopy` experiment. """ raise CalibrationError( @@ -147,7 +146,7 @@ class Frequency(BaseUpdater): @classmethod def update( cls, - calibrations: BackendCalibrations, + calibrations: Calibrations, exp_data: ExperimentData, result_index: Optional[int] = None, parameter: str = None, @@ -172,7 +171,7 @@ def update( """ if parameter is None: - parameter = calibrations.__qubit_freq_parameter__ + parameter = calibrations.__drive_freq_parameter__ super().update( calibrations=calibrations, diff --git a/qiskit_experiments/library/calibration/fine_amplitude.py b/qiskit_experiments/library/calibration/fine_amplitude.py index c143a03614..5f21f5ed62 100644 --- a/qiskit_experiments/library/calibration/fine_amplitude.py +++ b/qiskit_experiments/library/calibration/fine_amplitude.py @@ -20,7 +20,7 @@ from qiskit_experiments.calibration_management import ( BaseCalibrationExperiment, - BackendCalibrations, + Calibrations, ) from qiskit_experiments.library.characterization import FineAmplitude from qiskit_experiments.framework import ExperimentData, Options @@ -44,7 +44,7 @@ class FineAmplitudeCal(BaseCalibrationExperiment, FineAmplitude): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, schedule_name: str, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "amp", @@ -156,7 +156,7 @@ class FineXAmplitudeCal(FineAmplitudeCal): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, schedule_name: str, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "amp", @@ -218,7 +218,7 @@ class FineSXAmplitudeCal(FineAmplitudeCal): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, 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 ca71591350..b70513ef10 100644 --- a/qiskit_experiments/library/calibration/fine_drag_cal.py +++ b/qiskit_experiments/library/calibration/fine_drag_cal.py @@ -23,7 +23,7 @@ from qiskit_experiments.framework import ExperimentData, Options from qiskit_experiments.calibration_management import ( BaseCalibrationExperiment, - BackendCalibrations, + Calibrations, ) from qiskit_experiments.calibration_management.update_library import BaseUpdater from qiskit_experiments.library.characterization.fine_drag import FineDrag @@ -39,7 +39,7 @@ class FineDragCal(BaseCalibrationExperiment, FineDrag): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, schedule_name: str, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "β", @@ -157,7 +157,7 @@ class FineXDragCal(FineDragCal): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "β", auto_update: bool = True, @@ -192,7 +192,7 @@ class FineSXDragCal(FineDragCal): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, backend: Optional[Backend] = None, cal_parameter_name: Optional[str] = "β", auto_update: bool = True, diff --git a/qiskit_experiments/library/calibration/frequency_cal.py b/qiskit_experiments/library/calibration/frequency_cal.py index 1cfc4dfd2b..a0dad6be22 100644 --- a/qiskit_experiments/library/calibration/frequency_cal.py +++ b/qiskit_experiments/library/calibration/frequency_cal.py @@ -18,7 +18,7 @@ from qiskit_experiments.framework import ExperimentData from qiskit_experiments.library.characterization.ramsey_xy import RamseyXY -from qiskit_experiments.calibration_management.backend_calibrations import BackendCalibrations +from qiskit_experiments.calibration_management.calibrations import Calibrations from qiskit_experiments.calibration_management.update_library import BaseUpdater from qiskit_experiments.calibration_management.base_calibration_experiment import ( BaseCalibrationExperiment, @@ -35,7 +35,7 @@ class FrequencyCal(BaseCalibrationExperiment, RamseyXY): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, backend: Optional[Backend] = None, delays: Optional[List] = None, unit: str = "s", @@ -63,7 +63,7 @@ def __init__( delays=delays, unit=unit, osc_freq=osc_freq, - cal_parameter_name="qubit_lo_freq", + cal_parameter_name=calibrations.__drive_freq_parameter__, auto_update=auto_update, ) diff --git a/qiskit_experiments/library/calibration/half_angle_cal.py b/qiskit_experiments/library/calibration/half_angle_cal.py index fb6e43bed8..75747a3b75 100644 --- a/qiskit_experiments/library/calibration/half_angle_cal.py +++ b/qiskit_experiments/library/calibration/half_angle_cal.py @@ -20,7 +20,7 @@ from qiskit_experiments.framework import ExperimentData from qiskit_experiments.calibration_management import ( BaseCalibrationExperiment, - BackendCalibrations, + Calibrations, ) from qiskit_experiments.library.characterization import HalfAngle from qiskit_experiments.calibration_management.update_library import BaseUpdater @@ -36,7 +36,7 @@ class HalfAngleCal(BaseCalibrationExperiment, HalfAngle): def __init__( self, qubit, - calibrations: BackendCalibrations, + calibrations: Calibrations, backend: Optional[Backend] = None, schedule_name: str = "sx", cal_parameter_name: Optional[str] = "amp", diff --git a/qiskit_experiments/library/calibration/rough_amplitude_cal.py b/qiskit_experiments/library/calibration/rough_amplitude_cal.py index 37c4f00b23..6afb71dfde 100644 --- a/qiskit_experiments/library/calibration/rough_amplitude_cal.py +++ b/qiskit_experiments/library/calibration/rough_amplitude_cal.py @@ -21,7 +21,7 @@ from qiskit.providers.backend import Backend from qiskit_experiments.framework import ExperimentData, Options -from qiskit_experiments.calibration_management import BaseCalibrationExperiment, BackendCalibrations +from qiskit_experiments.calibration_management import BaseCalibrationExperiment, Calibrations from qiskit_experiments.library.characterization import Rabi from qiskit_experiments.calibration_management.update_library import BaseUpdater from qiskit_experiments.curve_analysis import ParameterRepr @@ -41,7 +41,7 @@ class RoughAmplitudeCal(BaseCalibrationExperiment, Rabi): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, schedule_name: str = "x", amplitudes: Iterable[float] = None, cal_parameter_name: Optional[str] = "amp", @@ -203,7 +203,7 @@ class RoughXSXAmplitudeCal(RoughAmplitudeCal): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, amplitudes: Iterable[float] = None, backend: Optional[Backend] = None, ): @@ -236,7 +236,7 @@ class EFRoughXSXAmplitudeCal(RoughAmplitudeCal): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, amplitudes: Iterable[float] = None, backend: Optional[Backend] = None, ef_pulse_label: str = "12", diff --git a/qiskit_experiments/library/calibration/rough_drag_cal.py b/qiskit_experiments/library/calibration/rough_drag_cal.py index 1a7852f58e..2249b37f2a 100644 --- a/qiskit_experiments/library/calibration/rough_drag_cal.py +++ b/qiskit_experiments/library/calibration/rough_drag_cal.py @@ -20,7 +20,7 @@ from qiskit_experiments.framework import ExperimentData from qiskit_experiments.calibration_management import ( BaseCalibrationExperiment, - BackendCalibrations, + Calibrations, ) from qiskit_experiments.calibration_management.update_library import BaseUpdater from qiskit_experiments.library.characterization.drag import RoughDrag @@ -36,7 +36,7 @@ class RoughDragCal(BaseCalibrationExperiment, RoughDrag): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, 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 4f58b2df35..639c7b5c9b 100644 --- a/qiskit_experiments/library/calibration/rough_frequency.py +++ b/qiskit_experiments/library/calibration/rough_frequency.py @@ -18,7 +18,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.backend_calibrations import BackendCalibrations +from qiskit_experiments.calibration_management.calibrations import Calibrations from qiskit_experiments.calibration_management.base_calibration_experiment import ( BaseCalibrationExperiment, ) @@ -34,7 +34,7 @@ class RoughFrequencyCal(BaseCalibrationExperiment, QubitSpectroscopy): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, frequencies: Iterable[float], backend: Optional[Backend] = None, unit: str = "Hz", @@ -85,7 +85,7 @@ class RoughEFFrequencyCal(BaseCalibrationExperiment, EFSpectroscopy): def __init__( self, qubit: int, - calibrations: BackendCalibrations, + calibrations: Calibrations, frequencies: Iterable[float], unit: str = "Hz", auto_update: bool = True, diff --git a/test/calibration/experiments/test_drag.py b/test/calibration/experiments/test_drag.py index d3200e1eef..29e6316626 100644 --- a/test/calibration/experiments/test_drag.py +++ b/test/calibration/experiments/test_drag.py @@ -27,7 +27,7 @@ from qiskit_experiments.library import RoughDrag, RoughDragCal from qiskit_experiments.test.mock_iq_backend import DragBackend from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon -from qiskit_experiments.calibration_management import BackendCalibrations +from qiskit_experiments.calibration_management import Calibrations class TestDragEndToEnd(QiskitExperimentsTestCase): @@ -145,7 +145,7 @@ def setUp(self): library = FixedFrequencyTransmon() self.backend = DragBackend(gate_name="Drag(x)") - self.cals = BackendCalibrations(self.backend, library) + self.cals = Calibrations.from_backend(self.backend, library) self.test_tol = 0.05 def test_update(self): diff --git a/test/calibration/experiments/test_fine_amplitude.py b/test/calibration/experiments/test_fine_amplitude.py index d7e8d6af66..b34115a1f1 100644 --- a/test/calibration/experiments/test_fine_amplitude.py +++ b/test/calibration/experiments/test_fine_amplitude.py @@ -30,7 +30,7 @@ FineSXAmplitudeCal, ) from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon -from qiskit_experiments.calibration_management import BackendCalibrations +from qiskit_experiments.calibration_management import Calibrations from qiskit_experiments.test.mock_iq_backend import MockFineAmp @@ -158,7 +158,7 @@ def setUp(self): library = FixedFrequencyTransmon() self.backend = MockFineAmp(-np.pi * 0.07, np.pi, "xp") - self.cals = BackendCalibrations(self.backend, library) + self.cals = Calibrations.from_backend(self.backend, library) def test_cal_options(self): """Test that the options are properly propagated.""" diff --git a/test/calibration/experiments/test_fine_drag.py b/test/calibration/experiments/test_fine_drag.py index 2115a2e43f..1b09b984bf 100644 --- a/test/calibration/experiments/test_fine_drag.py +++ b/test/calibration/experiments/test_fine_drag.py @@ -23,7 +23,7 @@ from qiskit_experiments.library import FineDrag, FineXDrag, FineDragCal from qiskit_experiments.test.mock_iq_backend import DragBackend -from qiskit_experiments.calibration_management import BackendCalibrations +from qiskit_experiments.calibration_management import Calibrations from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon @@ -95,7 +95,7 @@ def setUp(self): library = FixedFrequencyTransmon() self.backend = FineDragTestBackend() - self.cals = BackendCalibrations(self.backend, library) + self.cals = Calibrations.from_backend(self.backend, library) def test_experiment_config(self): """Test converting to and from config works""" diff --git a/test/calibration/experiments/test_ramsey_xy.py b/test/calibration/experiments/test_ramsey_xy.py index 4aa5bf0e02..73e65c7135 100644 --- a/test/calibration/experiments/test_ramsey_xy.py +++ b/test/calibration/experiments/test_ramsey_xy.py @@ -16,7 +16,7 @@ from test.base import QiskitExperimentsTestCase from qiskit.test.mock import FakeArmonk -from qiskit_experiments.calibration_management.backend_calibrations import BackendCalibrations +from qiskit_experiments.calibration_management.calibrations import Calibrations from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon from qiskit_experiments.library import RamseyXY, FrequencyCal from qiskit_experiments.test.mock_iq_backend import MockRamseyXY @@ -30,7 +30,7 @@ def setUp(self): super().setUp() library = FixedFrequencyTransmon() - self.cals = BackendCalibrations(FakeArmonk(), library) + self.cals = Calibrations.from_backend(FakeArmonk(), library) def test_end_to_end(self): """Test that we can run on a mock backend and perform a fit. @@ -52,9 +52,11 @@ def test_update_calibrations(self): tol = 1e4 # 10 kHz resolution + freq_name = self.cals.__drive_freq_parameter__ + # Check qubit frequency before running the cal - f01 = self.cals.get_parameter_value("qubit_lo_freq", 0) - self.assertTrue(len(self.cals.parameters_table(parameters=["qubit_lo_freq"])["data"]), 1) + f01 = self.cals.get_parameter_value(freq_name, 0) + self.assertTrue(len(self.cals.parameters_table(parameters=[freq_name])["data"]), 1) self.assertEqual(f01, FakeArmonk().defaults().qubit_freq_est[0]) freq_shift = 4e6 @@ -63,8 +65,8 @@ def test_update_calibrations(self): FrequencyCal(0, self.cals, backend, osc_freq=osc_shift).run().block_for_results() # Check that qubit frequency after running the cal is shifted by freq_shift, i.e. 4 MHz. - f01 = self.cals.get_parameter_value("qubit_lo_freq", 0) - self.assertTrue(len(self.cals.parameters_table(parameters=["qubit_lo_freq"])["data"]), 2) + f01 = self.cals.get_parameter_value(freq_name, 0) + self.assertTrue(len(self.cals.parameters_table(parameters=[freq_name])["data"]), 2) self.assertTrue(abs(f01 - (freq_shift + FakeArmonk().defaults().qubit_freq_est[0])) < tol) def test_ramseyxy_experiment_config(self): diff --git a/test/calibration/experiments/test_rough_amplitude.py b/test/calibration/experiments/test_rough_amplitude.py index 14fd602355..2316526b72 100644 --- a/test/calibration/experiments/test_rough_amplitude.py +++ b/test/calibration/experiments/test_rough_amplitude.py @@ -21,7 +21,7 @@ from qiskit.test.mock import FakeArmonk from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon -from qiskit_experiments.calibration_management import BackendCalibrations +from qiskit_experiments.calibration_management import Calibrations from qiskit_experiments.library import EFRoughXSXAmplitudeCal, RoughXSXAmplitudeCal from qiskit_experiments.test.mock_iq_backend import RabiBackend @@ -35,7 +35,7 @@ def setUp(self): library = FixedFrequencyTransmon() self.backend = FakeArmonk() - self.cals = BackendCalibrations(self.backend, library) + self.cals = Calibrations.from_backend(self.backend, library) def test_circuits(self): """Test the quantum circuits.""" @@ -85,7 +85,7 @@ def setUp(self): library = FixedFrequencyTransmon() self.backend = FakeArmonk() - self.cals = BackendCalibrations(self.backend, library) + self.cals = Calibrations.from_backend(self.backend, library) # Add some pulses on the 1-2 transition. d0 = pulse.DriveChannel(0) diff --git a/test/calibration/experiments/test_rough_frequency.py b/test/calibration/experiments/test_rough_frequency.py index c040be4309..15b7142d75 100644 --- a/test/calibration/experiments/test_rough_frequency.py +++ b/test/calibration/experiments/test_rough_frequency.py @@ -19,7 +19,7 @@ from qiskit.test.mock import FakeArmonk from qiskit_experiments.library import RoughFrequencyCal -from qiskit_experiments.calibration_management import BackendCalibrations +from qiskit_experiments.calibration_management import Calibrations from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon @@ -30,7 +30,7 @@ def test_init(self): """Test that initialization.""" qubit = 1 - cals = BackendCalibrations(FakeArmonk()) + cals = Calibrations.from_backend(FakeArmonk()) frequencies = [1, 2, 3] unit = "kHz" auto_update = False @@ -46,7 +46,7 @@ def test_init(self): self.assertEqual(freq.auto_update, False) def test_update_calibrations(self): - """Test that we can properly update an instance of BackendCalibrations.""" + """Test that we can properly update an instance of Calibrations.""" freq01 = FakeArmonk().defaults().qubit_freq_est[0] @@ -54,9 +54,9 @@ def test_update_calibrations(self): backend.defaults().qubit_freq_est = [freq01, freq01] library = FixedFrequencyTransmon(basis_gates=["x", "sx"]) - cals = BackendCalibrations(FakeArmonk(), library=library) + cals = Calibrations.from_backend(FakeArmonk(), library=library) - prev_freq = cals.get_parameter_value(cals.__qubit_freq_parameter__, (0,)) + prev_freq = cals.get_parameter_value(cals.__drive_freq_parameter__, (0,)) self.assertEqual(prev_freq, freq01) frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) @@ -64,12 +64,12 @@ def test_update_calibrations(self): RoughFrequencyCal(0, cals, frequencies).run(backend).block_for_results() # Check the updated frequency which should be shifted by 5MHz. - post_freq = cals.get_parameter_value(cals.__qubit_freq_parameter__, (0,)) + post_freq = cals.get_parameter_value(cals.__drive_freq_parameter__, (0,)) self.assertTrue(abs(post_freq - freq01 - 5e6) < 1e6) def test_experiment_config(self): """Test converting to and from config works""" - cals = BackendCalibrations(FakeArmonk()) + cals = Calibrations.from_backend(FakeArmonk()) frequencies = [1, 2, 3] exp = RoughFrequencyCal(0, cals, frequencies) loaded_exp = RoughFrequencyCal.from_config(exp.config()) diff --git a/test/calibration/test_backend_calibrations.py b/test/calibration/test_backend_calibrations.py deleted file mode 100644 index d22adc3088..0000000000 --- a/test/calibration/test_backend_calibrations.py +++ /dev/null @@ -1,234 +0,0 @@ -# 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. - -"""Class to test the backend calibrations.""" - -import unittest -from test.base import QiskitExperimentsTestCase - -from qiskit import transpile, QuantumCircuit -from qiskit.circuit import Parameter, Gate -import qiskit.pulse as pulse -from qiskit.test.mock import FakeArmonk, FakeBelem - -from qiskit_experiments.calibration_management import BackendCalibrations -from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon - - -class TestBackendCalibrations(QiskitExperimentsTestCase): - """Class to test the functionality of a BackendCalibrations""" - - def test_run_options(self): - """Test that we can get run options.""" - cals = BackendCalibrations(FakeArmonk()) - - 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) - - def test_instruction_schedule_map_export(self): - """Test that exporting the inst map works as planned.""" - - backend = FakeBelem() - - cals = BackendCalibrations( - backend, - library=FixedFrequencyTransmon(basis_gates=["sx"]), - ) - - u_chan = pulse.ControlChannel(Parameter("ch0.1")) - with pulse.build(name="cr") as cr: - pulse.play(pulse.GaussianSquare(640, 0.5, 64, 384), u_chan) - - cals.add_schedule(cr, num_qubits=2) - cals.update_inst_map({"cr"}) - - for qubit in range(backend.configuration().num_qubits): - self.assertTrue(cals.default_inst_map.has("sx", (qubit,))) - - # based on coupling map of Belem to keep the test robust. - expected_pairs = [(0, 1), (1, 0), (1, 2), (2, 1), (1, 3), (3, 1), (3, 4), (4, 3)] - coupling_map = set(tuple(pair) for pair in backend.configuration().coupling_map) - - for pair in expected_pairs: - self.assertTrue(pair in coupling_map) - self.assertTrue(cals.default_inst_map.has("cr", pair), pair) - - @unittest.skip("Requires qiskit terra >= 0.19.0") - def test_inst_map_transpilation(self): - """Test that we can use the inst_map to inject the cals into the circuit.""" - - cals = BackendCalibrations( - FakeArmonk(), - library=FixedFrequencyTransmon(basis_gates=["x"]), - ) - - param = Parameter("amp") - cals.inst_map_add("Rabi", (0,), "x", assign_params={"amp": param}) - - circ = QuantumCircuit(1) - circ.x(0) - circ.append(Gate("Rabi", num_qubits=1, params=[param]), (0,)) - - circs, amps = [], [0.12, 0.25] - - for amp in amps: - new_circ = circ.assign_parameters({param: amp}, inplace=False) - circs.append(new_circ) - - # Check that calibrations are absent - for circ in circs: - self.assertEqual(len(circ.calibrations), 0) - - # Transpile to inject the cals. - # TODO Enable this test once terra 0.19.0 is live. - circs = transpile(circs) # TODO add: inst_map=cals.instruction_schedule_map - - # Check that we have the expected schedules. - with pulse.build() as x_expected: - pulse.play(pulse.Drag(160, 0.5, 40, 0), pulse.DriveChannel(0)) - - for idx, circ in enumerate(circs): - amp = amps[idx] - - with pulse.build() as rabi_expected: - pulse.play(pulse.Drag(160, amp, 40, 0), pulse.DriveChannel(0)) - - self.assertEqual(circ.calibrations["x"][((0,), ())], x_expected) - - circ_rabi = next(iter(circ.calibrations["Rabi"].values())) - self.assertEqual(circ_rabi, rabi_expected) - - # Test the removal of the Rabi instruction - self.assertTrue(cals.default_inst_map.has("Rabi", (0,))) - - cals.default_inst_map.remove("Rabi", (0,)) - - self.assertFalse(cals.default_inst_map.has("Rabi", (0,))) - - def test_inst_map_updates(self): - """Test that updating a parameter will force an inst map update.""" - - cals = BackendCalibrations( - FakeBelem(), - library=FixedFrequencyTransmon(basis_gates=["sx", "x"]), - ) - - # Test the schedules before the update. - for qubit in range(5): - for gate, amp in [("x", 0.5), ("sx", 0.25)]: - with pulse.build() as expected: - pulse.play(pulse.Drag(160, amp, 40, 0), pulse.DriveChannel(qubit)) - - self.assertEqual(cals.default_inst_map.get(gate, qubit), expected) - - # Update the duration, this should impact all gates. - cals.add_parameter_value(200, "duration", schedule="sx") - - # Test that all schedules now have an updated duration in the inst_map - for qubit in range(5): - for gate, amp in [("x", 0.5), ("sx", 0.25)]: - with pulse.build() as expected: - pulse.play(pulse.Drag(200, amp, 40, 0), pulse.DriveChannel(qubit)) - - self.assertEqual(cals.default_inst_map.get(gate, qubit), expected) - - # Update the amp on a single qubit, this should only update one gate in the inst_map - cals.add_parameter_value(0.8, "amp", qubits=(4,), schedule="sx") - - # Test that all schedules now have an updated duration in the inst_map - for qubit in range(5): - for gate, amp in [("x", 0.5), ("sx", 0.25)]: - - if gate == "sx" and qubit == 4: - amp = 0.8 - - with pulse.build() as expected: - pulse.play(pulse.Drag(200, amp, 40, 0), pulse.DriveChannel(qubit)) - - self.assertEqual(cals.default_inst_map.get(gate, qubit), expected) - - def test_cx_cz_case(self): - """Test the case where the coupling map has CX and CZ on different qubits. - - We use FakeBelem which has a linear coupling map and will restrict ourselves to - qubits 0, 1, and 2. The Cals will define a template schedule for CX and CZ. We will - mock this with GaussianSquare and Gaussian pulses since the nature of the schedules - is irrelevant here. The parameters for CX will only have values for qubis 0 and 1 while - the parameters for CZ will only have values for qubis 1 and 2. We therefore will have - a CX on qubits 0, 1 in the inst. map and a CZ on qubits 1, 2. - """ - - cals = BackendCalibrations(FakeBelem()) - - sig = Parameter("σ") - dur = Parameter("duration") - width = Parameter("width") - amp_cx = Parameter("amp") - amp_cz = Parameter("amp") - uchan = Parameter("ch1.0") - - with pulse.build(name="cx") as cx: - pulse.play( - pulse.GaussianSquare(duration=dur, amp=amp_cx, sigma=sig, width=width), - pulse.ControlChannel(uchan), - ) - - with pulse.build(name="cz") as cz: - pulse.play( - pulse.Gaussian(duration=dur, amp=amp_cz, sigma=sig), pulse.ControlChannel(uchan) - ) - - cals.add_schedule(cx, num_qubits=2) - cals.add_schedule(cz, num_qubits=2) - - cals.add_parameter_value(640, "duration", schedule="cx") - cals.add_parameter_value(64, "σ", schedule="cx") - cals.add_parameter_value(320, "width", qubits=(0, 1), schedule="cx") - cals.add_parameter_value(320, "width", qubits=(1, 0), schedule="cx") - cals.add_parameter_value(0.1, "amp", qubits=(0, 1), schedule="cx") - cals.add_parameter_value(0.8, "amp", qubits=(1, 0), schedule="cx") - cals.add_parameter_value(0.1, "amp", qubits=(2, 1), schedule="cz") - cals.add_parameter_value(0.8, "amp", qubits=(1, 2), schedule="cz") - - # CX only defined for qubits (0, 1) and (1,0)? - self.assertTrue(cals.default_inst_map.has("cx", (0, 1))) - self.assertTrue(cals.default_inst_map.has("cx", (1, 0))) - self.assertFalse(cals.default_inst_map.has("cx", (2, 1))) - self.assertFalse(cals.default_inst_map.has("cx", (1, 2))) - - # CZ only defined for qubits (2, 1) and (1,2)? - self.assertTrue(cals.default_inst_map.has("cz", (2, 1))) - self.assertTrue(cals.default_inst_map.has("cz", (1, 2))) - self.assertFalse(cals.default_inst_map.has("cz", (0, 1))) - self.assertFalse(cals.default_inst_map.has("cz", (1, 0))) diff --git a/test/calibration/test_calibrations.py b/test/calibration/test_calibrations.py index 0e6405f904..7bcd5e1bec 100644 --- a/test/calibration/test_calibrations.py +++ b/test/calibration/test_calibrations.py @@ -16,7 +16,7 @@ import uuid from collections import defaultdict from datetime import datetime -from qiskit.circuit import Parameter +from qiskit.circuit import Parameter, Gate from qiskit.pulse import ( Drag, DriveChannel, @@ -28,13 +28,13 @@ RegisterSlot, Play, ) +from qiskit import transpile, QuantumCircuit from qiskit.pulse.transforms import inline_subroutines, block_to_schedule import qiskit.pulse as pulse -from qiskit.test.mock import FakeArmonk +from qiskit.test.mock import FakeArmonk, FakeBelem from qiskit_experiments.calibration_management.calibrations import Calibrations, ParameterKey from qiskit_experiments.calibration_management.parameter_value import ParameterValue from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon -from qiskit_experiments.calibration_management import BackendCalibrations from qiskit_experiments.exceptions import CalibrationError @@ -154,12 +154,12 @@ def test_remove_schedule(self): self.cals.add_schedule(sched, num_qubits=1) self.assertEqual(len(self.cals.schedules()), 4) - self.assertEqual(len(self.cals.parameters), 7) + self.assertEqual(len(self.cals.parameters), 9) self.cals.remove_schedule(sched) self.assertEqual(len(self.cals.schedules()), 3) - self.assertEqual(len(self.cals.parameters), 6) + self.assertEqual(len(self.cals.parameters), 8) for param in [self.sigma, self.amp_xp, self.amp_x90p, self.amp_y90p, self.beta]: self.assertTrue(param in self.cals.parameters) @@ -374,7 +374,15 @@ def test_default_schedules(self): self.assertEqual(xp3.instructions[0][1].pulse.sigma, 50) # Check that we have the expected parameters in the calibrations. - expected = {self.amp_xp, self.amp, self.sigma, self.beta, self.duration} + expected = { + self.amp_xp, + self.amp, + self.sigma, + self.beta, + self.duration, + self.cals.drive_freq, + self.cals.meas_freq, + } self.assertEqual(len(set(self.cals.parameters.keys())), len(expected)) def test_replace_schedule(self): @@ -766,7 +774,7 @@ def setUp(self): (3, 2): [ControlChannel(10), ControlChannel(123)], (2, 3): [ControlChannel(15), ControlChannel(23)], } - self.cals = Calibrations(control_config=controls) + self.cals = Calibrations(control_channel_map=controls) self.amp_cr = Parameter("amp") self.amp_rot = Parameter("amp_rot") @@ -896,7 +904,7 @@ def setUp(self): controls = {(3, 2): [ControlChannel(10)]} - self.cals = Calibrations(control_config=controls) + self.cals = Calibrations(control_channel_map=controls) self.amp_xp = Parameter("amp") self.ch0 = Parameter("ch0") @@ -1102,7 +1110,7 @@ def setUp(self): controls = {(3, 2): [ControlChannel(10)]} - self.cals = Calibrations(control_config=controls) + self.cals = Calibrations(control_channel_map=controls) self.amp_cr = Parameter("amp") self.amp_xp = Parameter("amp") @@ -1276,6 +1284,16 @@ def setUp(self): ParameterValue(0.4, self.date_time2, group="super_cal"), "amp", (0,), "xp" ) + def test_parameter_table_most_recent(self): + """Test the most_recent argument to the parameter_table method.""" + + table = self.cals.parameters_table(parameters=["amp"], most_recent_only=False) + self.assertTrue(len(table["data"]), 2) + + table = self.cals.parameters_table(parameters=["amp"], most_recent_only=True) + self.assertTrue(len(table["data"]), 1) + self.assertTrue(table["data"][0]["value"], 0.2) + def test_get_parameter_value(self): """Test that getting parameter values funcions properly.""" @@ -1364,7 +1382,7 @@ def test_save_load_library(self): library = FixedFrequencyTransmon() backend = FakeArmonk() - cals = BackendCalibrations(backend, library) + cals = Calibrations.from_backend(backend, library) cals.parameters_table() @@ -1375,6 +1393,222 @@ def test_save_load_library(self): # Test the value of a few loaded params. self.assertEqual(cals.get_parameter_value("amp", (0,), "x"), 0.5) self.assertEqual( - cals.get_parameter_value("qubit_lo_freq", (0,)), + cals.get_parameter_value("drive_freq", (0,)), backend.defaults().qubit_freq_est[0], ) + + +class TestInstructionScheduleMap(QiskitExperimentsTestCase): + """Class to test the functionality of a Calibrations""" + + def test_setup_withLibrary(self): + """Test that we can setup with a library.""" + + cals = Calibrations.from_backend( + 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) + + def test_instruction_schedule_map_export(self): + """Test that exporting the inst map works as planned.""" + + backend = FakeBelem() + + cals = Calibrations.from_backend( + backend, + library=FixedFrequencyTransmon(basis_gates=["sx"]), + ) + + u_chan = pulse.ControlChannel(Parameter("ch0.1")) + with pulse.build(name="cr") as cr: + pulse.play(pulse.GaussianSquare(640, 0.5, 64, 384), u_chan) + + cals.add_schedule(cr, num_qubits=2) + cals.update_inst_map({"cr"}) + + for qubit in range(backend.configuration().num_qubits): + self.assertTrue(cals.default_inst_map.has("sx", (qubit,))) + + # based on coupling map of Belem to keep the test robust. + expected_pairs = [(0, 1), (1, 0), (1, 2), (2, 1), (1, 3), (3, 1), (3, 4), (4, 3)] + coupling_map = set(tuple(pair) for pair in backend.configuration().coupling_map) + + for pair in expected_pairs: + self.assertTrue(pair in coupling_map) + self.assertTrue(cals.default_inst_map.has("cr", pair), pair) + + def test_inst_map_transpilation(self): + """Test that we can use the inst_map to inject the cals into the circuit.""" + + cals = Calibrations.from_backend( + FakeArmonk(), + library=FixedFrequencyTransmon(basis_gates=["x"]), + ) + + param = Parameter("amp") + cals.inst_map_add("Rabi", (0,), "x", assign_params={"amp": param}) + + circ = QuantumCircuit(1) + circ.x(0) + circ.append(Gate("Rabi", num_qubits=1, params=[param]), (0,)) + + circs, amps = [], [0.12, 0.25] + + for amp in amps: + new_circ = circ.assign_parameters({param: amp}, inplace=False) + circs.append(new_circ) + + # Check that calibrations are absent + for circ in circs: + self.assertEqual(len(circ.calibrations), 0) + + # Transpile to inject the cals. + circs = transpile(circs, inst_map=cals.default_inst_map) + + # Check that we have the expected schedules. + with pulse.build() as x_expected: + pulse.play(pulse.Drag(160, 0.5, 40, 0), pulse.DriveChannel(0)) + + for idx, circ in enumerate(circs): + amp = amps[idx] + + with pulse.build() as rabi_expected: + pulse.play(pulse.Drag(160, amp, 40, 0), pulse.DriveChannel(0)) + + self.assertEqual(circ.calibrations["x"][((0,), ())], x_expected) + + circ_rabi = next(iter(circ.calibrations["Rabi"].values())) + self.assertEqual(circ_rabi, rabi_expected) + + # Test the removal of the Rabi instruction + self.assertTrue(cals.default_inst_map.has("Rabi", (0,))) + + cals.default_inst_map.remove("Rabi", (0,)) + + self.assertFalse(cals.default_inst_map.has("Rabi", (0,))) + + def test_inst_map_updates(self): + """Test that updating a parameter will force an inst map update.""" + + cals = Calibrations.from_backend( + FakeBelem(), + library=FixedFrequencyTransmon(basis_gates=["sx", "x"]), + ) + + # Test the schedules before the update. + for qubit in range(5): + for gate, amp in [("x", 0.5), ("sx", 0.25)]: + with pulse.build() as expected: + pulse.play(pulse.Drag(160, amp, 40, 0), pulse.DriveChannel(qubit)) + + self.assertEqual(cals.default_inst_map.get(gate, qubit), expected) + + # Update the duration, this should impact all gates. + cals.add_parameter_value(200, "duration", schedule="sx") + + # Test that all schedules now have an updated duration in the inst_map + for qubit in range(5): + for gate, amp in [("x", 0.5), ("sx", 0.25)]: + with pulse.build() as expected: + pulse.play(pulse.Drag(200, amp, 40, 0), pulse.DriveChannel(qubit)) + + self.assertEqual(cals.default_inst_map.get(gate, qubit), expected) + + # Update the amp on a single qubit, this should only update one gate in the inst_map + cals.add_parameter_value(0.8, "amp", qubits=(4,), schedule="sx") + + # Test that all schedules now have an updated duration in the inst_map + for qubit in range(5): + for gate, amp in [("x", 0.5), ("sx", 0.25)]: + + if gate == "sx" and qubit == 4: + amp = 0.8 + + with pulse.build() as expected: + pulse.play(pulse.Drag(200, amp, 40, 0), pulse.DriveChannel(qubit)) + + self.assertEqual(cals.default_inst_map.get(gate, qubit), expected) + + def test_cx_cz_case(self): + """Test the case where the coupling map has CX and CZ on different qubits. + + We use FakeBelem which has a linear coupling map and will restrict ourselves to + qubits 0, 1, and 2. The Cals will define a template schedule for CX and CZ. We will + mock this with GaussianSquare and Gaussian pulses since the nature of the schedules + is irrelevant here. The parameters for CX will only have values for qubis 0 and 1 while + the parameters for CZ will only have values for qubis 1 and 2. We therefore will have + a CX on qubits 0, 1 in the inst. map and a CZ on qubits 1, 2. + """ + + cals = Calibrations.from_backend(FakeBelem()) + + sig = Parameter("σ") + dur = Parameter("duration") + width = Parameter("width") + amp_cx = Parameter("amp") + amp_cz = Parameter("amp") + uchan = Parameter("ch1.0") + + with pulse.build(name="cx") as cx: + pulse.play( + pulse.GaussianSquare(duration=dur, amp=amp_cx, sigma=sig, width=width), + pulse.ControlChannel(uchan), + ) + + with pulse.build(name="cz") as cz: + pulse.play( + pulse.Gaussian(duration=dur, amp=amp_cz, sigma=sig), pulse.ControlChannel(uchan) + ) + + cals.add_schedule(cx, num_qubits=2) + cals.add_schedule(cz, num_qubits=2) + + cals.add_parameter_value(640, "duration", schedule="cx") + cals.add_parameter_value(64, "σ", schedule="cx") + cals.add_parameter_value(320, "width", qubits=(0, 1), schedule="cx") + cals.add_parameter_value(320, "width", qubits=(1, 0), schedule="cx") + cals.add_parameter_value(0.1, "amp", qubits=(0, 1), schedule="cx") + cals.add_parameter_value(0.8, "amp", qubits=(1, 0), schedule="cx") + cals.add_parameter_value(0.1, "amp", qubits=(2, 1), schedule="cz") + cals.add_parameter_value(0.8, "amp", qubits=(1, 2), schedule="cz") + + # CX only defined for qubits (0, 1) and (1,0)? + self.assertTrue(cals.default_inst_map.has("cx", (0, 1))) + self.assertTrue(cals.default_inst_map.has("cx", (1, 0))) + self.assertFalse(cals.default_inst_map.has("cx", (2, 1))) + self.assertFalse(cals.default_inst_map.has("cx", (1, 2))) + + # CZ only defined for qubits (2, 1) and (1,2)? + self.assertTrue(cals.default_inst_map.has("cz", (2, 1))) + self.assertTrue(cals.default_inst_map.has("cz", (1, 2))) + self.assertFalse(cals.default_inst_map.has("cz", (0, 1))) + self.assertFalse(cals.default_inst_map.has("cz", (1, 0))) + + def test_alternate_initialization(self): + """Test that we can initialize without a backend object.""" + + backend = FakeBelem() + library = FixedFrequencyTransmon(basis_gates=["sx", "x"]) + + cals1 = Calibrations.from_backend(backend, library) + cals2 = Calibrations( + library=library, + control_channel_map=backend.configuration().control_channels, + coupling_map=backend.configuration().coupling_map, + ) + + self.assertEqual(str(cals1.get_schedule("x", 1)), str(cals2.get_schedule("x", 1))) diff --git a/test/calibration/test_setup_library.py b/test/calibration/test_setup_library.py index 1747d20b26..762402b331 100644 --- a/test/calibration/test_setup_library.py +++ b/test/calibration/test_setup_library.py @@ -19,6 +19,7 @@ import qiskit.pulse as pulse from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon +from qiskit_experiments.calibration_management.calibration_key_types import DefaultCalValue from qiskit_experiments.exceptions import CalibrationError from qiskit_experiments.framework.json import ExperimentEncoder, ExperimentDecoder @@ -67,14 +68,14 @@ def test_standard_single_qubit_gates(self): self.assertEqual(len(sched_sx.parameters & sched_sy.parameters), 4) expected = [ - (0.5, "amp", (), "x"), - (0.0, "β", (), "x"), - (320, "duration", (), "x"), - (80, "σ", (), "x"), - (320, "duration", (), "sx"), - (0.0, "β", (), "sx"), - (0.25, "amp", (), "sx"), - (80, "σ", (), "sx"), + DefaultCalValue(0.5, "amp", (), "x"), + DefaultCalValue(0.0, "β", (), "x"), + DefaultCalValue(320, "duration", (), "x"), + DefaultCalValue(80, "σ", (), "x"), + DefaultCalValue(320, "duration", (), "sx"), + DefaultCalValue(0.0, "β", (), "sx"), + DefaultCalValue(0.25, "amp", (), "sx"), + DefaultCalValue(80, "σ", (), "sx"), ] for param_conf in library.default_values(): @@ -102,22 +103,22 @@ def test_unlinked_parameters(self): self.assertEqual(len(sched_sx.parameters & sched_sy.parameters), 2) expected = [ - (0.5, "amp", (), "x"), - (0.0, "β", (), "x"), - (160, "duration", (), "x"), - (40, "σ", (), "x"), - (160, "duration", (), "sx"), - (0.0, "β", (), "sx"), - (0.25, "amp", (), "sx"), - (40, "σ", (), "sx"), - (0.5j, "amp", (), "y"), - (0.0, "β", (), "y"), - (160, "duration", (), "y"), - (40, "σ", (), "y"), - (160, "duration", (), "sy"), - (0.0, "β", (), "sy"), - (0.25j, "amp", (), "sy"), - (40, "σ", (), "sy"), + DefaultCalValue(0.5, "amp", (), "x"), + DefaultCalValue(0.0, "β", (), "x"), + DefaultCalValue(160, "duration", (), "x"), + DefaultCalValue(40, "σ", (), "x"), + DefaultCalValue(160, "duration", (), "sx"), + DefaultCalValue(0.0, "β", (), "sx"), + DefaultCalValue(0.25, "amp", (), "sx"), + DefaultCalValue(40, "σ", (), "sx"), + DefaultCalValue(0.5j, "amp", (), "y"), + DefaultCalValue(0.0, "β", (), "y"), + DefaultCalValue(160, "duration", (), "y"), + DefaultCalValue(40, "σ", (), "y"), + DefaultCalValue(160, "duration", (), "sy"), + DefaultCalValue(0.0, "β", (), "sy"), + DefaultCalValue(0.25j, "amp", (), "sy"), + DefaultCalValue(40, "σ", (), "sy"), ] self.assertSetEqual(set(library.default_values()), set(expected)) diff --git a/test/calibration/test_update_library.py b/test/calibration/test_update_library.py index 253e0b0037..94cb552949 100644 --- a/test/calibration/test_update_library.py +++ b/test/calibration/test_update_library.py @@ -23,7 +23,6 @@ from qiskit_experiments.library import QubitSpectroscopy from qiskit_experiments.calibration_management.calibrations import Calibrations from qiskit_experiments.calibration_management.update_library import Frequency -from qiskit_experiments.calibration_management.backend_calibrations import BackendCalibrations class TestAmplitudeUpdate(QiskitExperimentsTestCase): @@ -73,8 +72,8 @@ def test_frequency(self): self.assertTrue(freq01 + peak_offset - 2e6 < value < freq01 + peak_offset + 2e6) self.assertEqual(result.quality, "good") - # Test the integration with the BackendCalibrations - cals = BackendCalibrations(FakeAthens()) - self.assertNotEqual(cals.get_qubit_frequencies()[qubit], value) + # Test the integration with the Calibrations + cals = Calibrations.from_backend(FakeAthens()) + self.assertNotEqual(cals.get_parameter_value(cals.__drive_freq_parameter__, qubit), value) Frequency.update(cals, exp_data) - self.assertEqual(cals.get_qubit_frequencies()[qubit], value) + self.assertEqual(cals.get_parameter_value(cals.__drive_freq_parameter__, qubit), value)