From a7a3112f59b6340473d8ff3c442a5e7532d42a3f Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Wed, 31 Jan 2024 12:09:27 -0500 Subject: [PATCH] Add support for saving/loading calibration parameters without schedules (#1357) 5b6fa0622d4a3036118130a89771692c8e32e444 added support for saving and loading `Calibrations` objects with a JSON format that preserved calibrated gate schedules. However, it did not capture and restore the `Parameter` objects for calibration parameters (like `drive_freq`) which were not associated with a schedule. This commit adds support for these parameters by adding an entry to the serialization model that holds a placeholder `QuantumCircuit` with the parameters attached to placeholder instructions. `ExperimentEncoder` can serialize `QuantumCircuit` to qpy and so supports this format. Using a placeholder circuit like this is the only supported way to serialize `Parameter` objects. An alternative approach would be to store and retrieve the hidden attributes of the `Parameter` objects directly. Closes #1355 --- .../calibration_management/save_utils.py | 32 +++++++++++++++++++ test/calibration/test_calibrations.py | 4 --- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/calibration_management/save_utils.py b/qiskit_experiments/calibration_management/save_utils.py index 716c50119c..7be2054f3b 100644 --- a/qiskit_experiments/calibration_management/save_utils.py +++ b/qiskit_experiments/calibration_management/save_utils.py @@ -43,6 +43,8 @@ from datetime import datetime from typing import List, Dict, Any +from qiskit import QuantumCircuit +from qiskit.circuit import Instruction from qiskit.pulse import ScheduleBlock from .calibrations import Calibrations @@ -118,6 +120,14 @@ class CalibrationModelV1: parameters: List[ParameterModelV1] = field(default_factory=list) """List of calibrated pulse parameters.""" + schedule_free_parameters: QuantumCircuit = field(default_factory=lambda: QuantumCircuit(1)) + """Placeholder circuit for parameters not associated with a schedule + + The circuit contains placeholder instructions which have the Parameter + objects attached and operate on the qubits that the parameter is associated + with in the calibrations. + """ + schema_version: str = "1.0" """Version of this data model. This must be static.""" @@ -177,6 +187,18 @@ def calibrations_to_dict( sched_obj.metadata.update(qubit_metadata) sched_entries.append(sched_obj) + max_qubit = max( + (max(k.qubits or (0,)) for k in cals._parameter_map if k.schedule is None), + default=0, + ) + schedule_free_parameters = QuantumCircuit(max_qubit + 1) + for sched_key, param in cals._parameter_map.items(): + if sched_key.schedule is None: + schedule_free_parameters.append( + Instruction("parameter_container", len(sched_key.qubits), 0, [param]), + sched_key.qubits, + ) + model = CalibrationModelV1( backend_name=cals.backend_name, backend_version=cals.backend_version, @@ -184,6 +206,7 @@ def calibrations_to_dict( control_channel_map=ControlChannelMap(getattr(cals, "_control_channel_map")), schedules=sched_entries, parameters=data_entries, + schedule_free_parameters=schedule_free_parameters, ) return asdict(model) @@ -257,6 +280,15 @@ def calibrations_from_dict( schedule=param.schedule, update_inst_map=False, ) + + for instruction in model.schedule_free_parameters.data: + # For some reason, pylint thinks the items in data are tuples instead + # of CircuitInstruction. Remove the following line if it ever stops + # thinking that: + # pylint: disable=no-member + for param in instruction.operation.params: + cals._register_parameter(param, instruction.qubits) + cals.update_inst_map() return cals diff --git a/test/calibration/test_calibrations.py b/test/calibration/test_calibrations.py index 9cbdab4c46..3b99cd7668 100644 --- a/test/calibration/test_calibrations.py +++ b/test/calibration/test_calibrations.py @@ -14,7 +14,6 @@ from test.base import QiskitExperimentsTestCase import os -import unittest import uuid from collections import defaultdict from datetime import datetime, timezone, timedelta @@ -1692,9 +1691,6 @@ def test_save_load_library_csv(self): BackendData(backend).drive_freqs[0], ) - # Expected to fail because json calibration loading does not support - # restoring Parameter objects - @unittest.expectedFailure def test_save_load_library(self): """Test that we can load and save a library.