diff --git a/qiskit_experiments/calibration_management/basis_gate_library.py b/qiskit_experiments/calibration_management/basis_gate_library.py index bf48dd7f47..c5fd5a6781 100644 --- a/qiskit_experiments/calibration_management/basis_gate_library.py +++ b/qiskit_experiments/calibration_management/basis_gate_library.py @@ -36,6 +36,9 @@ class BasisGateLibrary(ABC, Mapping): # Location where default parameter values are stored. These may be updated at construction. __default_values__ = {} + # Parameters that do not belong to a schedule, a set of names + __parameters_without_schedule__ = set() + def __init__( self, basis_gates: Optional[List[str]] = None, @@ -204,12 +207,18 @@ class FixedFrequencyTransmon(BasisGateLibrary): The amplitude of the ``sx`` and ``sy`` pulses is half the provided value. - angle: The phase of the complex amplitude of the pulses. + Parameters without schedule: + - meas_freq: frequency of the measurement drives. + - drive_freq: frequency of the qubit drives. + Note that the β and amp parameters may be linked between the x and y as well as between the sx and sy pulses. All pulses share the same duration and σ parameters. """ __default_values__ = {"duration": 160, "amp": 0.5, "β": 0.0, "angle": 0.0} + __parameters_without_schedule__ = {"meas_freq", "drive_freq"} + def __init__( self, basis_gates: Optional[List[str]] = None, diff --git a/qiskit_experiments/calibration_management/calibrations.py b/qiskit_experiments/calibration_management/calibrations.py index 7ace1fba3b..0ee98c707b 100644 --- a/qiskit_experiments/calibration_management/calibrations.py +++ b/qiskit_experiments/calibration_management/calibrations.py @@ -63,12 +63,6 @@ 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" - def __init__( self, coupling_map: Optional[List[List[int]]] = None, @@ -153,7 +147,6 @@ def __init__( libraries = [libraries] for lib in libraries: - # Add the basis gates for gate in lib.basis_gates: self.add_schedule(lib[gate], num_qubits=lib.num_qubits(gate)) @@ -163,6 +156,10 @@ def __init__( for param_conf in lib.default_values(): self.add_parameter_value(*param_conf, update_inst_map=False) + # Add the parameters that do not belong to a schedule. + for param_name in lib.__parameters_without_schedule__: + self._register_parameter(Parameter(param_name), tuple()) + # This internal parameter is False so that if a schedule is added after the # init it will be set to True and serialization will raise an error. self._has_manually_added_schedule = False @@ -170,12 +167,6 @@ def __init__( # 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. self._coupling_map = coupling_map if coupling_map is not None else [] @@ -187,6 +178,28 @@ def __init__( # Push the schedules to the instruction schedule map. self.update_inst_map() + @property + @deprecate_func( + is_property=True, + since="0.6", + package_name="qiskit-experiments", + additional_msg="The drive_freq is moved to FixedFrequencyTransmon basis gate library.", + ) + def drive_freq(self): + """Parameter object for qubit drive frequency.""" + return self._parameter_map.get(("drive_freq", (), None), None) + + @property + @deprecate_func( + is_property=True, + since="0.6", + package_name="qiskit-experiments", + additional_msg="The meas_freq is moved to FixedFrequencyTransmon basis gate library.", + ) + def meas_freq(self): + """Parameter object for qubit measure frequency.""" + return self._parameter_map.get(("meas_freq", (), None), None) + def _check_consistency(self): """Check that the attributes defined in self are consistent. @@ -237,7 +250,7 @@ def from_backend( libraries: A list of libraries 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 + the given library should be used to populate the calibrations. By default, this value is ``True``. Returns: @@ -260,11 +273,13 @@ def from_backend( ) if add_parameter_defaults: - for qubit, freq in enumerate(backend_data.drive_freqs): - cals.add_parameter_value(freq, cals.drive_freq, qubit, update_inst_map=False) + if ("drive_freq", (), None) in cals._parameter_map: + for qubit, freq in enumerate(backend_data.drive_freqs): + cals.add_parameter_value(freq, "drive_freq", qubit, update_inst_map=False) - for meas, freq in enumerate(backend_data.meas_freqs): - cals.add_parameter_value(freq, cals.meas_freq, meas, update_inst_map=False) + if ("meas_freq", (), None) in cals._parameter_map: + for meas, freq in enumerate(backend_data.meas_freqs): + cals.add_parameter_value(freq, "meas_freq", meas, update_inst_map=False) # Update the instruction schedule map after adding all parameter values. cals.update_inst_map() @@ -449,7 +464,6 @@ def _get_full_qubits_of_schedule( """ for key, circuit_inst_num_qubits in self._schedules_qubits.items(): if key.schedule == schedule_name: - if len(partial_qubits) == circuit_inst_num_qubits: return [partial_qubits] @@ -888,7 +902,6 @@ def _get_channel_index(self, qubits: Tuple[int, ...], chan: PulseChannel) -> int # Control channels name example ch1.0$1 if isinstance(chan, ControlChannel): - channel_index_parts = chan.index.name[2:].split("$") qubit_channels = channel_index_parts[0] diff --git a/qiskit_experiments/calibration_management/update_library.py b/qiskit_experiments/calibration_management/update_library.py index eac5e32d04..ff6009432c 100644 --- a/qiskit_experiments/calibration_management/update_library.py +++ b/qiskit_experiments/calibration_management/update_library.py @@ -149,7 +149,7 @@ def update( calibrations: Calibrations, exp_data: ExperimentData, result_index: Optional[int] = None, - parameter: str = None, + parameter: str = "drive_freq", group: str = "default", fit_parameter: Optional[str] = None, **options, @@ -163,16 +163,13 @@ def update( exp_data: The experiment data from which to update. result_index: The result index to use which defaults to -1. parameter: The name of the parameter to update. If None is given this will default - to :code:`calibrations.__qubit_freq_parameter__`. + to `drive_freq`. group: The calibrations group to update. Defaults to "default." options: Trailing options. fit_parameter: The name of the fit parameter in the analysis result. This will default to the class variable :code:`__fit_parameter__` if not given. """ - if parameter is None: - parameter = calibrations.__drive_freq_parameter__ - super().update( calibrations=calibrations, exp_data=exp_data, diff --git a/qiskit_experiments/library/calibration/fine_frequency_cal.py b/qiskit_experiments/library/calibration/fine_frequency_cal.py index 049712b7f9..66c9479918 100644 --- a/qiskit_experiments/library/calibration/fine_frequency_cal.py +++ b/qiskit_experiments/library/calibration/fine_frequency_cal.py @@ -37,6 +37,7 @@ def __init__( physical_qubits: Sequence[int], calibrations: Calibrations, backend: Optional[Backend] = None, + cal_parameter_name: Optional[str] = "drive_freq", delay_duration: Optional[int] = None, repetitions: List[int] = None, auto_update: bool = True, @@ -54,11 +55,13 @@ def __init__( fine frequency calibration. calibrations: The calibrations instance with the schedules. backend: Optional, the backend to run the experiment on. + cal_parameter_name: The name of the parameter to update in the calibrations. + This defaults to `drive_freq`. delay_duration: The duration of the delay at :math:`n=1`. If this value is not given then the duration of the gate named ``gate_name`` in the calibrations will be used. - auto_update: Whether or not to automatically update the calibrations. By - default this variable is set to True. + auto_update: Whether to automatically update the calibrations or not. By + default, this variable is set to True. gate_name: This argument is only needed if ``delay_duration`` is None. This should be the name of a valid schedule in the calibrations. """ @@ -72,7 +75,7 @@ def __init__( schedule_name=None, repetitions=repetitions, backend=backend, - cal_parameter_name=calibrations.__drive_freq_parameter__, + cal_parameter_name=cal_parameter_name, auto_update=auto_update, ) diff --git a/qiskit_experiments/library/calibration/frequency_cal.py b/qiskit_experiments/library/calibration/frequency_cal.py index 82e52c4eb2..79c3d7bc47 100644 --- a/qiskit_experiments/library/calibration/frequency_cal.py +++ b/qiskit_experiments/library/calibration/frequency_cal.py @@ -36,6 +36,7 @@ def __init__( physical_qubits: Sequence[int], calibrations: Calibrations, backend: Optional[Backend] = None, + cal_parameter_name: Optional[str] = "drive_freq", delays: Optional[List] = None, osc_freq: float = 2e6, auto_update: bool = True, @@ -46,6 +47,8 @@ def __init__( frequency calibration. calibrations: The calibrations instance with the schedules. backend: Optional, the backend to run the experiment on. + cal_parameter_name: The name of the parameter to update in the calibrations. + This defaults to `drive_freq`. delays: The list of delays that will be scanned in the experiment, in seconds. osc_freq: A frequency shift in Hz that will be applied by means of a virtual Z rotation to increase the frequency of the measured oscillation. @@ -58,7 +61,7 @@ def __init__( backend=backend, delays=delays, osc_freq=osc_freq, - cal_parameter_name=calibrations.__drive_freq_parameter__, + cal_parameter_name=cal_parameter_name, auto_update=auto_update, ) diff --git a/qiskit_experiments/library/calibration/rough_frequency.py b/qiskit_experiments/library/calibration/rough_frequency.py index a618c53749..da7a15baa9 100644 --- a/qiskit_experiments/library/calibration/rough_frequency.py +++ b/qiskit_experiments/library/calibration/rough_frequency.py @@ -40,6 +40,7 @@ def __init__( backend: Optional[Backend] = None, auto_update: bool = True, absolute: bool = True, + cal_parameter_name: Optional[str] = "drive_freq", ): """See :class:`.QubitSpectroscopy` for detailed documentation. @@ -53,6 +54,8 @@ def __init__( automatically update the frequency in the calibrations. absolute: Boolean to specify if the frequencies are absolute or relative to the qubit frequency in the backend. + cal_parameter_name: The name of the parameter to update in the calibrations. + This defaults to `drive_freq`. Raises: QiskitError: If there are less than three frequency shifts. @@ -66,6 +69,7 @@ def __init__( absolute=absolute, updater=Frequency, auto_update=auto_update, + cal_parameter_name=cal_parameter_name, ) def _attach_calibrations(self, circuit: QuantumCircuit): diff --git a/releasenotes/notes/params_without_schedule-20555d98875a626b.yaml b/releasenotes/notes/params_without_schedule-20555d98875a626b.yaml new file mode 100644 index 0000000000..5c1c019ac8 --- /dev/null +++ b/releasenotes/notes/params_without_schedule-20555d98875a626b.yaml @@ -0,0 +1,11 @@ +--- +upgrade: + - | + The variables `__drive_freq_parameter__` and `__readout_freq_parameter__` + have been removed from `Calibrations`. These variables were given special + treatment which is inconsistent with the framework. To replace them a + mechanism to define and add parameters without a schedule has been added to + the basis gate library. This has the added benefit of making the API of + frequency calibration experiments more consistent with the other calibration + experiments. Calibration developers can now add parameters to their library that are not + attached to a schedule in a meaningful way. diff --git a/test/calibration/test_calibrations.py b/test/calibration/test_calibrations.py index af70681421..0d8f4d78a7 100644 --- a/test/calibration/test_calibrations.py +++ b/test/calibration/test_calibrations.py @@ -217,12 +217,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), 9) + self.assertEqual(len(self.cals.parameters), 7) self.cals.remove_schedule(sched) self.assertEqual(len(self.cals.schedules()), 3) - self.assertEqual(len(self.cals.parameters), 8) + self.assertEqual(len(self.cals.parameters), 6) for param in [self.sigma, self.amp_xp, self.amp_x90p, self.amp_y90p, self.beta]: self.assertTrue(param in self.cals.parameters) @@ -323,7 +323,7 @@ def test_from_backend(self): """Test that when generating calibrations from backend the data is passed correctly""" backend = FakeBelemV2() - cals = Calibrations.from_backend(backend) + cals = Calibrations.from_backend(backend, libraries=[FixedFrequencyTransmon()]) with self.assertWarns(DeprecationWarning): config_args = cals.config()["kwargs"] control_channel_map_size = len(config_args["control_channel_map"].chan_map) @@ -565,8 +565,6 @@ def test_default_schedules(self): self.sigma, self.beta, self.duration, - self.cals.drive_freq, - self.cals.meas_freq, } self.assertEqual(len(set(self.cals.parameters.keys())), len(expected)) diff --git a/test/calibration/test_setup_library.py b/test/calibration/test_setup_library.py index 04b40a95a2..1c1307c97a 100644 --- a/test/calibration/test_setup_library.py +++ b/test/calibration/test_setup_library.py @@ -92,7 +92,7 @@ def test_standard_single_qubit_gates(self): self.assertListEqual(library.basis_gates, ["x", "y", "sx", "sy"]) def test_unlinked_parameters(self): - """Test the we get schedules with unlinked parameters.""" + """Test that we get schedules with unlinked parameters.""" library = FixedFrequencyTransmon(link_parameters=False) diff --git a/test/calibration/test_update_library.py b/test/calibration/test_update_library.py index a9404d64fd..0cc8186dfc 100644 --- a/test/calibration/test_update_library.py +++ b/test/calibration/test_update_library.py @@ -21,6 +21,7 @@ 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.basis_gate_library import FixedFrequencyTransmon from qiskit_experiments.test.mock_iq_backend import MockIQBackend from qiskit_experiments.test.mock_iq_helpers import MockIQSpectroscopyHelper as SpectroscopyHelper @@ -55,7 +56,7 @@ def test_frequency(self): self.assertEqual(result.quality, "good") # Test the integration with the Calibrations - cals = Calibrations.from_backend(FakeAthensV2()) - self.assertNotEqual(cals.get_parameter_value(cals.__drive_freq_parameter__, qubit), value) + cals = Calibrations.from_backend(FakeAthensV2(), libraries=[FixedFrequencyTransmon()]) + self.assertNotEqual(cals.get_parameter_value("drive_freq", qubit), value) Frequency.update(cals, exp_data) - self.assertEqual(cals.get_parameter_value(cals.__drive_freq_parameter__, qubit), value) + self.assertEqual(cals.get_parameter_value("drive_freq", qubit), value) diff --git a/test/library/calibration/test_fine_frequency.py b/test/library/calibration/test_fine_frequency.py index 09f40471ac..b9f256dd02 100644 --- a/test/library/calibration/test_fine_frequency.py +++ b/test/library/calibration/test_fine_frequency.py @@ -80,14 +80,14 @@ def test_calibration_version(self): fine_freq = FineFrequencyCal([0], self.cals, backend) armonk_freq = BackendData(FakeArmonkV2()).drive_freqs[0] - freq_before = self.cals.get_parameter_value(self.cals.__drive_freq_parameter__, 0) + freq_before = self.cals.get_parameter_value("drive_freq", 0) self.assertAlmostEqual(freq_before, armonk_freq) expdata = fine_freq.run() self.assertExperimentDone(expdata) - freq_after = self.cals.get_parameter_value(self.cals.__drive_freq_parameter__, 0) + freq_after = self.cals.get_parameter_value("drive_freq", 0) # Test equality up to 10kHz on a 100 kHz shift self.assertAlmostEqual(freq_after, armonk_freq + exp_helper.freq_shift, delta=1e4) diff --git a/test/library/calibration/test_ramsey_xy.py b/test/library/calibration/test_ramsey_xy.py index 391700b8b6..a3c9d11252 100644 --- a/test/library/calibration/test_ramsey_xy.py +++ b/test/library/calibration/test_ramsey_xy.py @@ -76,7 +76,7 @@ def test_update_calibrations(self): tol = 1e4 # 10 kHz resolution - freq_name = self.cals.__drive_freq_parameter__ + freq_name = "drive_freq" # Check qubit frequency before running the cal f01 = self.cals.get_parameter_value(freq_name, 0) diff --git a/test/library/calibration/test_rough_frequency.py b/test/library/calibration/test_rough_frequency.py index bc4171f774..3b32ad3d26 100644 --- a/test/library/calibration/test_rough_frequency.py +++ b/test/library/calibration/test_rough_frequency.py @@ -58,7 +58,7 @@ def test_update_calibrations(self): library = FixedFrequencyTransmon() cals = Calibrations.from_backend(self.backend, libraries=[library]) - prev_freq = cals.get_parameter_value(cals.__drive_freq_parameter__, (0,)) + prev_freq = cals.get_parameter_value("drive_freq", (0,)) self.assertEqual(prev_freq, freq01) frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 11) @@ -69,7 +69,7 @@ def test_update_calibrations(self): self.assertExperimentDone(expdata) # Check the updated frequency which should be shifted by 5MHz. - post_freq = cals.get_parameter_value(cals.__drive_freq_parameter__, (0,)) + post_freq = cals.get_parameter_value("drive_freq", (0,)) self.assertTrue(abs(post_freq - freq01 - 5e6) < 1e6) def test_experiment_config(self):