Skip to content

Commit

Permalink
Merge pull request #949 from qiboteam/exec-ctx-manager
Browse files Browse the repository at this point in the history
Executor context manager
  • Loading branch information
alecandido committed Aug 13, 2024
2 parents b1079b1 + a3d3809 commit d5c0f2d
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 71 deletions.
124 changes: 61 additions & 63 deletions runcards/rx_calibration.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,75 @@
from qibocal.auto.execute import Executor
from qibocal.cli.report import report

executor = Executor.create(name="myexec", platform="dummy")

from myexec import close, drag_tuning, init, rabi_amplitude, ramsey

target = 0
platform = executor.platform
platform.settings.nshots = 2048
init("test_rx_calibration", force=True, targets=[target])
with Executor.open(
"myexec",
path="test_rx_calibration",
platform="dummy",
targets=[target],
update=True,
force=True,
) as e:
e.platform.settings.nshots = 2000

rabi_output = rabi_amplitude(
min_amp_factor=0.5,
max_amp_factor=1.5,
step_amp_factor=0.01,
pulse_length=platform.qubits[target].native_gates.RX.duration,
)
# update only if chi2 is satisfied
if rabi_output.results.chi2[target][0] > 2:
raise RuntimeError(
f"Rabi fit has chi2 {rabi_output.results.chi2[target][0]} greater than 2. Stopping."
rabi_output = e.rabi_amplitude(
min_amp_factor=0.5,
max_amp_factor=1.5,
step_amp_factor=0.01,
pulse_length=e.platform.qubits[target].native_gates.RX.duration,
)
rabi_output.update_platform(platform)
# update only if chi2 is satisfied
if rabi_output.results.chi2[target][0] > 2:
raise RuntimeError(
f"Rabi fit has chi2 {rabi_output.results.chi2[target][0]} greater than 2. Stopping."
)

ramsey_output = ramsey(
delay_between_pulses_start=10,
delay_between_pulses_end=5000,
delay_between_pulses_step=100,
detuning=1_000_000,
)
if ramsey_output.results.chi2[target][0] > 2:
raise RuntimeError(
f"Ramsey fit has chi2 {ramsey_output.results.chi2[target][0]} greater than 2. Stopping."
ramsey_output = e.ramsey(
delay_between_pulses_start=10,
delay_between_pulses_end=5000,
delay_between_pulses_step=100,
detuning=1_000_000,
update=False,
)
if ramsey_output.results.delta_phys[target][0] < 1e4:
print(
f"Ramsey frequency not updated, correction too small { ramsey_output.results.delta_phys[target][0]}"
)
else:
ramsey_output.update_platform(platform)
if ramsey_output.results.chi2[target][0] > 2:
raise RuntimeError(
f"Ramsey fit has chi2 {ramsey_output.results.chi2[target][0]} greater than 2. Stopping."
)
if ramsey_output.results.delta_phys[target][0] < 1e4:
print(
f"Ramsey frequency not updated, correction too small { ramsey_output.results.delta_phys[target][0]}"
)
else:
ramsey_output.update_platform(e.platform)

rabi_output_2 = rabi_amplitude(
min_amp_factor=0.5,
max_amp_factor=1.5,
step_amp_factor=0.01,
pulse_length=platform.qubits[target].native_gates.RX.duration,
)
# update only if chi2 is satisfied
if rabi_output_2.results.chi2[target][0] > 2:
raise RuntimeError(
f"Rabi fit has chi2 {rabi_output_2.results.chi2[target][0]} greater than 2. Stopping."
rabi_output_2 = e.rabi_amplitude(
min_amp_factor=0.5,
max_amp_factor=1.5,
step_amp_factor=0.01,
pulse_length=e.platform.qubits[target].native_gates.RX.duration,
)
rabi_output_2.update_platform(platform)
# update only if chi2 is satisfied
if rabi_output_2.results.chi2[target][0] > 2:
raise RuntimeError(
f"Rabi fit has chi2 {rabi_output_2.results.chi2[target][0]} greater than 2. Stopping."
)

drag_output = drag_tuning(beta_start=-4, beta_end=4, beta_step=0.5)
if drag_output.results.chi2[target][0] > 2:
raise RuntimeError(
f"Drag fit has chi2 {drag_output.results.chi2[target][0]} greater than 2. Stopping."
)
drag_output.update_platform(platform)
drag_output = e.drag_tuning(beta_start=-4, beta_end=4, beta_step=0.5)
if drag_output.results.chi2[target][0] > 2:
raise RuntimeError(
f"Drag fit has chi2 {drag_output.results.chi2[target][0]} greater than 2. Stopping."
)

rabi_output_3 = rabi_amplitude(
min_amp_factor=0.5,
max_amp_factor=1.5,
step_amp_factor=0.01,
pulse_length=platform.qubits[target].native_gates.RX.duration,
)
# update only if chi2 is satisfied
if rabi_output_3.results.chi2[target][0] > 2:
raise RuntimeError(
f"Rabi fit has chi2 {rabi_output_3.results.chi2[target][0]} greater than 2. Stopping."
rabi_output_3 = e.rabi_amplitude(
min_amp_factor=0.5,
max_amp_factor=1.5,
step_amp_factor=0.01,
pulse_length=e.platform.qubits[target].native_gates.RX.duration,
)
rabi_output_3.update_platform(platform)
# update only if chi2 is satisfied
if rabi_output_3.results.chi2[target][0] > 2:
raise RuntimeError(
f"Rabi fit has chi2 {rabi_output_3.results.chi2[target][0]} greater than 2. Stopping."
)

close()
report(executor.path, executor.history)
report(e.path, e.history)
31 changes: 24 additions & 7 deletions runcards/single_shot.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
from qibocal.auto.execute import Executor
from qibocal.cli.report import report
"""Minimal Qibocal script example.
In this example, the default Qibocal executor is used. Additional
configurations can still be passed through the `init()` call, but there
is no explicit API to access the execution details.
If more fine grained control is needed, refer to the `rx_calibration.py` example.
executor = Executor.create(name="myexec", platform="dummy")
.. note::
from myexec import close, init, single_shot_classification
though simple, this example is not limited to single protocol execution, but
multiple protocols can be added as well, essentially in the same fashion of a plain
runcard - still with the advantage of handling execution and results
programmatically
"""

from pathlib import Path

from qibocal.cli.report import report
from qibocal.routines import close, init, single_shot_classification

init("test_x", force=True)
path = Path("test_x")
init(platform="dummy", path=path, force=True)

completed = single_shot_classification(nshots=1000)
# Here
ssc = single_shot_classification(nshots=1000)
print("\nfidelities:\n", ssc.results.fidelity, "\n")

close()
report(executor.path, executor.history)
report(path)
49 changes: 48 additions & 1 deletion src/qibocal/auto/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import importlib.util
import os
import sys
from contextlib import contextmanager
from copy import deepcopy
from dataclasses import dataclass, fields
from pathlib import Path
Expand Down Expand Up @@ -205,6 +206,8 @@ def wrapper(
parameters: Optional[dict] = None,
id: str = operation,
mode: ExecutionMode = AUTOCALIBRATION,
update: bool = True,
targets: Optional[Targets] = None,
**kwargs,
):
positional = dict(
Expand All @@ -215,6 +218,8 @@ def wrapper(
source={
"id": id,
"operation": operation,
"targets": targets,
"update": update,
"parameters": params | positional | kwargs,
}
)
Expand Down Expand Up @@ -248,6 +253,7 @@ def init(
path: os.PathLike,
force: bool = False,
platform: Union[Platform, str, None] = None,
update: Optional[bool] = None,
targets: Optional[Targets] = None,
):
"""Initialize execution."""
Expand All @@ -258,6 +264,8 @@ def init(
platform = self.platform = backend.platform
assert isinstance(platform, Platform)

if update is not None:
self.update = update
if targets is not None:
self.targets = targets

Expand Down Expand Up @@ -290,8 +298,47 @@ def close(self, path: Optional[os.PathLike] = None):
self.meta.end()

# dump history, metadata, and updated platform
output = Output(self.history, self.meta)
output = Output(self.history, self.meta, self.platform)
output.dump(path)

# attempt unloading
self.__del__()

@classmethod
@contextmanager
def open(
cls,
name: str,
path: os.PathLike,
force: bool = False,
platform: Union[Platform, str, None] = None,
update: Optional[bool] = None,
targets: Optional[Targets] = None,
):
"""Enter the execution context."""
ex = cls.create(name, platform)
ex.init(path, force, platform, update, targets)
try:
yield ex
finally:
ex.close()

def __enter__(self):
"""Reenter the execution context.
This method its here to reuse an already existing (and
initialized) executor, in a new context.
It should not be used with new executors. In which case, cf. :meth:`__open__`.
"""
# connect and initialize platform
self.platform.connect()
return self

def __exit__(self, exc_type, exc_value, traceback):
"""Exit execution context.
This pairs with :meth:`__enter__`.
"""
self.close()
return False
22 changes: 22 additions & 0 deletions tests/test_executor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections.abc import Callable
from copy import deepcopy
from dataclasses import dataclass
from importlib import reload
Expand Down Expand Up @@ -183,3 +184,24 @@ def test_default_executor(tmp_path: Path, fake_platform: str, monkeypatch):
path = tmp_path / "my-default-exec-folder"
qibocal.routines.init(path, platform=fake_platform)
assert qibocal.DEFAULT_EXECUTOR.platform.name == 42


def test_context_manager(tmp_path: Path, executor: Executor):
path = tmp_path / "my-ctx-folder"

executor.init(path)

with executor:
assert executor.meta is not None
assert executor.meta.start is not None


def test_open(tmp_path: Path):
path = tmp_path / "my-open-folder"

with Executor.open("myexec", path) as e:
assert isinstance(e.t1, Callable)
assert e.meta is not None
assert e.meta.start is not None

assert e.meta.end is not None

0 comments on commit d5c0f2d

Please sign in to comment.