Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose single protocol run to executor #865

Merged
merged 15 commits into from
Jun 6, 2024
Merged

Conversation

andrea-pasquale
Copy link
Contributor

@andrea-pasquale andrea-pasquale commented May 21, 2024

Adds possibility to run one protocol at a time using Executor.
Possible script:

from qibocal.auto.execute import Executor
from qibocal.auto.mode import ExecutionMode
from qibocal.auto.runcard import Action
from qibolab import create_platform
import pathlib

protocol = {"id": "flipping",
            "operation": "flipping",
            "targets": [0,1,2],
            "parameters": {
                "nflips_max": 20,
                "nflips_step": 2,
                "detuning": +0.1,
            }}

action = Action(**protocol)
executor = Executor([action], platform=create_platform("dummy"), output=pathlib.Path("FOLDER"))

completed = executor.run_protocol(ExecutionMode.acquire, action)

@alecandido I think this should be more or less what we discussed :)

Checklist:

  • Reviewers confirm new code works as expected.
  • Tests are passing.
  • Coverage does not decrease.
  • Documentation is updated.
  • Compatibility with Qibo modules (Please edit this section if the current pull request is not compatible with the following branches).
    • Qibo: master
    • Qibolab: main
    • Qibolab_platforms_qrc: main

Copy link

codecov bot commented May 21, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.32%. Comparing base (a1161f6) to head (7a03b1b).

Current head 7a03b1b differs from pull request most recent head c10d86d

Please upload reports for the commit c10d86d to get more accurate results.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #865      +/-   ##
==========================================
- Coverage   97.37%   97.32%   -0.05%     
==========================================
  Files         115      115              
  Lines        8695     8645      -50     
==========================================
- Hits         8467     8414      -53     
- Misses        228      231       +3     
Flag Coverage Δ
unittests 97.32% <100.00%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
src/qibocal/__init__.py 100.00% <100.00%> (ø)
src/qibocal/auto/execute.py 100.00% <100.00%> (ø)
src/qibocal/auto/mode.py 100.00% <100.00%> (ø)
src/qibocal/auto/runcard.py 95.91% <100.00%> (ø)
src/qibocal/auto/task.py 99.09% <100.00%> (-0.04%) ⬇️
src/qibocal/cli/acquisition.py 100.00% <100.00%> (ø)
src/qibocal/cli/autocalibration.py 100.00% <100.00%> (ø)
src/qibocal/cli/fit.py 100.00% <100.00%> (ø)
src/qibocal/cli/report.py 100.00% <100.00%> (ø)
src/qibocal/protocols/__init__.py 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

@alecandido
Copy link
Member

alecandido commented May 21, 2024

To make it (slightly) simpler, I would:

  • use a custom constructor, e.g. `Executor.create
  • accept for the first parameter a list[Union[Action, ActionLike]], where ActionLike is a dictionary that could be loaded into an action
  • for the platform a Union[Platform, str] (make the call to create_platform() internally, if it's not already an instance of Platform)
  • for the output a PathLike (same of the previous)
  • re-export Executor top-level
  • changing as well .run_protocol() parameters, to require only the protocol id
  • and using acquire + fit as default mode
    • we could use auto as well as a keyword, but I'd avoid generating reports because it would happen during the execution

turning your example into:

from qibocal import Executor

protocol = {"id": "flipping",
            "operation": "flipping",
            "targets": [0,1,2],
            "parameters": {
                "nflips_max": 20,
                "nflips_step": 2,
                "detuning": +0.1,
            }}
executor = Executor.create([protocol], platform="dummy", output="FOLDER")

completed = executor.run_protocol("flipping", mode="acquire")

This is just for interfacing purposes, saving the user some boilerplate and limiting the required understanding of Qibocal internals.

@alecandido
Copy link
Member

Concerning the mode, currently, Qibocal is using:

class ExecutionMode(Enum):
acquire = "acquire"
fit = "fit"
autocalibration = "autocalibration"
report = "report"

However, we could use an IntFlag and define auto as:

auto = ExecutionMode.acquire | ExecutionMode.fit | ExecutionMode.report

with all the bits set.

For .run_protocol() I'd just use ExecutionMode.acquire | ExecutionMode.fit. We could give it a name, or, to make it simpler, set the default to None, and set the mode internally to that, instead of retrieving it by name.

@alecandido
Copy link
Member

Apart from names and other interface suggestions, the current proposal looks essentially good: it should be minimal, but it should allow running a fully flexible calibration program.

Of course, if you have many of these protocols (calibration programs, not individual routines) there will be repeated patterns (e.g. the dependencies) and we could abstract them in the framework.
But given that (for the time being) we do not even have a single one of them, I believe this could be an optimal starting point :)

@andrea-pasquale
Copy link
Contributor Author

Thanks for the review @alecandido.
I think I have addressed all comments.
There is only one think worth mentioning ExecutionMode.REPORT will not actually generate any report.
It is meant to indicate a mode where both acquisition and fit are skipped. The data is just loaded and then pass it to the ReportBuilder to perform the report generation. At this point we could even drop it or expose to the user a proper way to generate the report.

@alecandido
Copy link
Member

At this point we could even drop it or expose to the user a proper way to generate the report.

Yes, that's definitely an option.

Since the Executor is not involved in the report generation, we could restrict the modes to acquire and fit (and the combination), and instead delegate the rest to the ReportBuilder.
This would only change the modes and the way the CLI is implemented, but you could keep the exact same interface with qq auto/acquire/fit/report.

Copy link
Member

@alecandido alecandido left a comment

Choose a reason for hiding this comment

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

(Very) minor further suggestions, but to me, it's perfect.

It should be non-breaking for the current way of using it (i.e. CLI + runcards), and it should allow running individual protocols.

The only thing missing is an example of how to use it, but given that is a non-breaking/minimal contribution you could even merge it like this, and contribute the example in a further PR, considering this just as a refactor that will allow that feature (and not a new feature to be documented).

src/qibocal/__init__.py Outdated Show resolved Hide resolved
src/qibocal/auto/execute.py Outdated Show resolved Hide resolved
src/qibocal/auto/mode.py Outdated Show resolved Hide resolved
src/qibocal/auto/task.py Outdated Show resolved Hide resolved
@andrea-pasquale
Copy link
Contributor Author

The only thing missing is an example of how to use it, but given that is a non-breaking/minimal contribution you could even merge it like this, and contribute the example in a further PR, considering this just as a refactor that will allow that feature (and not a new feature to be documented).

I can write an example on how to use it. We have already a section in documentation on how to use qibocal as a library https://qibo.science/qibocal/stable/tutorials/advanced.html#how-to-use-qibocal-as-a-library, which was doing similar things in a more complicated way. At this point shall we remove it and modify this section with the new layout proposed here?
Again the only difference concern the plotting, I believe that we the current layout it is not trivial to generate plots.
What do you think?

@scarrazza
Copy link
Member

scarrazza commented May 22, 2024

In view of #866, I would suggest a minor API change (demo code):

from qibocal import Executor, Routine
from qibocal.protocols.operation import flipping


flipping_params = {
            "id" : "flipping initial guess",
            "targets": [0,1,2],
            "parameters": {
                "nflips_max": 20,
                "nflips_step": 2,
                "detuning": +0.1,
            }}

def acquire(data, ...):
    """Custom routine"""
    return ...

my_routine = Routine(acquire)
routine_params = {"......."}
executor = Executor.create(platform="dummy", output="FOLDER")

for i in range(10):
    completed = executor.run_protocol(flipping, flipping_params, mode="acquire")
    flipping_params['parameters']['detuning'] += i
    completed2 = executor.run_protocol(my_routine, routine_params, mode="acquire")

Where:

  • we do not have to specify in Executor.create the protocols that are already registered internally in qibocal.
  • we have the flexibility to update the runcard on-the-fly and execute an arbitrary number of time the same protocol.
  • we can create an Executor with custom plugin routines.

@alecandido
Copy link
Member

We have already a section in documentation on how to use qibocal as a library qibo.science/qibocal/stable/tutorials/advanced.html#how-to-use-qibocal-as-a-library, which was doing similar things in a more complicated way. At this point shall we remove it and modify this section with the new layout proposed here?

Yes, and I'd move it out from "Advanced Examples", and rather make its own page under tutorials (just to avoid passing the idea that this is something you should beware of).

Again the only difference concern the plotting, I believe that we the current layout it is not trivial to generate plots.
What do you think?

Currently, we're coupling report generation to the execution, though the two things are well separate.
What we could do is to expose the report function

def report(path: pathlib.Path, executor: Optional[Executor] = None):

(that already has a pretty simple API), even dropping the executor argument, i.e. assuming that an executor already ran.
What we will need instead will be a History, but I would make that optional (if possible), and try to deserialize the History from the path itself (the purpose of having a History anyhow is to make the input path optional, i.e. you could generate everything in memory, before dumping to disk).
The only other executor attribute used is:
targets=executor.targets,

but we can inherit that in the history.

This should be sufficient for qq report (it is not qq auto, so it's not supposed to run a calibration on its own), and report(path) or report(path, executor.history) should be a simple enough API even for an end user (at which point I'd take out the report function from the cli subpackage, and expose top-level).
We could even do report(executor=executor), in such a way that the path and history are automatically inferred (or report(executor) and check the type, also using @overload for that).

In any case, I'd do this in a separate PR.

@alecandido alecandido mentioned this pull request May 22, 2024
@alecandido alecandido mentioned this pull request May 24, 2024
1 task
@alecandido
Copy link
Member

Since this is working, and #869's target is to provide a simpler syntax to use what's already here, I wonder whether we should aim to merge this now.

I will re-review, but I've already approved it, and this PR is just exposing something that is already more or less there in main (i.e. executing routines explicitly with the Executor, now possible individually).

Since it's at an internal stage, we can avoid documenting it as a public feature (the public interface will come with #869), but the improvement is simple and valid on its own.

Comment on lines +59 to +62
@property
def _actions_dict(self):
"""Helper dict to find protocol."""
return {action.id: action for action in self.actions}
Copy link
Member

Choose a reason for hiding this comment

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

The other thing we may consider is to kill the .actions attribute of the Executor, and essentially kill Executor.load() as well, just keeping Executor.run() for the runcards.

I.e., .load() is there to load a runcard, and .run() is there to execute it (since if you don't have a runcard you would use a script, not running all routines in one shot). So, we have two methods that are always going to be called one after the other (Executor.load(runcard).run(mode)), thus it could be just one (Executor.run(runcard, mode)).

src/qibocal/auto/execute.py Show resolved Hide resolved
Comment on lines +83 to +88
executor = Executor.load(
runcard, path, targets=runcard.targets, platform=GlobalBackend().platform
)
# produce html
list(executor.run(mode=ExecutionMode.report))
# TODO: skip report in a proper way
list(executor.run(mode=[]))
Copy link
Member

Choose a reason for hiding this comment

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

However, to replace Executor.load() we'll need to be able to deserialize a History (i.e. let's use History.load(path)), and replace executor occurrences here with history, as proposed in #865 (comment).

But this starts being a separate PR.

Comment on lines 8 to 12
- id: resoantor_amplitude
operation: resonator_amplitude
parameters:
amplitude_step: 0.1
amplitude_stop: 0.5
Copy link
Contributor

Choose a reason for hiding this comment

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

comment the lines

@alecandido
Copy link
Member

@andrea-pasquale @Edoardo-Pedicillo should we then solve the conflicts and merge this PR?

I know we are going to change it even further, in #870 and #869, but this PR is rather self-contained, despite introducing a temporary solution (in any case, main is not a release, and the other two PRs will come quite soon).

@andrea-pasquale andrea-pasquale added this pull request to the merge queue Jun 6, 2024
Merged via the queue into main with commit b0c0ef7 Jun 6, 2024
19 checks passed
@andrea-pasquale andrea-pasquale deleted the run_single_protocol branch June 6, 2024 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants