Skip to content

Commit

Permalink
Merge branch 'main' into dispersive_qutrit
Browse files Browse the repository at this point in the history
  • Loading branch information
andrea-pasquale committed Oct 31, 2023
2 parents 60e81e5 + a731ce0 commit 6901c6b
Show file tree
Hide file tree
Showing 34 changed files with 1,278 additions and 486 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ repos:
- id: check-merge-conflict
- id: debug-statements
- repo: https://github.com/psf/black
rev: 23.9.1
rev: 23.10.1
hooks:
- id: black
- repo: https://github.com/pycqa/isort
Expand Down
2 changes: 2 additions & 0 deletions doc/source/getting-started/interface.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _interface:

How to use Qibocal?
===================

Expand Down
108 changes: 108 additions & 0 deletions doc/source/tutorials/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
How to use Qibocal as a library
===============================

Qibocal also allows executing protocols without the standard :ref:`interface <interface>`.

In the following tutorial we show how to run a single protocol using Qibocal as a library.
For this particular example we will focus on the `single shot classification protocol
<https://github.com/qiboteam/qibocal/blob/main/src/qibocal/protocols/characterization/classification.py>`_.

.. code-block:: python
from qibocal.protocols.characterization import Operation
from qibolab import create_platform
# allocate platform
platform = create_platform("....")
# get qubits from platform
qubits = platform.qubits
# we select the protocol
protocol = Operation.single_shot_classification.value
``protocol`` is a `Routine <https://qibo.science/qibocal/stable/api-reference/qibocal.auto.html#qibocal.auto.operation.Routine>`_ object which contains all the necessary
methods to execute the experiment.

In order to run a protocol the user needs to specify the parameters.
The user can check which parameters need to be provided either by checking the
documentation of the specific protocol or by simply inspecting ``protocol.parameters_type``.
For ``single_shot_classification`` we can pass just the number of shots
in the following way:

.. code-block:: python
parameters = experiment.parameters_type.load(dict(nshots=1024))
After defining the parameters, the user can perform the acquisition using
``experiment.acquisition`` which accepts the following parameters:

* params (`experiment.parameters_type <https://qibo.science/qibocal/latest/api-reference/qibocal.auto.html#qibocal.auto.operation.Routine.parameters_type>`_): input parameters for the experiment
* platform (`qibolab.platform.Platform <https://qibo.science/qibolab/latest/api-reference/qibolab.html#qibolab.platform.Platform>`_): Qibolab platform class
* qubits (dict[`QubitId <https://qibo.science/qibolab/latest/api-reference/qibolab.html#qibolab.qubits.QubitId>`_, `QubitPairId <https://qibo.science/qibolab/latest/api-reference/qibolab.html#qibolab.qubits.QubitPairId>`_]) dictionary with qubits where the acquisition will run

and returns the following:

* data (`experiment.data_type <https://qibo.science/qibocal/latest/api-reference/qibocal.auto.html#qibocal.auto.operation.Routine.data_type>`_): data acquired
* acquisition_time (float): acquisition time on hardware

.. code-block:: python
data, acquisition_time = experiment.acquisition(params=parameters,
platform=platform,
qubits=qubits)
The user can now use the raw data acquired by the quantum processor to perform
an arbitrary post-processing analysis. This is one of the main advantages of this API
compared to the cli execution.

The fitting corresponding to the experiment (``experiment.fit``) can be launched in the
following way:

.. code-block:: python
fit, fit_time = experiment.fit(data)
To be more specific the user should pass as input ``data`` which is of type
``experiment.data_type`` and the outputs are the following:

* fit: (`experiment.results_type <https://qibo.science/qibocal/latest/api-reference/qibocal.auto.html#qibocal.auto.operation.Routine.results_type>`_) input parameters for the experiment
* fit_time (float): post-processing time


It is also possible to access the plots and the tables generated in the
report using ``experiment.report`` which accepts the following parameters:

* data: (`experiment.data_type <https://qibo.science/qibocal/latest/api-reference/qibocal.auto.html#qibocal.auto.operation.Routine.data_type>`_) data structure used by ``experiment``
* qubit (dict[`QubitId <https://qibo.science/qibolab/latest/api-reference/qibolab.html#qibolab.qubits.QubitId>`_, `QubitPairId <https://qibo.science/qibolab/latest/api-reference/qibolab.html#qibolab.qubits.QubitPairId>`_]): qubit / qubit pair to be plotted
* fit: (`experiment.results_type <https://qibo.science/qibocal/latest/api-reference/qibocal.auto.html#qibocal.auto.operation.Routine.results_type>`_): data structure for post-processing used by ``experiment``

.. code-block:: python
# Plot for qubit 0
qubit = 0
figs, html_content = experiment.report(data=data, qubit=0, fit=fit)
``experiment.report`` returns the following:

* figs: list of plotly figures
* html_content: raw html with additional information usually in the form of a table

In our case we get the following figure for qubit 0:

.. code-block:: python
figs[0]
.. image:: classification_plot.png

and we can render the html content in the following way:

.. code-block:: python
import IPython
IPython.display.HTML(html_content)
.. image:: classification_table.png
Binary file added doc/source/tutorials/classification_plot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/tutorials/classification_table.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/source/tutorials/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ In this section we present code examples from basic to advanced features impleme
.. toctree::
:maxdepth: 2

api
protocol
16 changes: 13 additions & 3 deletions serverscripts/qibocal-index-reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,17 @@
"start-time": "-",
"end-time": "-",
"tag": "-",
"author": "-",
}
REQUIRED_FILE_METADATA = {
"title",
"date",
"platform",
"start-time",
"end-time",
"tag",
"author",
}
REQUIRED_FILE_METADATA = {"title", "date", "platform", "start-time" "end-time", "tag"}


def meta_from_path(p):
Expand All @@ -36,17 +45,18 @@ def meta_from_path(p):

def register(p):
path_meta = meta_from_path(p)
title, date, platform, start_time, end_time, tag = (
title, date, platform, start_time, end_time, tag, author = (
path_meta["title"],
path_meta["date"],
path_meta["platform"],
path_meta["start-time"],
path_meta["end-time"],
path_meta["tag"],
path_meta["author"],
)
url = ROOT_URL + p.name
titlelink = f'<a href="{url}">{title}</a>'
return (titlelink, date, platform, start_time, end_time, tag)
return (titlelink, date, platform, start_time, end_time, tag, author)


def make_index():
Expand Down
3 changes: 2 additions & 1 deletion serverscripts/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
var data = json['data'];
var global_table = $('#global').DataTable({
"data": data,
"order": [[2, "desc"]],
"order": [[1, "desc"]],
"dom": "frtipl"
});
});
Expand Down Expand Up @@ -55,6 +55,7 @@ <h2>Uploaded Reports</h2>
<th scope="col">Start-time (UTC)</th>
<th scope="col">End-time (UTC)</th>
<th scope="col">Tag</th>
<th scope="col">Author</th>
</tr>
</thead>
</table>
Expand Down
11 changes: 9 additions & 2 deletions src/qibocal/cli/_base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Adds global CLI options."""
import getpass
import pathlib

import click
Expand Down Expand Up @@ -126,11 +127,17 @@ def fit(folder: pathlib.Path, update):
type=str,
help="Optional tag.",
)
def upload(path, tag):
@click.option(
"--author",
default=getpass.getuser(),
type=str,
help="Default is UID username.",
)
def upload(path, tag, author):
"""Uploads output folder to server
Arguments:
- FOLDER: input folder.
"""
upload_report(path, tag)
upload_report(path, tag, author)
7 changes: 4 additions & 3 deletions src/qibocal/cli/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@
ROOT_URL = "http://login.qrccluster.com:9000/"


def upload_report(path: pathlib.Path, tag: str):
def upload_report(path: pathlib.Path, tag: str, author: str):
# load meta and update tag
meta = yaml.safe_load((path / META).read_text())
meta["author"] = author
if tag is not None:
meta = yaml.safe_load((path / META).read_text())
meta["tag"] = tag
(path / META).write_text(json.dumps(meta, indent=4))
(path / META).write_text(json.dumps(meta, indent=4))

# check the rsync command exists.
if not shutil.which("rsync"):
Expand Down
7 changes: 3 additions & 4 deletions src/qibocal/fitting/classifier/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ def generate_models(data, qubit, test_size=0.25):
- x_test: Test inputs.
- y_test: Test outputs.
"""
data0 = data.data[qubit, 0].tolist()
data1 = data.data[qubit, 1].tolist()
qubit_data = data.data[qubit]
return train_test_split(
np.array(np.concatenate((data0, data1))),
np.array([0] * len(data0) + [1] * len(data1)),
np.array(qubit_data[["i", "q"]].tolist())[:, :],
np.array(qubit_data[["state"]].tolist())[:, 0],
test_size=test_size,
random_state=0,
shuffle=True,
Expand Down
40 changes: 40 additions & 0 deletions src/qibocal/fitting/classifier/decision_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from sklearn.model_selection import GridSearchCV, RepeatedStratifiedKFold
from sklearn.tree import DecisionTreeClassifier

from . import scikit_utils


def constructor(hyperpars):
r"""Return the model class.
Args:
hyperparams: Model hyperparameters.
"""
return DecisionTreeClassifier().set_params(**hyperpars)


def hyperopt(x_train, y_train, _path):
r"""Perform an hyperparameter optimization and return the hyperparameters.
Args:
x_train: Training inputs.
y_train: Training outputs.
_path (path): Model save path.
Returns:
Dictionary with model's hyperparameters.
"""
clf = DecisionTreeClassifier()
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
space = {}
space["criterion"] = ["gini", "entropy", "log_loss"]
space["splitter"] = ["best", "random"]
search = GridSearchCV(clf, space, scoring="accuracy", n_jobs=-1, cv=cv)
_ = search.fit(x_train, y_train)

return search.best_params_


normalize = scikit_utils.scikit_normalize
dump = scikit_utils.scikit_dump
predict_from_file = scikit_utils.scikit_predict
7 changes: 6 additions & 1 deletion src/qibocal/fitting/classifier/qubit_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import numpy as np
import numpy.typing as npt
import skops.io as sio

from qibocal.protocols.characterization.utils import cumulative

Expand Down Expand Up @@ -37,13 +36,19 @@ def hyperopt(_x_train, _y_train, _path):

def dump(model, save_path: Path):
r"""Dumps the `model` in `save_path`"""
# relative import to reduce overhead when importing qibocal
import skops.io as sio

sio.dump(model, save_path.with_suffix(".skops"))


def predict_from_file(loading_path: Path, input: np.typing.NDArray):
r"""This function loads the model saved in `loading_path`
and returns the predictions of `input`.
"""
# relative import to reduce overhead when importing qibocal
import skops.io as sio

model = sio.load(loading_path, trusted=True)
return model.predict(input)

Expand Down
15 changes: 7 additions & 8 deletions src/qibocal/fitting/classifier/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"random_forest",
"rbf_svm",
"qblox_fit",
"decision_tree",
]

PRETTY_NAME = [
Expand All @@ -35,6 +36,7 @@
"Random Forest",
"RBF SVM",
"Qblox Fit",
"Decision Tree",
]


Expand Down Expand Up @@ -63,10 +65,10 @@ def pretty_name(classifier_name: str):


class Classifier:
r"""Classs to define the different classifiers used in the benchmarking.
r"""Class to define the different classifiers used in the benchmarking.
Args:
mod: Classsification model.
mod: Classification model.
base_dir (Path): Where to store the classification results.
"""
Expand Down Expand Up @@ -249,12 +251,9 @@ def train_qubit(
classifier = Classifier(mod, qubit_dir)
classifier.savedir.mkdir(exist_ok=True)
logging.info(f"Training quibt with classifier: {pretty_name(classifier.name)}")
if classifier.name not in cls_data.classifiers_hpars[qubit]:
hyperpars = classifier.hyperopt(
x_train, y_train.astype(np.int64), classifier.savedir
)
else:
hyperpars = cls_data.classifiers_hpars[qubit][classifier.name]
hyperpars = classifier.hyperopt(
x_train, y_train.astype(np.int64), classifier.savedir
)
hpars_list.append(hyperpars)
classifier.dump_hyper(hyperpars)
model = classifier.create_model(hyperpars)
Expand Down
2 changes: 1 addition & 1 deletion src/qibocal/fitting/classifier/scikit_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def scikit_normalize(constructor):

def scikit_dump(model, path: Path):
r"""Dumps scikit `model` in `path`"""
initial_type = [("float_input", FloatTensorType([1, 2]))]
initial_type = [("float_input", FloatTensorType([None, 2]))]
onx = to_onnx(model, initial_types=initial_type)
with open(path.with_suffix(".onnx"), "wb") as f:
f.write(onx.SerializeToString())
6 changes: 6 additions & 0 deletions src/qibocal/protocols/characterization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
)
from .qubit_spectroscopy import qubit_spectroscopy
from .qubit_spectroscopy_ef import qubit_spectroscopy_ef
from .qutrit_classification import qutrit_classification
from .rabi.amplitude import rabi_amplitude
from .rabi.amplitude_msr import rabi_amplitude_msr
from .rabi.ef import rabi_amplitude_ef
from .rabi.length import rabi_length
from .rabi.length_msr import rabi_length_msr
from .rabi.length_sequences import rabi_length_sequences
from .ramsey import ramsey
from .ramsey_msr import ramsey_msr
Expand Down Expand Up @@ -60,6 +63,8 @@ class Operation(Enum):
rabi_amplitude = rabi_amplitude
rabi_length = rabi_length
rabi_length_sequences = rabi_length_sequences
rabi_amplitude_msr = rabi_amplitude_msr
rabi_length_msr = rabi_length_msr
ramsey = ramsey
ramsey_msr = ramsey_msr
ramsey_sequences = ramsey_sequences
Expand Down Expand Up @@ -93,5 +98,6 @@ class Operation(Enum):
twpa_power = twpa_power
rabi_amplitude_ef = rabi_amplitude_ef
qubit_spectroscopy_ef = qubit_spectroscopy_ef
qutrit_classification = qutrit_classification
resonator_amplitude = resonator_amplitude
dispersive_shift_qutrit = dispersive_shift_qutrit
Loading

0 comments on commit 6901c6b

Please sign in to comment.