Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason to have this over the _additional_metadata function in BaseExperiment which can already add arbitrary dict values to ExperimentData.metadata?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use _additional_metadata. I'll open a small PR to adjust to this.

"""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

Expand All @@ -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)

Expand Down
62 changes: 29 additions & 33 deletions qiskit_experiments/library/calibration/fine_amplitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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):
Expand Down
72 changes: 33 additions & 39 deletions qiskit_experiments/library/calibration/fine_drag_cal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down
44 changes: 19 additions & 25 deletions qiskit_experiments/library/calibration/frequency_cal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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,
)
54 changes: 23 additions & 31 deletions qiskit_experiments/library/calibration/half_angle_cal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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,
)
Loading