diff --git a/qiskit_experiments/calibration_management/base_calibration_experiment.py b/qiskit_experiments/calibration_management/base_calibration_experiment.py index bc9d048155..60095d405b 100644 --- a/qiskit_experiments/calibration_management/base_calibration_experiment.py +++ b/qiskit_experiments/calibration_management/base_calibration_experiment.py @@ -13,11 +13,11 @@ """Base class for calibration-type experiments.""" from abc import ABC -from typing import Dict, List, Optional, Tuple, Type +from typing import Dict, Optional, Tuple, Type import warnings from qiskit.providers.backend import Backend -from qiskit.circuit import Parameter, QuantumCircuit +from qiskit.circuit import Parameter from qiskit.pulse import ScheduleBlock from qiskit_experiments.calibration_management.calibrations import Calibrations @@ -356,20 +356,11 @@ def get_schedule( return schedules - def circuits(self) -> List[QuantumCircuit]: - """A wrapper to introduce an optional hook to add circuit metadata.""" - circuits = super().circuits() + def _add_cal_metadata(self, experiment_data: ExperimentData): + """A hook to add calibration metadata to the experiment data. - self._add_cal_metadata(circuits) - - return circuits - - def _add_cal_metadata(self, circuits: List[QuantumCircuit]): - """A hook to add calibration metadata to the circuits. - - Many calibration experiments will not define the circuits method but rely on those defined - by the experiment in the characterization module. This hook allows calibration experiments - to add their own meta data to the circuits if needed. + This hook allows calibration experiments to add their own meta data to the + experiment data if needed. """ pass @@ -393,6 +384,8 @@ def run( """ experiment_data = super().run(backend, analysis, **run_options) + self._add_cal_metadata(experiment_data) + if self.auto_update and analysis: experiment_data.add_analysis_callback(self.update_calibrations) diff --git a/qiskit_experiments/library/calibration/fine_amplitude.py b/qiskit_experiments/library/calibration/fine_amplitude.py index 9cbd3c3c34..b4f9de537a 100644 --- a/qiskit_experiments/library/calibration/fine_amplitude.py +++ b/qiskit_experiments/library/calibration/fine_amplitude.py @@ -12,10 +12,10 @@ """Fine amplitude calibration experiment.""" -from typing import List, Optional +from typing import Optional import numpy as np -from qiskit.circuit import Gate, QuantumCircuit +from qiskit.circuit import Gate from qiskit.providers.backend import Backend from qiskit_experiments.calibration_management import ( @@ -81,8 +81,8 @@ def _default_experiment_options(cls): options.target_angle = np.pi return options - def _add_cal_metadata(self, circuits: List[QuantumCircuit]): - """Add metadata to the circuit to make the experiment data more self contained. + def _add_cal_metadata(self, experiment_data: ExperimentData): + """Add metadata to the experiment data making it more self contained. The following keys are added to each circuit's metadata: cal_param_value: The value of the pulse amplitude. This value together with @@ -100,12 +100,11 @@ def _add_cal_metadata(self, circuits: List[QuantumCircuit]): group=self.experiment_options.group, ) - for circuit in circuits: - circuit.metadata["cal_param_value"] = param_val - circuit.metadata["cal_param_name"] = self._param_name - circuit.metadata["cal_schedule"] = self._sched_name - circuit.metadata["target_angle"] = self.experiment_options.target_angle - circuit.metadata["cal_group"] = self.experiment_options.group + experiment_data.metadata["cal_param_value"] = param_val + experiment_data.metadata["cal_param_name"] = self._param_name + experiment_data.metadata["cal_schedule"] = self._sched_name + experiment_data.metadata["target_angle"] = self.experiment_options.target_angle + experiment_data.metadata["cal_group"] = self.experiment_options.group def update_calibrations(self, experiment_data: ExperimentData): r"""Update the amplitude of the pulse in the calibrations. @@ -122,29 +121,26 @@ def update_calibrations(self, experiment_data: ExperimentData): experiment_data: The experiment data from which to extract the measured over/under rotation used to adjust the amplitude. """ - data = experiment_data.data() - - # No data -> no update - if len(data) > 0: - result_index = self.experiment_options.result_index - group = data[0]["metadata"]["cal_group"] - target_angle = data[0]["metadata"]["target_angle"] - prev_amp = data[0]["metadata"]["cal_param_value"] - - # Protect against cases where the complex amplitude was converted to a list. - if isinstance(prev_amp, list) and len(prev_amp) == 2: - prev_amp = prev_amp[0] + 1.0j * prev_amp[1] - - d_theta = BaseUpdater.get_value(experiment_data, "d_theta", result_index) - - BaseUpdater.add_parameter_value( - self._cals, - experiment_data, - prev_amp * target_angle / (target_angle + d_theta), - self._param_name, - self._sched_name, - group, - ) + + result_index = self.experiment_options.result_index + group = experiment_data.metadata["cal_group"] + target_angle = experiment_data.metadata["target_angle"] + prev_amp = experiment_data.metadata["cal_param_value"] + + # Protect against cases where the complex amplitude was converted to a list. + if isinstance(prev_amp, list) and len(prev_amp) == 2: + prev_amp = prev_amp[0] + 1.0j * prev_amp[1] + + d_theta = BaseUpdater.get_value(experiment_data, "d_theta", result_index) + + BaseUpdater.add_parameter_value( + self._cals, + experiment_data, + prev_amp * target_angle / (target_angle + d_theta), + self._param_name, + self._sched_name, + group, + ) class FineXAmplitudeCal(FineAmplitudeCal): diff --git a/qiskit_experiments/library/calibration/fine_drag_cal.py b/qiskit_experiments/library/calibration/fine_drag_cal.py index 1929d81c7f..2d8b3a348b 100644 --- a/qiskit_experiments/library/calibration/fine_drag_cal.py +++ b/qiskit_experiments/library/calibration/fine_drag_cal.py @@ -12,10 +12,10 @@ """Fine drag calibration experiment.""" -from typing import List, Optional +from typing import Optional import numpy as np -from qiskit.circuit import Gate, QuantumCircuit +from qiskit.circuit import Gate from qiskit.providers.backend import Backend from qiskit.pulse import Play @@ -82,8 +82,8 @@ def _default_experiment_options(cls) -> Options: options.target_angle = np.pi return options - def _add_cal_metadata(self, circuits: List[QuantumCircuit]): - """Add metadata to the circuit to make the experiment data more self contained. + def _add_cal_metadata(self, experiment_data: ExperimentData): + """Add metadata to the experiment data making it more self contained. The following keys are added to each circuit's metadata: cal_param_value: The value of the drag parameter. This value together with @@ -101,52 +101,46 @@ def _add_cal_metadata(self, circuits: List[QuantumCircuit]): group=self.experiment_options.group, ) - for circuit in circuits: - circuit.metadata["cal_param_value"] = param_val - circuit.metadata["cal_param_name"] = self._param_name - circuit.metadata["cal_schedule"] = self._sched_name - circuit.metadata["target_angle"] = self.experiment_options.target_angle - circuit.metadata["cal_group"] = self.experiment_options.group + experiment_data.metadata["cal_param_value"] = param_val + experiment_data.metadata["cal_param_name"] = self._param_name + experiment_data.metadata["cal_schedule"] = self._sched_name + experiment_data.metadata["target_angle"] = self.experiment_options.target_angle + experiment_data.metadata["cal_group"] = self.experiment_options.group def update_calibrations(self, experiment_data: ExperimentData): """Update the drag parameter of the pulse in the calibrations.""" - data = experiment_data.data() + result_index = self.experiment_options.result_index + group = experiment_data.metadata["cal_group"] + target_angle = experiment_data.metadata["target_angle"] + qubits = experiment_data.metadata["physical_qubits"] - # No data -> no update - if len(data) > 0: + schedule = self._cals.get_schedule(self._sched_name, qubits) - result_index = self.experiment_options.result_index - group = data[0]["metadata"]["cal_group"] - target_angle = data[0]["metadata"]["target_angle"] - qubits = experiment_data.metadata["physical_qubits"] + # Obtain sigma as it is needed for the fine DRAG update rule. + sigmas = [] + for block in schedule.blocks: + if isinstance(block, Play) and hasattr(block.pulse, "sigma"): + sigmas.append(getattr(block.pulse, "sigma")) - schedule = self._cals.get_schedule(self._sched_name, qubits) - - # Obtain sigma as it is needed for the fine DRAG update rule. - sigmas = [] - for block in schedule.blocks: - if isinstance(block, Play) and hasattr(block.pulse, "sigma"): - sigmas.append(getattr(block.pulse, "sigma")) - - if len(set(sigmas)) != 1: - raise CalibrationError( - "Cannot run fine Drag calibration on a schedule with multiple values of sigma." - ) + if len(set(sigmas)) != 1: + raise CalibrationError( + "Cannot run fine Drag calibration on a schedule with multiple values of sigma." + ) - if len(sigmas) == 0: - raise CalibrationError(f"Could not infer sigma from {schedule}.") + if len(sigmas) == 0: + raise CalibrationError(f"Could not infer sigma from {schedule}.") - d_theta = BaseUpdater.get_value(experiment_data, "d_theta", result_index) + d_theta = BaseUpdater.get_value(experiment_data, "d_theta", result_index) - # See the documentation in fine_drag.py for the derivation of this rule. - d_beta = -np.sqrt(np.pi) * d_theta * sigmas[0] / target_angle ** 2 - old_beta = data[0]["metadata"]["cal_param_value"] - new_beta = old_beta + d_beta + # See the documentation in fine_drag.py for the derivation of this rule. + d_beta = -np.sqrt(np.pi) * d_theta * sigmas[0] / target_angle ** 2 + old_beta = experiment_data.metadata["cal_param_value"] + new_beta = old_beta + d_beta - BaseUpdater.add_parameter_value( - self._cals, experiment_data, new_beta, self._param_name, schedule, group - ) + BaseUpdater.add_parameter_value( + self._cals, experiment_data, new_beta, self._param_name, schedule, group + ) class FineXDragCal(FineDragCal): diff --git a/qiskit_experiments/library/calibration/frequency_cal.py b/qiskit_experiments/library/calibration/frequency_cal.py index 61440e32fe..1cfc4dfd2b 100644 --- a/qiskit_experiments/library/calibration/frequency_cal.py +++ b/qiskit_experiments/library/calibration/frequency_cal.py @@ -14,7 +14,6 @@ from typing import List, Optional -from qiskit import QuantumCircuit from qiskit.providers.backend import Backend from qiskit_experiments.framework import ExperimentData @@ -71,7 +70,7 @@ def __init__( # 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]): + def _add_cal_metadata(self, experiment_data: ExperimentData): """Add the oscillation frequency of the experiment to the metadata.""" param_val = self._cals.get_parameter_value( @@ -80,30 +79,25 @@ def _add_cal_metadata(self, circuits: List[QuantumCircuit]): 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 + experiment_data.metadata["cal_param_value"] = param_val + experiment_data.metadata["cal_group"] = self.experiment_options.group + experiment_data.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, - ) + result_index = self.experiment_options.result_index + osc_freq = experiment_data.metadata["osc_freq"] + group = experiment_data.metadata["cal_group"] + old_freq = experiment_data.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/calibration/half_angle_cal.py b/qiskit_experiments/library/calibration/half_angle_cal.py index 56cbeae1a1..dc195ddf77 100644 --- a/qiskit_experiments/library/calibration/half_angle_cal.py +++ b/qiskit_experiments/library/calibration/half_angle_cal.py @@ -12,10 +12,9 @@ """Half angle calibration.""" -from typing import List, Optional +from typing import Optional import numpy as np -from qiskit import QuantumCircuit from qiskit.providers.backend import Backend from qiskit_experiments.framework import ExperimentData @@ -62,10 +61,10 @@ def __init__( self.set_transpile_options(inst_map=calibrations.default_inst_map) - def _add_cal_metadata(self, circuits: List[QuantumCircuit]): - """Add metadata to the circuit to make the experiment data more self contained. + def _add_cal_metadata(self, experiment_data: ExperimentData): + """Add metadata to the experiment data making it more self contained. - The following keys are added to each circuit's metadata: + The following keys are added to the metadata: cal_param_value: The value of the pulse amplitude. This value together with the fit result will be used to find the new value of the pulse amplitude. cal_param_name: The name of the parameter in the calibrations. @@ -80,13 +79,10 @@ def _add_cal_metadata(self, circuits: List[QuantumCircuit]): group=self.experiment_options.group, ) - for circuit in circuits: - circuit.metadata["cal_param_value"] = param_val - circuit.metadata["cal_param_name"] = self._param_name - circuit.metadata["cal_schedule"] = self._sched_name - circuit.metadata["cal_group"] = self.experiment_options.group - - return circuits + experiment_data.metadata["cal_param_value"] = param_val + experiment_data.metadata["cal_param_name"] = self._param_name + experiment_data.metadata["cal_schedule"] = self._sched_name + experiment_data.metadata["cal_group"] = self.experiment_options.group def update_calibrations(self, experiment_data: ExperimentData): r"""Update the value of the parameter in the calibrations. @@ -109,22 +105,18 @@ def update_calibrations(self, experiment_data: ExperimentData): rotation used to adjust the amplitude. """ - data = experiment_data.data() - - # No data -> no update - if len(data) > 0: - result_index = self.experiment_options.result_index - group = data[0]["metadata"]["cal_group"] - prev_amp = data[0]["metadata"]["cal_param_value"] - - d_theta = BaseUpdater.get_value(experiment_data, "d_hac", result_index) - new_amp = prev_amp * np.exp(-1.0j * d_theta / 2) - - BaseUpdater.add_parameter_value( - self._cals, - experiment_data, - new_amp, - self._param_name, - self._sched_name, - group, - ) + result_index = self.experiment_options.result_index + group = experiment_data.metadata["cal_group"] + prev_amp = experiment_data.metadata["cal_param_value"] + + d_theta = BaseUpdater.get_value(experiment_data, "d_hac", result_index) + new_amp = prev_amp * np.exp(-1.0j * d_theta / 2) + + BaseUpdater.add_parameter_value( + self._cals, + experiment_data, + new_amp, + self._param_name, + self._sched_name, + group, + ) diff --git a/qiskit_experiments/library/calibration/rough_amplitude_cal.py b/qiskit_experiments/library/calibration/rough_amplitude_cal.py index 5b2fce026e..ffe865b741 100644 --- a/qiskit_experiments/library/calibration/rough_amplitude_cal.py +++ b/qiskit_experiments/library/calibration/rough_amplitude_cal.py @@ -13,7 +13,7 @@ """Rough amplitude calibration using Rabi.""" from collections import namedtuple -from typing import Iterable, List, Optional +from typing import Iterable, Optional import numpy as np from qiskit import QuantumCircuit @@ -120,8 +120,8 @@ def _default_experiment_options(cls): return options - def _add_cal_metadata(self, circuits: List[QuantumCircuit]): - """Add metadata to the circuit to make the experiment data more self contained. + def _add_cal_metadata(self, experiment_data: ExperimentData): + """Add metadata to the experiment data making it more self contained. The following keys are added to each circuit's metadata: angles_schedules: A list of parameter update information. Each entry of the list @@ -149,11 +149,8 @@ def _add_cal_metadata(self, circuits: List[QuantumCircuit]): ) ) - for circuit in circuits: - circuit.metadata["angles_schedules"] = param_values - circuit.metadata["cal_group"] = self.experiment_options.group - - return circuits + experiment_data.metadata["angles_schedules"] = param_values + experiment_data.metadata["cal_group"] = self.experiment_options.group def update_calibrations(self, experiment_data: ExperimentData): r"""Update the amplitude of one or several schedules. @@ -175,26 +172,22 @@ def update_calibrations(self, experiment_data: ExperimentData): used to set the pulse amplitude. """ - data = experiment_data.data() - - # No data -> no update - if len(data) > 0: - result_index = self.experiment_options.result_index - group = data[0]["metadata"]["cal_group"] + result_index = self.experiment_options.result_index + group = experiment_data.metadata["cal_group"] - rate = ( - 2 - * np.pi - * BaseUpdater.get_value(experiment_data, self._analysis_param_name, result_index) - ) + rate = ( + 2 + * np.pi + * BaseUpdater.get_value(experiment_data, self._analysis_param_name, result_index) + ) - for angle, param, schedule, prev_amp in data[0]["metadata"]["angles_schedules"]: + for angle, param, schedule, prev_amp in experiment_data.metadata["angles_schedules"]: - value = np.round(angle / rate, decimals=8) * np.exp(1.0j * np.angle(prev_amp)) + value = np.round(angle / rate, decimals=8) * np.exp(1.0j * np.angle(prev_amp)) - BaseUpdater.add_parameter_value( - self._cals, experiment_data, value, param, schedule, group - ) + BaseUpdater.add_parameter_value( + self._cals, experiment_data, value, param, schedule, group + ) class RoughXSXAmplitudeCal(RoughAmplitudeCal): diff --git a/qiskit_experiments/library/calibration/rough_drag_cal.py b/qiskit_experiments/library/calibration/rough_drag_cal.py index 92d3e620ad..9d64a4a1f7 100644 --- a/qiskit_experiments/library/calibration/rough_drag_cal.py +++ b/qiskit_experiments/library/calibration/rough_drag_cal.py @@ -12,9 +12,8 @@ """Rough drag calibration experiment.""" -from typing import Iterable, List, Optional +from typing import Iterable, Optional -from qiskit import QuantumCircuit from qiskit.circuit import Parameter from qiskit.providers.backend import Backend @@ -75,8 +74,8 @@ def __init__( auto_update=auto_update, ) - def _add_cal_metadata(self, circuits: List[QuantumCircuit]): - """Add metadata to the circuit to make the experiment data more self contained. + def _add_cal_metadata(self, experiment_data: ExperimentData): + """Add metadata to the experiment data making it more self contained. The following keys are added to each circuit's metadata: cal_param_value: The value of the previous calibrated beta. @@ -89,11 +88,10 @@ def _add_cal_metadata(self, circuits: List[QuantumCircuit]): self._param_name, self.physical_qubits, self._sched_name, self.experiment_options.group ) - for circuit in circuits: - circuit.metadata["cal_param_value"] = prev_beta - circuit.metadata["cal_param_name"] = self._param_name - circuit.metadata["cal_schedule"] = self._sched_name - circuit.metadata["cal_group"] = self.experiment_options.group + experiment_data.metadata["cal_param_value"] = prev_beta + experiment_data.metadata["cal_param_name"] = self._param_name + experiment_data.metadata["cal_schedule"] = self._sched_name + experiment_data.metadata["cal_group"] = self.experiment_options.group def update_calibrations(self, experiment_data: ExperimentData): """Update the beta using the value directly reported from the fit.