From 3eccf6459d2bcada6490fd19af9073115e1968ee Mon Sep 17 00:00:00 2001 From: andrea-pasquale Date: Wed, 26 Apr 2023 20:42:32 +0400 Subject: [PATCH 1/6] Add example with dummy routine --- src/qibocal/auto/operation.py | 20 +++- src/qibocal/auto/runcard.py | 4 +- src/qibocal/auto/task.py | 13 ++- src/qibocal/cli/auto_builder.py | 17 +++- src/qibocal/cli/builders.py | 20 ++-- .../protocols/characterization/__init__.py | 2 + .../protocols/characterization/rotation.py | 93 +++++++++++++++++++ .../web/templates/autocalibration.html | 8 +- 8 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 src/qibocal/protocols/characterization/rotation.py diff --git a/src/qibocal/auto/operation.py b/src/qibocal/auto/operation.py index 42c008c6f..c84c6b953 100644 --- a/src/qibocal/auto/operation.py +++ b/src/qibocal/auto/operation.py @@ -90,8 +90,14 @@ class Routine(Generic[_ParametersT, _DataT, _ResultsT]): """A wrapped calibration routine.""" acquisition: Callable[[_ParametersT], _DataT] - fit: Callable[[_DataT], _ResultsT] - report: Callable[[_DataT, _ResultsT], None] + fit: Callable[[_DataT], _ResultsT] = None + report: Callable[[_DataT, _ResultsT], None] = None + + def __post_init__(self): + if self.fit == None: + self.fit = _dummy_fit + if self.report == None: + self.report = _dummy_report @property def parameters_type(self): @@ -108,6 +114,14 @@ def data_type(self): def results_type(self): return inspect.signature(self.fit).return_annotation + @property + def platform_dependent(self): + return "platform" in inspect.signature(self.acquisition).parameters + + @property + def qubits_dependent(self): + return "qubits" in inspect.signature(self.acquisition).parameters + @dataclass class DummyPars(Parameters): @@ -133,7 +147,7 @@ def _dummy_fit(data: DummyData) -> DummyRes: def _dummy_report(data: DummyData, result: DummyRes): - return + return [], "" dummy_operation = Routine(_dummy_acquisition, _dummy_fit, _dummy_report) diff --git a/src/qibocal/auto/runcard.py b/src/qibocal/auto/runcard.py index 38a64ed9b..506be0075 100644 --- a/src/qibocal/auto/runcard.py +++ b/src/qibocal/auto/runcard.py @@ -56,6 +56,6 @@ def load(cls, card: Union[dict, Path]): ) return cls( actions=content["actions"], - qubits=content["qubits"], - format=content["format"], + qubits=content["qubits"] if "qubits" in content else [], + format=content["format"] if "format" in content else None, ) diff --git a/src/qibocal/auto/task.py b/src/qibocal/auto/task.py index ede3d6dc5..c6d44cfbd 100644 --- a/src/qibocal/auto/task.py +++ b/src/qibocal/auto/task.py @@ -1,4 +1,5 @@ """Action execution tracker.""" +import inspect import os from dataclasses import dataclass from pathlib import Path @@ -90,9 +91,15 @@ def run(self, folder: Path, platform: AbstractPlatform, qubits: Qubits) -> Resul path = self.datapath(folder) # TODO: fix data attributes - self._data: Data = operation.acquisition( - parameters, platform=platform, qubits=qubits - ) + + if operation.platform_dependent and operation.qubits_dependent: + self._data: Data = operation.acquisition( + parameters, platform=platform, qubits=qubits + ) + else: + self._data: Data = operation.acquisition( + parameters, + ) self._data.save(path) # TODO: data dump # path.write_text(yaml.dump(pydantic_encoder(self.data(base_dir)))) diff --git a/src/qibocal/cli/auto_builder.py b/src/qibocal/cli/auto_builder.py index efa902871..3b0c2d902 100644 --- a/src/qibocal/cli/auto_builder.py +++ b/src/qibocal/cli/auto_builder.py @@ -71,7 +71,7 @@ def routine_name(self, routine, iteration): name = routine.replace("_", " ").title() return f"{name} - {iteration}" - def plot(self, routine_name, iteration, qubit): + def single_qubit_plot(self, routine_name, iteration, qubit): node = self.history[(routine_name, iteration)] data = node.task.data figures, fitting_report = node.task.operation.report(data, node.res, qubit) @@ -85,3 +85,18 @@ def plot(self, routine_name, iteration, qubit): all_html = "".join(html_list) return all_html, fitting_report + + def plot(self, routine_name, iteration): + node = self.history[(routine_name, iteration)] + data = node.task.data + figures, fitting_report = node.task.operation.report(data) + with tempfile.NamedTemporaryFile(delete=False) as temp: + html_list = [] + for figure in figures: + figure.write_html(temp.name, include_plotlyjs=False, full_html=False) + temp.seek(0) + fightml = temp.read().decode("utf-8") + html_list.append(fightml) + + all_html = "".join(html_list) + return all_html, fitting_report diff --git a/src/qibocal/cli/builders.py b/src/qibocal/cli/builders.py index c1c297afb..fa031dca0 100644 --- a/src/qibocal/cli/builders.py +++ b/src/qibocal/cli/builders.py @@ -150,15 +150,19 @@ def __init__(self, runcard, folder=None, force=False): self.backend, self.platform = self._allocate_backend( backend_name, platform_name, path, platform_runcard ) - if self.platform is not None: - self.qubits = { - q: self.platform.qubits[q] - for q in self.runcard["qubits"] - if q in self.platform.qubits - } + if "qubits" in self.runcard: + if self.platform is not None: + self.qubits = { + q: self.platform.qubits[q] + for q in self.runcard["qubits"] + if q in self.platform.qubits + } + else: + self.qubits = self.runcard.get("qubits") else: - self.qubits = self.runcard.get("qubits") - self.format = self.runcard["format"] + self.qubits = [] + if "format" in runcard: + self.format = self.runcard["format"] # Saving runcard shutil.copy(runcard, f"{path}/runcard.yml") diff --git a/src/qibocal/protocols/characterization/__init__.py b/src/qibocal/protocols/characterization/__init__.py index 917e00624..47f7e9e61 100644 --- a/src/qibocal/protocols/characterization/__init__.py +++ b/src/qibocal/protocols/characterization/__init__.py @@ -14,6 +14,7 @@ from .resonator_punchout import resonator_punchout from .resonator_punchout_attenuation import resonator_punchout_attenuation from .resonator_spectroscopy import resonator_spectroscopy +from .rotation import rotation from .spin_echo import spin_echo from .t1 import t1 @@ -35,3 +36,4 @@ class Operation(Enum): allxy_drag_pulse_tuning = allxy_drag_pulse_tuning drag_pulse_tuning = drag_pulse_tuning flipping = flipping + rotation = rotation diff --git a/src/qibocal/protocols/characterization/rotation.py b/src/qibocal/protocols/characterization/rotation.py new file mode 100644 index 000000000..eea37f6b7 --- /dev/null +++ b/src/qibocal/protocols/characterization/rotation.py @@ -0,0 +1,93 @@ +from dataclasses import dataclass +from typing import List + +import numpy as np +import plotly.graph_objects as go +from qibo import gates, models, set_backend + +from qibocal.auto.operation import Data, Parameters, Results, Routine + + +@dataclass +class RotationParameters(Parameters): + nshots: int + theta_start: float + theta_end: float + samples: float + + +@dataclass +class RotationData(Data): + thetas: List[float] + probabilities: List[float] + simulated_probabilities: List[float] + + def save(self, path): + np.savetxt( + path / "test.txt", + np.array([self.thetas, self.probabilities, self.simulated_probabilities]), + ) + + +@dataclass +class RotationResults(Results): + """""" + + +def _acquisition(params: RotationParameters) -> RotationData: + thetas = np.linspace(params.theta_start, params.theta_end, params.samples) + probabilities = [] + simulated_probabilities = [] + + for theta in thetas: + circuit = models.Circuit(1) + circuit.add(gates.RX(0, theta)) + circuit.add(gates.M(0)) + result = circuit(nshots=params.nshots) + prob = result.probabilities()[0] + probabilities.append(prob) + + set_backend("numpy") + + for theta in thetas: + circuit = models.Circuit(1) + circuit.add(gates.RX(0, theta)) + circuit.add(gates.M(0)) + result = circuit(nshots=params.nshots) + prob = result.probabilities()[0] + simulated_probabilities.append(prob) + + return RotationData(thetas, probabilities, simulated_probabilities) + + +def _plot(data): + figure = go.Figure() + + figure.add_trace( + go.Scatter( + x=data.thetas, + y=data.simulated_probabilities, + name="Simulation", + ), + ) + + figure.add_trace( + go.Scatter( + x=data.thetas, + y=data.probabilities, + name="QPU", + mode="markers", + ), + ) + + figure.update_layout( + showlegend=True, + uirevision="0", # ``uirevision`` allows zooming while live plotting + xaxis_title=r"Rotation angle (rad)", + yaxis_title="Ground State Probability", + ) + + return [figure], "" + + +rotation = Routine(_acquisition, None, _plot) diff --git a/src/qibocal/web/templates/autocalibration.html b/src/qibocal/web/templates/autocalibration.html index 83404bcc0..bb7ddf55f 100644 --- a/src/qibocal/web/templates/autocalibration.html +++ b/src/qibocal/web/templates/autocalibration.html @@ -151,7 +151,7 @@

{{ report.title }}

{{ report.routine_name(routine, iteration) }}

- + {% if report.qubits != [] %} {% for qubit in report.qubits %}
{{ report.title }}
{{ header }} - Qubit {{ qubit }} - {% set figures, fitting_report = report.plot(routine, iteration, qubit) %} + {% set figures, fitting_report = report.single_qubit_plot(routine, iteration, qubit) %} {% if "No fitting data" not in fitting_report %} {% set fitting_params= fitting_report.split('
') %}
@@ -191,6 +191,10 @@

{{ report.title }}

{% endfor %} + {% else %} + {% set figures, fitting_report = report.plot(routine, iteration) %} + {{ figures }} + {% endif %}
{% endfor %} From 5ee0ab62a7b2bc0247983a89176ac8441181ce20 Mon Sep 17 00:00:00 2001 From: andrea-pasquale Date: Wed, 26 Apr 2023 20:45:38 +0400 Subject: [PATCH 2/6] Add comment --- src/qibocal/auto/operation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibocal/auto/operation.py b/src/qibocal/auto/operation.py index c84c6b953..95689f28d 100644 --- a/src/qibocal/auto/operation.py +++ b/src/qibocal/auto/operation.py @@ -94,6 +94,7 @@ class Routine(Generic[_ParametersT, _DataT, _ResultsT]): report: Callable[[_DataT, _ResultsT], None] = None def __post_init__(self): + # TODO: this could be improved if self.fit == None: self.fit = _dummy_fit if self.report == None: @@ -102,7 +103,6 @@ def __post_init__(self): @property def parameters_type(self): sig = inspect.signature(self.acquisition) - # we are assuming that params is the last argument, maybe should be the first(?) param = list(sig.parameters.values())[0] return param.annotation From 9a0ab1d1d26f897db9947026e047062fc90bbb83 Mon Sep 17 00:00:00 2001 From: andrea-pasquale Date: Wed, 26 Apr 2023 20:48:22 +0400 Subject: [PATCH 3/6] Add runcard --- runcards/test_rotation.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 runcards/test_rotation.yml diff --git a/runcards/test_rotation.yml b/runcards/test_rotation.yml new file mode 100644 index 000000000..4fc3187a6 --- /dev/null +++ b/runcards/test_rotation.yml @@ -0,0 +1,12 @@ +platform: tii1q_b1 #qw5q_gold_qblox + +actions: + + - id: rotation experiment + priority: 0 + operation: rotation + parameters: + nshots: 1024 + theta_start: 0 + theta_end: 6 + samples: 50 From 11dab13ab7bed62191bb3b3d78a49dbd3f3ff9ef Mon Sep 17 00:00:00 2001 From: andrea-pasquale Date: Wed, 26 Apr 2023 20:53:00 +0400 Subject: [PATCH 4/6] Removing dummy routine --- runcards/test_rotation.yml | 12 --- src/qibocal/auto/operation.py | 1 + src/qibocal/auto/task.py | 1 - .../protocols/characterization/__init__.py | 2 - .../protocols/characterization/rotation.py | 93 ------------------- 5 files changed, 1 insertion(+), 108 deletions(-) delete mode 100644 runcards/test_rotation.yml delete mode 100644 src/qibocal/protocols/characterization/rotation.py diff --git a/runcards/test_rotation.yml b/runcards/test_rotation.yml deleted file mode 100644 index 4fc3187a6..000000000 --- a/runcards/test_rotation.yml +++ /dev/null @@ -1,12 +0,0 @@ -platform: tii1q_b1 #qw5q_gold_qblox - -actions: - - - id: rotation experiment - priority: 0 - operation: rotation - parameters: - nshots: 1024 - theta_start: 0 - theta_end: 6 - samples: 50 diff --git a/src/qibocal/auto/operation.py b/src/qibocal/auto/operation.py index 95689f28d..3d8862f87 100644 --- a/src/qibocal/auto/operation.py +++ b/src/qibocal/auto/operation.py @@ -114,6 +114,7 @@ def data_type(self): def results_type(self): return inspect.signature(self.fit).return_annotation + # TODO: I don't like these properties but it seems to work @property def platform_dependent(self): return "platform" in inspect.signature(self.acquisition).parameters diff --git a/src/qibocal/auto/task.py b/src/qibocal/auto/task.py index c6d44cfbd..b160d735b 100644 --- a/src/qibocal/auto/task.py +++ b/src/qibocal/auto/task.py @@ -90,7 +90,6 @@ def run(self, folder: Path, platform: AbstractPlatform, qubits: Qubits) -> Resul parameters = DummyPars() path = self.datapath(folder) - # TODO: fix data attributes if operation.platform_dependent and operation.qubits_dependent: self._data: Data = operation.acquisition( diff --git a/src/qibocal/protocols/characterization/__init__.py b/src/qibocal/protocols/characterization/__init__.py index 47f7e9e61..917e00624 100644 --- a/src/qibocal/protocols/characterization/__init__.py +++ b/src/qibocal/protocols/characterization/__init__.py @@ -14,7 +14,6 @@ from .resonator_punchout import resonator_punchout from .resonator_punchout_attenuation import resonator_punchout_attenuation from .resonator_spectroscopy import resonator_spectroscopy -from .rotation import rotation from .spin_echo import spin_echo from .t1 import t1 @@ -36,4 +35,3 @@ class Operation(Enum): allxy_drag_pulse_tuning = allxy_drag_pulse_tuning drag_pulse_tuning = drag_pulse_tuning flipping = flipping - rotation = rotation diff --git a/src/qibocal/protocols/characterization/rotation.py b/src/qibocal/protocols/characterization/rotation.py deleted file mode 100644 index eea37f6b7..000000000 --- a/src/qibocal/protocols/characterization/rotation.py +++ /dev/null @@ -1,93 +0,0 @@ -from dataclasses import dataclass -from typing import List - -import numpy as np -import plotly.graph_objects as go -from qibo import gates, models, set_backend - -from qibocal.auto.operation import Data, Parameters, Results, Routine - - -@dataclass -class RotationParameters(Parameters): - nshots: int - theta_start: float - theta_end: float - samples: float - - -@dataclass -class RotationData(Data): - thetas: List[float] - probabilities: List[float] - simulated_probabilities: List[float] - - def save(self, path): - np.savetxt( - path / "test.txt", - np.array([self.thetas, self.probabilities, self.simulated_probabilities]), - ) - - -@dataclass -class RotationResults(Results): - """""" - - -def _acquisition(params: RotationParameters) -> RotationData: - thetas = np.linspace(params.theta_start, params.theta_end, params.samples) - probabilities = [] - simulated_probabilities = [] - - for theta in thetas: - circuit = models.Circuit(1) - circuit.add(gates.RX(0, theta)) - circuit.add(gates.M(0)) - result = circuit(nshots=params.nshots) - prob = result.probabilities()[0] - probabilities.append(prob) - - set_backend("numpy") - - for theta in thetas: - circuit = models.Circuit(1) - circuit.add(gates.RX(0, theta)) - circuit.add(gates.M(0)) - result = circuit(nshots=params.nshots) - prob = result.probabilities()[0] - simulated_probabilities.append(prob) - - return RotationData(thetas, probabilities, simulated_probabilities) - - -def _plot(data): - figure = go.Figure() - - figure.add_trace( - go.Scatter( - x=data.thetas, - y=data.simulated_probabilities, - name="Simulation", - ), - ) - - figure.add_trace( - go.Scatter( - x=data.thetas, - y=data.probabilities, - name="QPU", - mode="markers", - ), - ) - - figure.update_layout( - showlegend=True, - uirevision="0", # ``uirevision`` allows zooming while live plotting - xaxis_title=r"Rotation angle (rad)", - yaxis_title="Ground State Probability", - ) - - return [figure], "" - - -rotation = Routine(_acquisition, None, _plot) From b6b6ebaee50164f0287377a2f1872027bf974275 Mon Sep 17 00:00:00 2001 From: andrea-pasquale Date: Wed, 26 Apr 2023 21:11:46 +0400 Subject: [PATCH 5/6] Enable simulation --- src/qibocal/auto/execute.py | 3 ++- src/qibocal/cli/auto_builder.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/qibocal/auto/execute.py b/src/qibocal/auto/execute.py index b74bd60fc..1359c4f61 100644 --- a/src/qibocal/auto/execute.py +++ b/src/qibocal/auto/execute.py @@ -129,4 +129,5 @@ def run(self): completed = Completed(task, output, Normal()) self.history.push(completed) self.head = self.next() - self.platform.update(completed.res.update) + if self.platform is not None: + self.platform.update(completed.res.update) diff --git a/src/qibocal/cli/auto_builder.py b/src/qibocal/cli/auto_builder.py index 3b0c2d902..4fa80a38c 100644 --- a/src/qibocal/cli/auto_builder.py +++ b/src/qibocal/cli/auto_builder.py @@ -52,7 +52,8 @@ def dump_report(self): create_autocalibration_report(self.folder, self.executor.history) def dump_platform_runcard(self): - self.platform.dump(self.folder / UPDATED_PLATFORM) + if self.platform is not None: + self.platform.dump(self.folder / UPDATED_PLATFORM) class AutoCalibrationReportBuilder: From 87a4f67a0a1c55bce609a85d54968da06b9a5b6c Mon Sep 17 00:00:00 2001 From: andrea-pasquale Date: Fri, 28 Apr 2023 20:22:58 +0400 Subject: [PATCH 6/6] Remove unsued imports --- src/qibocal/auto/operation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qibocal/auto/operation.py b/src/qibocal/auto/operation.py index 3d8862f87..7718d8b0d 100644 --- a/src/qibocal/auto/operation.py +++ b/src/qibocal/auto/operation.py @@ -1,8 +1,8 @@ import inspect -from dataclasses import dataclass, field, fields -from typing import Callable, Dict, Generic, NewType, Optional, TypeVar, Union +from dataclasses import dataclass, fields +from typing import Callable, Dict, Generic, NewType, TypeVar, Union -from qibolab.platforms.abstract import AbstractPlatform, Qubit +from qibolab.platforms.abstract import Qubit OperationId = NewType("OperationId", str) """Identifier for a calibration routine."""