Skip to content
Open
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
33 changes: 27 additions & 6 deletions src/qibocal/auto/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,37 @@ def run_protocol(
protocol: Protocol,
parameters: Action,
mode: ExecutionMode = AUTOCALIBRATION,
output: Path | None = None,
) -> Completed:
"""Run single protocol in ExecutionMode mode."""
"""Run a calibration protocol and record the completed task.

The executor preserves the execution history and chooses the platform
instance used for the task based on the requested mode. If acquisition is
requested, the current live platform is used. If only fitting or analysis
is required, the platform is reconstructed from the output folder so that
the exact experiment configuration is reused.
If the mode contains :class:`ExecutionMode.FIT`, and the action is
configured to update the platform, it is updated using the fitted parameters.
"""

output = self.path

task = Task(action=parameters, operation=protocol)
log.info(f"Executing mode {mode} on {task.action.id}.")
completed = task.run(
platform=self.platform,
platform=(
# if I need to acquire data I need to create from scratch
# the platform with all its hardware configurations;
# when executor is just fitting I need to create the exact same
# platform of the experiment (saved in the experiment folder), and here
# the hardware configuration is unnecessary.
self.platform
if ExecutionMode.ACQUIRE in mode
else CalibrationPlatform.from_datafolder(
folder_path=output,
platform_name=self.platform.name,
dummy_hardware=True,
)
),
targets=self.targets,
mode=mode,
folder=self.history.task_path(
Expand Down Expand Up @@ -168,9 +191,7 @@ def wrapper(
"parameters": params | positional | kwargs,
}
)
return self.run_protocol(
protocol, parameters=action, mode=mode, output=self.path
)
return self.run_protocol(protocol, parameters=action, mode=mode)

return wrapper

Expand Down
4 changes: 1 addition & 3 deletions src/qibocal/auto/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,12 @@ def push(self, completed: Completed) -> TaskId:
self._order.append(task_id)
return task_id

def task_path(self, task_id: TaskId, folder: Path | None) -> Path | None:
def task_path(self, task_id: TaskId, folder: Path) -> Path:
"""Determine the path related to a completed task given TaskId.

`folder` should be usually the general output folder, used by Qibocal to store
all the execution results. Cf. :class:`qibocal.auto.output.Output`.
"""
if folder is None:
return None
return folder / "data" / f"{task_id}"

def dump(self, output: Path):
Expand Down
37 changes: 31 additions & 6 deletions src/qibocal/auto/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,37 @@ def process(
update: bool = True,
force: bool = False,
):
"""Process existing output."""
backend = construct_backend(
backend=self.meta.backend, platform=self.meta.platform
)
assert backend.platform is not None
self.platform = CalibrationPlatform.from_platform(backend.platform)
"""Process an existing output directory.

Reconstruct the calibration platform from the live backend during
acquisition or from the saved datafolder during fitting, then rerun each
completed task using the requested execution mode and output folder.
If ``update`` is enabled and the task supports platform updates, the
platform is refreshed accordingly. When ``force`` is ``False``, tasks
that already contain fitting results raise an error to avoid
overwriting them.
"""
# NOTE: this function in principle takes also ``ExecutionMode.ACQUIRE`` as ``mode``
# value, but if we want this function to be used only for offline post-processing
# this input is completely unnecessary (we cannot acquire offline), so we might
# think of removing it.

# during acquisition we need the information of the hardware
if ExecutionMode.ACQUIRE in mode:
backend = construct_backend(
backend=self.meta.backend, platform=self.meta.platform
)
assert backend.platform is not None
self.platform = CalibrationPlatform.from_platform(backend.platform)
else:
# while performing a fitting task we do not need hardware information
# but also we need the params saved in the datafolder, since in the
# platform folder might been changed.
Comment on lines +258 to +259
self.platform = CalibrationPlatform.from_datafolder(
folder_path=output / "platform",
platform_name=self.meta.platform,
dummy_hardware=True,
)

for task_id, completed in self.history.items():
# TODO: should we drop this check as well, and just allow overwriting?
Expand Down
1 change: 0 additions & 1 deletion src/qibocal/auto/runcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ def run(
protocol=getattr(protocols, action.operation),
parameters=action,
mode=mode,
output=output,
)
instance.history.dump(output)
return instance.history
16 changes: 8 additions & 8 deletions src/qibocal/auto/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ def update(self):

def run(
self,
mode: ExecutionMode,
folder: Path,
platform: Platform | None = None,
targets: Targets | None = None,
mode: ExecutionMode | None = None,
folder: Path | None = None,
) -> "Completed":
if self.targets is None:
self.action.targets = targets
Expand All @@ -158,7 +158,6 @@ def run(
except (RuntimeError, AttributeError):
operation = dummy_operation
parameters = DummyPars()

completed.dump_parameters()

if ExecutionMode.ACQUIRE in mode:
Expand All @@ -174,6 +173,8 @@ def run(
)
completed.dump_data()
if ExecutionMode.FIT in mode:
if completed.data is None:
raise ValueError("Experiment folder does not contain data to fit.")
completed.results, completed.results_time = operation.fit(completed.data)
completed.dump_results()
return completed
Expand All @@ -191,7 +192,7 @@ class Completed:
once tasks will be immutable, a separate `iteration` attribute should
be added
"""
path: Path | None = None
path: Path
"""Folder contaning data and results files for task."""
_data: Data | None = None
"""Protocol data."""
Expand Down Expand Up @@ -231,17 +232,16 @@ def results(self, value):

def dump_parameters(self):
"""Dump parameters."""
if self.path is not None:
self.task.dump(self.path)
self.task.dump(self.path)

def dump_data(self):
"""Dumping data."""
if self.path is not None:
if self._data is not None:
self._data.save(self.path)

def dump_results(self):
"""Dumping results."""
if self.path is not None:
if self._results is not None:
self._results.save(self.path)

@classmethod
Expand Down
42 changes: 40 additions & 2 deletions src/qibocal/calibration/platform.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
from dataclasses import dataclass
from pathlib import Path

from qibolab import Platform, create_platform, locate_platform
from qibolab import Parameters, Platform, create_platform, locate_platform
from qibolab.platform import create_dummy

from .calibration import CALIBRATION, Calibration

__all__ = ["CalibrationPlatform", "create_calibration_platform"]


PARAMETERS = "parameters.json"
"""File containing information about platform parameters."""


class CalibrationError(Exception):
def __init__(self, *args):
super().__init__(*args)
Expand All @@ -17,7 +22,7 @@ def __init__(self, *args):
class CalibrationPlatform(Platform):
"""Qibolab platform with calibration information."""

calibration: Calibration = None
calibration: Calibration | None = None
"""Calibration information."""

def __post_init__(self):
Expand Down Expand Up @@ -64,11 +69,44 @@ def from_platform(cls, platform: Platform):
# TODO: this is loading twice a platform
return cls(**vars(platform), calibration=calibration)

@classmethod
def from_datafolder(
cls, folder_path: Path, platform_name: str, dummy_hardware: bool
):
"""Create a calibration platform from a serialized data folder.

The platform is rebuilt from the configuration saved in the experiment history,
using the ``parameters.json`` and ``calibration.json`` files stored in the data folder
rather than the platform in ``QIBOLAB_PLATFORMS``.
A real platform or a dummy platform is created according to
``dummy_hardware``, then populated with the data in ``folder_path``.
"""

parameters = Parameters.model_validate_json(
(folder_path / PARAMETERS).read_text()
)

calibration = Calibration.model_validate_json(
(folder_path / CALIBRATION).read_text()
)

platform = create_dummy() if dummy_hardware else create_platform(platform_name)
platform.parameters = parameters
platform.name = platform_name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to overwrite the name?

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.

That action is only effective when platform is a DummyPlatform, I've decided to overwrite to propagate the name even for dummy platforms, maybe you need later in the code and I didn't want to create confusion by printing dummy (or whatever name DummyPlatform has).


return cls(
calibration=calibration,
**vars(platform),
)

def dump(self, path: Path):
super().dump(path)
self.calibration.dump(path)


def create_calibration_platform(name: str) -> CalibrationPlatform:
"""This function builds a ``CalibrationPlatform`` object which is sentitive of the hardware,
so it needs information about the clusters and its connection. Has to be used for acquisition.
"""
Comment on lines +108 to +110
platform = create_platform(name)
return CalibrationPlatform.from_platform(platform)
8 changes: 7 additions & 1 deletion src/qibocal/cli/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,21 @@ def update(path: pathlib.Path, skip_qubits: list[QubitId] | None):
platform_name = json.loads((path / META).read_text())["platform"]

platform_path = locate_platform(platform_name)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(qpu165) roy.stegeman@dalma:~/calibration$ qq update /home/users/roy.stegeman/calibration/results/tuna5/260804/17-19-51-cryoscope-0
Traceback (most recent call last):
  File "/nfs/users/roy.stegeman/.venvs/qpu165/bin/qq", line 8, in <module>
    sys.exit(command())
  File "/nfs/users/roy.stegeman/.venvs/qpu165/lib/python3.10/site-packages/click/core.py", line 1514, in __call__
    return self.main(*args, **kwargs)
  File "/nfs/users/roy.stegeman/.venvs/qpu165/lib/python3.10/site-packages/click/core.py", line 1435, in main
    rv = self.invoke(ctx)
  File "/nfs/users/roy.stegeman/.venvs/qpu165/lib/python3.10/site-packages/click/core.py", line 1902, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/nfs/users/roy.stegeman/.venvs/qpu165/lib/python3.10/site-packages/click/core.py", line 1298, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/nfs/users/roy.stegeman/.venvs/qpu165/lib/python3.10/site-packages/click/core.py", line 853, in invoke
    return callback(*args, **kwargs)
  File "/nfs/users/roy.stegeman/github/qibocal/src/qibocal/cli/_base.py", line 145, in update
    updating(folder, skip_qubits)
  File "/nfs/users/roy.stegeman/github/qibocal/src/qibocal/cli/update.py", line 27, in update
    platform_path = locate_platform(platform_name)
  File "/nfs/users/roy.stegeman/github/qibolab/src/qibolab/_core/platform/load.py", line 81, in locate_platform
    return _search(name, paths)
  File "/nfs/users/roy.stegeman/github/qibolab/src/qibolab/_core/platform/load.py", line 56, in _search
    raise ValueError(
ValueError: Platform tuna5 not found. Check $QIBOLAB_PLATFORMS environment variable.

Sine no path is passed to locate_platform it will _search in the default _platforms_paths(), which is still
PLATFORMS_PATH = "QIBOLAB_PLATFORMS"

# we define here the old platform, before editing the files
old_platform = create_calibration_platform(platform_name)
# copying the results files in the platform folder
for filename in os.listdir(new_platform_path):
shutil.copy(
new_platform_path / filename,
platform_path / filename,
)

if skip_qubits is not None:
new_platform = create_calibration_platform(platform_name)
new_platform = CalibrationPlatform.from_datafolder(
folder_path=new_platform_path,
platform_name=platform_name,
dummy_hardware=False,
)
updated_platform = merge_with_skipped_qubits(
old_platform, new_platform, skip_qubits
)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def mock_output(tmp_path: Path, platform: CalibrationPlatform) -> tuple[Output,
path=tmp_path,
meta=meta,
)
executor.run_protocol(flipping, ACTION, mode=ExecutionMode.ACQUIRE, output=tmp_path)
executor.run_protocol(flipping, ACTION, mode=ExecutionMode.ACQUIRE)
meta.end()
platform.disconnect()
output.history = executor.history
Expand Down
Loading