Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
19f8899
* Added the update method to the calibrations.
eggerdj Jun 7, 2021
a19c821
* Used ParameterValue in update.
eggerdj Jun 7, 2021
71553c7
Update qiskit_experiments/calibration/calibrations.py
eggerdj Jun 10, 2021
4904e42
Merge branch 'main' into calibrations_spec_integration_v3
eggerdj Jun 10, 2021
9c6ef7f
* Updated docstring.
eggerdj Jun 10, 2021
299c959
* Added completion times.
eggerdj Jun 10, 2021
43a00cf
* More robust quality check.
eggerdj Jun 10, 2021
383fc08
* Changed default result index to -1.
eggerdj Jun 10, 2021
61c0920
* Updated spectroscopy integration.
eggerdj Jun 10, 2021
c1e29db
* Renamed calibration_types to calibration_key_types.
eggerdj Jun 10, 2021
4f2f40f
* Removed CalibrationExtraction.
eggerdj Jun 10, 2021
c7a4b71
* Removed CalibrationExtraction.
eggerdj Jun 10, 2021
f276c7b
* Black lint
eggerdj Jun 10, 2021
991f8ea
Merge branch 'main' into calibrations_spec_integration_v3
eggerdj Jun 14, 2021
9a76125
Update qiskit_experiments/calibration/calibration_key_types.py
eggerdj Jun 15, 2021
f6f2d83
Merge branch 'main' into calibrations_spec_integration_v3
eggerdj Jun 15, 2021
3fea5a4
* Added name for readability.
eggerdj Jun 15, 2021
722ba59
* Updated docstring.
eggerdj Jun 15, 2021
4e31847
Update qiskit_experiments/calibration/calibrations.py
eggerdj Jun 15, 2021
ae4a7c9
* Updated default timestamp to now.
eggerdj Jun 15, 2021
f032b5d
Merge branch 'main' into calibrations_spec_integration_v3
eggerdj Jun 15, 2021
0d8fc79
Merge branch 'main' into calibrations_spec_integration_v3
eggerdj Jun 16, 2021
ff9d254
Update qiskit_experiments/calibration/calibrations.py
eggerdj Jun 16, 2021
9f72426
Merge branch 'main' into calibrations_spec_integration_v3
eggerdj Jun 17, 2021
8cd5c89
Merge branch 'main' into calibrations_spec_integration_v3
eggerdj Jun 18, 2021
e0e3bfd
* Added an updater library.
eggerdj Jun 18, 2021
db00f3b
Merge branch 'main' into calibrations_spec_integration_v3
eggerdj Jun 20, 2021
a9a38b9
* Made update methodology more permissible.
eggerdj Jun 20, 2021
c3f5907
* Black
eggerdj Jun 20, 2021
468af33
* Moved methodology to class methods.
eggerdj Jun 21, 2021
d9d47df
* Bug fixes, black, and lint.
eggerdj Jun 21, 2021
864f3af
* Qubits in Rabi.
eggerdj Jun 21, 2021
744bb17
Merge branch 'main' of github.com:Qiskit/qiskit-experiments into cali…
eggerdj Jun 21, 2021
b818297
Update qiskit_experiments/calibration/update_library.py
eggerdj Jun 22, 2021
8e65eef
* Improved docstring.
eggerdj Jun 22, 2021
23b3a04
Update qiskit_experiments/calibration/update_library.py
eggerdj Jun 22, 2021
d4c792d
* Raise on Update instantiation.
eggerdj Jun 22, 2021
fac68ec
Merge branch 'calibrations_spec_integration_v3' of github.com:eggerdj…
eggerdj Jun 22, 2021
c2b4109
* Moved cals update tests to their own library.
eggerdj Jun 22, 2021
90b5617
* Renamed test.
eggerdj Jun 22, 2021
762ded0
* Made _add_parameter_value a static method.
eggerdj Jun 23, 2021
ce9b9fc
* Switched arguments order in update library.
eggerdj Jun 23, 2021
cac9706
* Added edgecase handling in the calibrations for parameter value add…
eggerdj Jun 23, 2021
e1e5a72
Merge branch 'main' into calibrations_spec_integration_v3
eggerdj Jun 23, 2021
4bb5861
* Changed behaviour of get_parameter_value.
eggerdj Jun 23, 2021
1262246
* Removed the adding of a microsecond.
eggerdj Jun 23, 2021
206baa1
* Test docstring fix.
eggerdj Jun 23, 2021
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
25 changes: 20 additions & 5 deletions qiskit_experiments/calibration/backend_calibrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,33 @@ class BackendCalibrations(Calibrations):
any schedule.
"""

__qubit_freq_parameter__ = "qubit_lo_freq"
__readout_freq_parameter__ = "meas_lo_freq"

def __init__(self, backend: Backend):
"""Setup an instance to manage the calibrations of a backend."""
super().__init__(backend.configuration().control_channels)
if hasattr(backend.configuration(), "control_channels"):
control_channels = backend.configuration().control_channels
else:
control_channels = None

super().__init__(control_channels)

# Use the same naming convention as in backend.defaults()
self.qubit_freq = Parameter("qubit_lo_freq")
self.meas_freq = Parameter("meas_lo_freq")
self.qubit_freq = Parameter(self.__qubit_freq_parameter__)
self.meas_freq = Parameter(self.__readout_freq_parameter__)
self._register_parameter(self.qubit_freq, ())
self._register_parameter(self.meas_freq, ())

self._qubits = set(range(backend.configuration().n_qubits))
self._backend = backend

for qubit, freq in enumerate(backend.defaults().qubit_freq_est):
self.add_parameter_value(freq, self.qubit_freq, qubit)

for meas, freq in enumerate(backend.defaults().meas_freq_est):
self.add_parameter_value(freq, self.meas_freq, meas)

def _get_frequencies(
self,
element: FrequencyElement,
Expand All @@ -70,8 +84,9 @@ def _get_frequencies(

freqs = []
for qubit in self._qubits:
if ParameterKey(None, param, (qubit,)) in self._params:
freq = self.get_parameter_value(param, (qubit,), None, True, group, cutoff_date)
schedule = None # A qubit frequency is not attached to a schedule.
if ParameterKey(param, (qubit,), schedule) in self._params:
freq = self.get_parameter_value(param, (qubit,), schedule, True, group, cutoff_date)
else:
if element == FrequencyElement.READOUT:
freq = self._backend.defaults().meas_freq_est[qubit]
Expand Down
23 changes: 23 additions & 0 deletions qiskit_experiments/calibration/calibration_key_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.

"""Types used by the calibration module."""

from typing import Union
from collections import namedtuple

from qiskit.circuit import ParameterExpression


ParameterKey = namedtuple("ParameterKey", ["parameter", "qubits", "schedule"])
ScheduleKey = namedtuple("ScheduleKey", ["schedule", "qubits"])
ParameterValueType = Union[ParameterExpression, float, int, complex]
Comment thread
eggerdj marked this conversation as resolved.
82 changes: 76 additions & 6 deletions qiskit_experiments/calibration/calibrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""Class to store and manage the results of calibration experiments."""

import os
from collections import namedtuple, defaultdict
from collections import defaultdict
from datetime import datetime
from typing import Any, Dict, Set, Tuple, Union, List, Optional
import csv
Expand All @@ -37,10 +37,12 @@
from qiskit.circuit import Parameter, ParameterExpression
from qiskit_experiments.calibration.exceptions import CalibrationError
from qiskit_experiments.calibration.parameter_value import ParameterValue

ParameterKey = namedtuple("ParameterKey", ["parameter", "qubits", "schedule"])
ScheduleKey = namedtuple("ScheduleKey", ["schedule", "qubits"])
ParameterValueType = Union[ParameterExpression, float, int, complex]
from qiskit_experiments.experiment_data import ExperimentData
from qiskit_experiments.calibration.calibration_key_types import (
ParameterKey,
ParameterValueType,
ScheduleKey,
)


class Calibrations:
Expand Down Expand Up @@ -897,6 +899,74 @@ def parameters_table(

return data

def update(
self,
exp_data: ExperimentData,
result_index: int = -1,
force_update: bool = False,
group: str = "default",
):
"""Update the calibrations form a result in the given experiment data.
Comment thread
eggerdj marked this conversation as resolved.
Outdated

This function allows users to update their calibrations from experiment data. Typically,
the value of the parameter to update is directly stored as the result of the fit.
Importantly, this method requires the following keys to be present in the analysis result
- cal_parameter: The name of the calibration parameter to update.
- qubits: The qubits to which the parameter belongs.
- cal_schedule: The name of the schedule which is updated, this can be None (e.g. for
qubit frequencies).
- cal_value: The value of the parameter which will enter the calibrations.
The ParameterKey formed by the values under (calibration_parameter, qubits,
calibration_schedule) must therefore be valid.

Args:
exp_data: An analysis result which contains either the value to update under the
key value or the information required by the calibration_extraction function
which will build the values to update.
result_index: The index of the result which defaults to 0.
force_update: If set to True then the calibrations will be updated even if the
quality of the result is "computer_bad".
group: The calibration group from which to draw the parameters.
If not specified this defaults to the 'default' group.

Raises:
CalibrationError: If the result does not have the required calibration keys. These
keys are "cal_parameter", "qubits", "cal_schedule", and "cal_value".
"""
result = exp_data.analysis_result(result_index)

if "quality" in result and result["quality"] == "computer_bad" and not force_update:
return

required_keys = ["cal_parameter", "cal_schedule", "cal_value"]
if not all(key in result for key in required_keys):
raise CalibrationError(
f"Cannot update calibrations from result. One of {required_keys} is missing."
)

timestamp = datetime.now()
all_times = exp_data.completion_times.values()
if all_times:
timestamp = max(all_times)
Comment thread
eggerdj marked this conversation as resolved.
Outdated

value = ParameterValue(
value=result["cal_value"],
date_time=timestamp,
group=group,
exp_id=exp_data.experiment_id,
)

schedule = result["cal_schedule"]
param = result["cal_parameter"]

# TODO update this with experiment metadata PR #67
try:
qubits = exp_data.data(0)["metadata"]["qubits"]
except KeyError as error:
raise CalibrationError("Cannot find qubit information in metadata.") from error

self.add_parameter_value(value, param, qubits, schedule)

def save(self, file_type: str = "csv", folder: str = None, overwrite: bool = False):
"""Save the parameterized schedules and parameter value.

Expand Down Expand Up @@ -1025,7 +1095,7 @@ def _to_tuple(qubits: Union[str, int, Tuple[int, ...]]) -> Tuple[int, ...]:
CalibrationError: If the given input does not conform to an int or
tuple of ints.
"""
if not qubits:
if qubits is None:
return tuple()

if isinstance(qubits, str):
Expand Down
8 changes: 7 additions & 1 deletion qiskit_experiments/characterization/qubit_spectroscopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
)
from qiskit_experiments.base_experiment import BaseExperiment
from qiskit_experiments.data_processing.processor_library import get_to_signal_processor
from qiskit_experiments.calibration.backend_calibrations import BackendCalibrations


class SpectroscopyAnalysis(CurveAnalysis):
Expand Down Expand Up @@ -169,6 +170,11 @@ def _post_processing(self, analysis_result: CurveAnalysisResult) -> CurveAnalysi
else:
analysis_result["quality"] = "computer_bad"

# Add calibration information
analysis_result["cal_parameter"] = BackendCalibrations.__qubit_freq_parameter__
analysis_result["cal_schedule"] = None
analysis_result["cal_value"] = fit_freq
Comment thread
eggerdj marked this conversation as resolved.
Outdated

return analysis_result


Expand Down Expand Up @@ -315,7 +321,7 @@ def circuits(self, backend: Optional[Backend] = None):
assigned_circ = circuit.assign_parameters({freq_param: freq}, inplace=False)
assigned_circ.metadata = {
"experiment_type": self._type,
"qubit": self.physical_qubits[0],
"qubits": (self.physical_qubits[0],),
"xval": freq,
"unit": "Hz",
"amplitude": self.experiment_options.amp,
Expand Down
11 changes: 11 additions & 0 deletions qiskit_experiments/experiment_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import os
import uuid
from collections import OrderedDict
from datetime import datetime

from qiskit.result import Result
from qiskit.providers import Backend
Expand Down Expand Up @@ -108,6 +109,16 @@ def job_ids(self) -> List[str]:
"""
return list(self._jobs.keys())

@property
def completion_times(self) -> Dict[str, datetime]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Once StoredData is merged, this will be the proper location of the completion_times property

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out @yaelbh . Which PR is StoredData?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

#113 and its continuation #115

"""Returns the completion times of the jobs."""
job_times = {}
for job_id, job in self._jobs.items():
if job is not None and "COMPLETED" in job.time_per_step():
job_times[job_id] = job.time_per_step().get("COMPLETED")

return job_times

@property
def backend(self) -> Backend:
"""Return backend.
Expand Down
6 changes: 6 additions & 0 deletions qiskit_experiments/test/mock_iq_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"""An mock IQ backend for testing."""

from typing import Dict, List, Tuple
from datetime import datetime
import numpy as np

from qiskit.providers.backend import BackendV1 as Backend
Expand All @@ -33,6 +34,11 @@ def result(self) -> Result:
"""Return a result."""
return Result.from_dict(self._result)

@staticmethod
def time_per_step() -> Dict[str, datetime]:
"""Return the completion time."""
return {"COMPLETED": datetime.now()}

def submit(self):
pass

Expand Down
16 changes: 15 additions & 1 deletion test/test_qubit_spectroscopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
import numpy as np
from qiskit.qobj.utils import MeasLevel
from qiskit.test import QiskitTestCase
from qiskit.test.mock import FakeAthens

from qiskit_experiments.characterization.qubit_spectroscopy import QubitSpectroscopy
from qiskit_experiments.test.mock_iq_backend import TestJob, IQTestBackend
from qiskit_experiments.analysis import get_opt_value
from qiskit_experiments.calibration.backend_calibrations import BackendCalibrations


class SpectroscopyBackend(IQTestBackend):
Expand Down Expand Up @@ -94,6 +96,12 @@ def run(
class TestQubitSpectroscopy(QiskitTestCase):
"""Test spectroscopy experiment."""

def setUp(self):
"""Setup the test."""
super().setUp()

self._cals = BackendCalibrations(FakeAthens())

def test_spectroscopy_end2end_classified(self):
"""End to end test of the spectroscopy experiment."""

Expand All @@ -114,14 +122,20 @@ def test_spectroscopy_end2end_classified(self):

spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz")
spec.set_run_options(meas_level=MeasLevel.CLASSIFIED)
result = spec.run(backend).analysis_result(0)
exp_data = spec.run(backend)
result = exp_data.analysis_result(0)

value = get_opt_value(result, "freq")

self.assertTrue(value < 5.1e6)
self.assertTrue(value > 4.9e6)
self.assertEqual(result["quality"], "computer_good")

# Test the integration with the BackendCalibrations
self.assertNotEqual(self._cals.get_qubit_frequencies()[3], result["cal_value"])
self._cals.update(exp_data)
self.assertEqual(self._cals.get_qubit_frequencies()[3], result["cal_value"])

def test_spectroscopy_end2end_kerneled(self):
"""End to end test of the spectroscopy experiment on IQ data."""

Expand Down