Execution
An Execution is responsible for running a Computation on a Computer and retrieving its results.
Internally, it hides the complexity of the execution, stores intermediate variables, and offers monitoring convenience.
It provides the same interface for local and remote computers, and supports both synchronous and asynchronous runs.
The computer within the execution determines where and how the computation is performed.
Executions are normally created by an ExecutionFactory:
>>> import perceval as pcvl
>>>
>>> experiment = pcvl.Experiment(pcvl.BS())
>>> experiment.with_input(pcvl.BasicState("|1,1>"))
>>> computer = pcvl.SimulatedComputer("SLOS")
>>> factory = pcvl.ExecutionFactory(computer, experiment)
>>> execution = factory.sample_count
They can also be created directly using a Computation or a ComputationIterator:
>>> computation = factory.build_computation("sample_count")
>>> computation.add_params(max_samples = 10000)
>>> execution = pcvl.Execution(computation, computer)
Synchronous execution
Call execute_sync() to run an execution synchronously. The call blocks until the results are ready. Calling
the execution directly is equivalent:
>>> with computer.acquire():
... results = execution.execute_sync(max_samples=10_000) # Giving parameters here override the ones given in the Computation
... # Equivalently: results = execution(max_samples=10_000)
The returned dictionary contains a "results" if it has a Computation,
or "results_list" entry if it has a ComputationIterator,
and may contain additional data such as performance values.
A progress callback can be installed before a synchronous run. Returning True from the callback requests
cancellation:
>>> def progress_callback(progress: float, message: str):
... print(f"{progress:.0%}: {message}")
... return False
>>>
>>> execution = factory.sample_count
>>> execution.set_progress_callback(progress_callback)
Asynchronous execution
Call execute_async() to launch an execution without waiting for completion. The method returns the same
execution object, whose status can then be monitored:
>>> import time
>>>
>>> execution = factory.sample_count
>>> with execution.computer.acquire():
... execution.execute_async(max_samples=10_000)
... while not execution.is_complete:
... print(execution.status.progress)
... time.sleep(1)
... if execution.is_failed:
... print(execution.status.stop_message)
... else:
... results = execution.get_results()
Warning
A computer must remain started until all asynchronous executions associated with it have finished. Use
Computer.start and
Computer.stop when launching an execution and
waiting for its completion in different scopes.
Cancellation and rerunning
Cancellation can be requested for an execution that has already been launched and has not completed:
>>> execution.cancel()
Cancellation may take some time. Partial results, when available, can be retrieved with:
>>> partial_results = execution.get_results(allow_partial_results=True)
An execution cannot be launched more than once. Use clone() to create an equivalent unsent execution, or
rerun() to create and asynchronously launch a replacement for a failed execution:
>>> another_execution = execution.clone()
Storage
Launched remote Executions can’t be directly created using their cloud id, as they could be linked to
several cloud jobs due to Error mitigation.
To be able to access the results of an Execution in another script,
it must be serialized after launch and stored somewhere, then deserialized in the other script.
Use an ExecutionGroup to do that automatically for you, or use the serialization system.
Execution status
The status property returns an ExecutionStatus. Convenience properties are available for common state
checks:
>>> status = execution.status
>>> execution.was_sent
>>> execution.is_waiting
>>> execution.is_running
>>> execution.is_complete
>>> execution.is_success
>>> execution.is_failed
Class reference
- class perceval.runtime.execution.Execution(computation, computer)
A class aimed at controlling the execution flow of a computation on a given computer. It provides means to compute synchronously or asynchronously, hiding the complexity of handling intermediate objects.
Depending on the mitigations that are set into the computer, one or several jobs (i.e. a unit computation on the cloud, or a single simulation call) may be created. This complexity is hidden by this object, but may appear on a provider’s cloud interface.
>>> execution = Execution(computation, computer) >>> res = execution(max_shots = 10000) # Synchronous call - Computation parameters can be given here
- Parameters:
computation (
Computation|ComputationIterator) – The computation to be executedcomputer (
AComputer) – The computer that will execute the computation
- cancel()
Request the cancellation of the execution.
- clone()
- Return type:
- Returns:
A new execution, identical to this one, except that it is not associated to any run or results, and can thus be used with other parameters, or to rerun an execution.
- execute_async(*args, **kwargs)
Execute the task asynchronously. This call is non-blocking allowing for concurrency. Results cannot be expected to be ready as soon as this call ends. The results have to be retrieved only when the execution status says it’s completed.
- Parameters:
args – arguments to pass to the task function
kwargs – keyword arguments to pass to the task function
- Return type:
- Returns:
self
- execute_sync(*args, allow_partial_results=False, **kwargs)
Execute the task synchronously.
- Parameters:
args – arguments to pass to the task function
allow_partial_results (
bool) – If True, results will be returned even if there is an error somewhere. Else, the error will be raisedkwargs – keyword arguments to pass to the task function
- Return type:
dict- Returns:
results dictionary. You can expect a “results” or a “results_list” field, performance scores and other data corresponding to the computation and computer nature.
- Raises:
RuntimeError if the execution hasn’t been launched, or if there is an error and allow_partial_results is False.
- get_details()
- Return type:
str- Returns:
A str representing the details of the execution.
- get_results(allow_partial_results=False)
Retrieve the results of the execution.
- Parameters:
allow_partial_results (
bool) – If True, results will be returned even if there is an error somewhere. Else, the error will be raised- Return type:
dict- Returns:
results dictionary. You can expect a “results” or a “results_list” field, performance scores and other data corresponding to the computation and computer nature.
- Raises:
RuntimeError if the execution hasn’t been launched, or if there is an error and allow_partial_results is False.
- property job_group_name: str | None
The execution group name, that will be used to give a job group name to the generated jobs
- property name: str
The execution name, that will be used to name generated jobs
- rerun()
- Return type:
- Returns:
A new execution, identical to this one, and run it asynchronously
- reset_results_cache()
Reset the results cache. May be useful in case of a communication error during
get_results()withallow_partial_results=True- Return type:
None
- set_progress_callback(callback)
Set a progress callback function with the following signature:
def progress_callback(progress: float, message: str) -> dict | bool | None
If the progress callback returns True, a cancellation is requested. This custom callback is only used for synchronous execution. For asynchronous execution, call self.cancel() to cancel, or self.status, self.is_complete… to monitor the progress.
Note
This callback is never serialized.
- Parameters:
callback (
Callable[[float,str],UnionType[None,dict,bool]]) – callback function
- property status: ExecutionStatus
The execution status metadata structure