diff --git a/qiskit_experiments/calibration_management/base_calibration_experiment.py b/qiskit_experiments/calibration_management/base_calibration_experiment.py index 50eac604e6..bc9d048155 100644 --- a/qiskit_experiments/calibration_management/base_calibration_experiment.py +++ b/qiskit_experiments/calibration_management/base_calibration_experiment.py @@ -141,7 +141,7 @@ def calibrations(self) -> Calibrations: @classmethod def _default_experiment_options(cls): - """Default values for the fine amplitude calibration experiment. + """Default values for a calibration experiment. Experiment Options: result_index (int): The index of the result from which to update the calibrations. diff --git a/qiskit_experiments/library/__init__.py b/qiskit_experiments/library/__init__.py index 6ef76a591b..b23ab5e9ef 100644 --- a/qiskit_experiments/library/__init__.py +++ b/qiskit_experiments/library/__init__.py @@ -69,6 +69,8 @@ ~characterization.FineSXAmplitude ~characterization.Rabi ~characterization.EFRabi + ~characterization.RamseyXY + .. _calibration: @@ -93,7 +95,6 @@ class instance to manage parameters and pulse schedules. ~calibration.FineAmplitudeCal ~calibration.FineXAmplitudeCal ~calibration.FineSXAmplitudeCal - ~calibration.RamseyXY ~calibration.RoughAmplitudeCal ~calibration.RoughXSXAmplitudeCal ~calibration.EFRoughXSXAmplitudeCal @@ -111,7 +112,7 @@ class instance to manage parameters and pulse schedules. FineXAmplitudeCal, FineSXAmplitudeCal, RoughFrequencyCal, - RamseyXY, + FrequencyCal, ) from .characterization import ( T1, @@ -127,6 +128,7 @@ class instance to manage parameters and pulse schedules. FineAmplitude, FineXAmplitude, FineSXAmplitude, + RamseyXY, ) from .randomized_benchmarking import StandardRB, InterleavedRB from .tomography import StateTomography, ProcessTomography diff --git a/qiskit_experiments/library/calibration/__init__.py b/qiskit_experiments/library/calibration/__init__.py index a54b7a4c94..c56d1b23b2 100644 --- a/qiskit_experiments/library/calibration/__init__.py +++ b/qiskit_experiments/library/calibration/__init__.py @@ -40,6 +40,7 @@ :template: autosummary/experiment.rst RoughFrequencyCal + FrequencyCal RoughDragCal FineDrag FineXDrag @@ -47,7 +48,6 @@ FineAmplitudeCal FineXAmplitudeCal FineSXAmplitudeCal - RamseyXY RoughAmplitudeCal RoughXSXAmplitudeCal EFRoughXSXAmplitudeCal @@ -74,7 +74,7 @@ from .fine_drag import FineDrag, FineXDrag, FineSXDrag from .rough_amplitude_cal import RoughAmplitudeCal, RoughXSXAmplitudeCal, EFRoughXSXAmplitudeCal from .fine_amplitude import FineAmplitudeCal, FineXAmplitudeCal, FineSXAmplitudeCal -from .ramsey_xy import RamseyXY +from .frequency_cal import FrequencyCal from .analysis.drag_analysis import DragCalAnalysis from .analysis.fine_drag_analysis import FineDragAnalysis diff --git a/qiskit_experiments/library/calibration/fine_amplitude.py b/qiskit_experiments/library/calibration/fine_amplitude.py index 118bfb22cd..b01e0bb703 100644 --- a/qiskit_experiments/library/calibration/fine_amplitude.py +++ b/qiskit_experiments/library/calibration/fine_amplitude.py @@ -112,8 +112,6 @@ def _add_cal_metadata(self, circuits: List[QuantumCircuit]): circuit.metadata["target_angle"] = self.experiment_options.target_angle circuit.metadata["cal_group"] = self.experiment_options.group - return circuits - def update_calibrations(self, experiment_data: ExperimentData): r"""Update the amplitude of the pulse in the calibrations. diff --git a/qiskit_experiments/library/calibration/frequency_cal.py b/qiskit_experiments/library/calibration/frequency_cal.py new file mode 100644 index 0000000000..db8acf95fe --- /dev/null +++ b/qiskit_experiments/library/calibration/frequency_cal.py @@ -0,0 +1,110 @@ +# 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. + +"""Ramsey XY frequency calibration experiment.""" + +from typing import List, Optional + +from qiskit import QuantumCircuit +from qiskit.providers.backend import Backend + +from qiskit_experiments.framework import ExperimentData, fix_class_docs +from qiskit_experiments.library.characterization.ramsey_xy import RamseyXY +from qiskit_experiments.calibration_management.backend_calibrations import BackendCalibrations +from qiskit_experiments.calibration_management.update_library import BaseUpdater +from qiskit_experiments.calibration_management.base_calibration_experiment import ( + BaseCalibrationExperiment, +) + + +@fix_class_docs +class FrequencyCal(BaseCalibrationExperiment, RamseyXY): + """A qubit frequency calibration experiment based on the Ramsey XY experiment. + + # section: see_also + qiskit_experiments.library.characterization.ramsey_xy.RamseyXY + """ + + def __init__( + self, + qubit: int, + calibrations: BackendCalibrations, + backend: Optional[Backend] = None, + delays: Optional[List] = None, + unit: str = "s", + osc_freq: float = 2e6, + auto_update: bool = True, + ): + """ + Args: + qubit: The qubit on which to run the frequency calibration. + calibrations: The calibrations instance with the schedules. + backend: Optional, the backend to run the experiment on. + delays: The list of delays that will be scanned in the experiment. + unit: The unit of the delays. Accepted values are dt, i.e. the + duration of a single sample on the backend, seconds, and sub-units, + e.g. ms, us, ns. + 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. + auto_update: If set to True, which is the default, then the experiment will + automatically update the frequency in the calibrations. + """ + super().__init__( + calibrations, + qubit, + backend=backend, + delays=delays, + unit=unit, + osc_freq=osc_freq, + cal_parameter_name="qubit_lo_freq", + auto_update=auto_update, + ) + + # Instruction schedule map to bring in the calibrations for the sx gate. + self.set_transpile_options(inst_map=calibrations.default_inst_map) + + def _add_cal_metadata(self, circuits: List[QuantumCircuit]): + """Add the oscillation frequency of the experiment to the metadata.""" + + param_val = self._cals.get_parameter_value( + self._param_name, + self.physical_qubits, + group=self.experiment_options.group, + ) + + for circuit in circuits: + circuit.metadata["cal_param_value"] = param_val + circuit.metadata["cal_group"] = self.experiment_options.group + circuit.metadata["osc_freq"] = self.experiment_options.osc_freq + + def update_calibrations(self, experiment_data: ExperimentData): + """Update the frequency using the reported frequency less the imparted oscillation.""" + + data = experiment_data.data() + + # No data -> no update + if len(data) > 0: + result_index = self.experiment_options.result_index + osc_freq = data[0]["metadata"]["osc_freq"] + group = data[0]["metadata"]["cal_group"] + old_freq = data[0]["metadata"]["cal_param_value"] + + fit_freq = BaseUpdater.get_value(experiment_data, "freq", result_index) + new_freq = old_freq + fit_freq - osc_freq + + BaseUpdater.add_parameter_value( + self._cals, + experiment_data, + new_freq, + self._param_name, + group=group, + ) diff --git a/qiskit_experiments/library/characterization/__init__.py b/qiskit_experiments/library/characterization/__init__.py index a3026c4515..c583f03dd3 100644 --- a/qiskit_experiments/library/characterization/__init__.py +++ b/qiskit_experiments/library/characterization/__init__.py @@ -34,6 +34,7 @@ FineAmplitude FineXAmplitude FineSXAmplitude + RamseyXY RoughDrag @@ -61,4 +62,5 @@ from .rabi import Rabi, EFRabi from .half_angle import HalfAngle from .fine_amplitude import FineAmplitude, FineXAmplitude, FineSXAmplitude +from .ramsey_xy import RamseyXY from .drag import RoughDrag diff --git a/qiskit_experiments/library/calibration/ramsey_xy.py b/qiskit_experiments/library/characterization/ramsey_xy.py similarity index 93% rename from qiskit_experiments/library/calibration/ramsey_xy.py rename to qiskit_experiments/library/characterization/ramsey_xy.py index c22ffedda9..ce18583c1e 100644 --- a/qiskit_experiments/library/calibration/ramsey_xy.py +++ b/qiskit_experiments/library/characterization/ramsey_xy.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Ramsey XY frequency calibration experiment.""" +"""Ramsey XY frequency characterization experiment.""" from typing import List, Optional import numpy as np @@ -88,7 +88,6 @@ def _default_experiment_options(cls): """Default values for the Ramsey XY experiment. Experiment Options: - schedule (ScheduleBlock): The schedule for the sx gate. delays (list): The list of delays that will be scanned in the experiment. unit (str): The unit of the delays. Accepted values are dt, i.e. the duration of a single sample on the backend, seconds, and sub-units, @@ -97,7 +96,6 @@ def _default_experiment_options(cls): a virtual Z rotation to increase the frequency of the measured oscillation. """ options = super()._default_experiment_options() - options.schedule = None options.delays = np.linspace(0, 1.0e-6, 51) options.unit = "s" options.osc_freq = 2e6 @@ -130,13 +128,13 @@ def __init__( def _pre_circuit(self) -> QuantumCircuit: """Return a preparation circuit. - This method can be overridden by subclasses e.g. to calibrate schedules on - transitions other than the 0 <-> 1 transition. + This method can be overridden by subclasses e.g. to run on transitions other + than the 0 <-> 1 transition. """ return QuantumCircuit(1) def circuits(self) -> List[QuantumCircuit]: - """Create the circuits for the Ramsey XY calibration experiment. + """Create the circuits for the Ramsey XY characterization experiment. Returns: A list of circuits with a variable delay. @@ -187,12 +185,6 @@ def circuits(self) -> List[QuantumCircuit]: ram_y.measure_active() ram_y.metadata = metadata.copy() - # Add the schedule if any. - schedule = self.experiment_options.schedule - if schedule is not None: - for circ in [ram_x, ram_y]: - circ.add_calibration("sx", self.physical_qubits, schedule) - circs = [] for delay in self.experiment_options.delays: diff --git a/test/calibration/experiments/test_ramsey_xy.py b/test/calibration/experiments/test_ramsey_xy.py index d0550ffc83..ace945416a 100644 --- a/test/calibration/experiments/test_ramsey_xy.py +++ b/test/calibration/experiments/test_ramsey_xy.py @@ -10,18 +10,27 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test Ramsey XY calibration experiment.""" +"""Test Ramsey XY experiments.""" from qiskit.test import QiskitTestCase -import qiskit.pulse as pulse +from qiskit.test.mock import FakeArmonk -from qiskit_experiments.library import RamseyXY +from qiskit_experiments.calibration_management.backend_calibrations import BackendCalibrations +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 class TestRamseyXY(QiskitTestCase): """Tests for the Ramsey XY experiment.""" + def setUp(self): + """Initialize some cals.""" + super().setUp() + + library = FixedFrequencyTransmon() + self.cals = BackendCalibrations(FakeArmonk(), library) + def test_end_to_end(self): """Test that we can run on a mock backend and perform a fit. @@ -31,13 +40,32 @@ def test_end_to_end(self): test_tol = 0.01 ramsey = RamseyXY(0) - ramsey.set_experiment_options(schedule=pulse.ScheduleBlock()) for freq_shift in [2e6, -3e6]: test_data = ramsey.run(MockRamseyXY(freq_shift=freq_shift)).block_for_results() meas_shift = test_data.analysis_results(1).value.value self.assertTrue((meas_shift - freq_shift) < abs(test_tol * freq_shift)) + def test_update_calibrations(self): + """Test that the calibration version of the experiment updates the cals.""" + + tol = 1e4 # 10 kHz resolution + + # 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) + self.assertEqual(f01, FakeArmonk().defaults().qubit_freq_est[0]) + + freq_shift = 4e6 + osc_shift = 2e6 + backend = MockRamseyXY(freq_shift=freq_shift + osc_shift) # oscillation with 6 MHz + 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) + self.assertTrue(abs(f01 - (freq_shift + FakeArmonk().defaults().qubit_freq_est[0])) < tol) + def test_experiment_config(self): """Test converting to and from config works""" exp = RamseyXY(0) @@ -45,3 +73,9 @@ def test_experiment_config(self): loaded_exp = RamseyXY.from_config(config) self.assertNotEqual(exp, loaded_exp) self.assertEqual(config, loaded_exp.config) + + exp = FrequencyCal(0, self.cals) + config = exp.config + loaded_exp = FrequencyCal.from_config(config) + self.assertNotEqual(exp, loaded_exp) + self.assertEqual(config, loaded_exp.config)