Execution Session
This section explains how to execute a quantum program using the ExecutionSession
class.
The class enables executing a quantum program with parameters and operations, eliminating the need to resynthesize the model.
Initializing
Initialize ExecutionSession
with a quantum program; the output of the synthesis operation. For example:
from classiq import (
qfunc,
create_model,
synthesize,
)
from classiq.execution import ExecutionSession
@qfunc
def main():
pass
qmod = create_model(main)
qprog = synthesize(qmod)
es = ExecutionSession(qprog)
Operations
ExecutionSession
supports two types of operations:
-
sample
: Executes the quantum program with the specified parameters. -
estimate
: Computes the expectation value of the specified Hamiltonian using the quantum program.
Each invocation mode can use one of these options:
-
single
-
batch
: Samples/estimates the quantum program multiple times. The number of samples or estimations is determined by the length of the parameters list. -
submit
: When using submit as a prefix, the job object returns immediately, and the results should be polled. SeeExecutionJob
in the sdk reference.
For the sample
method, these variations are available:
-
es.sample(parameter: Optional[ExecutionParams])
-
es.batch_sample(parameter: List[ExecutionParams])
-
es.submit_sample(parameter: Optional[ExecutionParams])
-
es.submit_batch_sample(parameter: List[ExecutionParams])
The estimate
method has the same variations:
-
es.estimate(hamiltonian: Union[str, PauliTerm], parameters: Optional[ExecutionParams])
-
es.batch_estimate(hamiltonian, parameters: List[ExecutionParams])
-
es.submit_estimate(hamiltonia, parameters: Optional[ExecutionParams])
-
es.submit_batch_estimate(hamiltonian, parameters: List[ExecutionParams])
Examples
sample()
A simple example of how to use sample
and its variations:
from classiq import qfunc, Output, QBit, CReal, allocate, create_model, synthesize, RX
from classiq.execution import ExecutionSession
@qfunc
def main(x: Output[QBit], t: CReal):
allocate(1, x)
RX(t, x)
qmod = create_model(main)
qprog = synthesize(qmod)
execution_session = ExecutionSession(qprog)
sample_result = execution_session.sample({"t": 0.5})
batch_sample_result = execution_session.batch_sample([{"t": 0.5}, {"t": 0.6}])
sample_job = execution_session.submit_sample({"t": 0.5})
batch_sample_job = execution_session.submit_batch_sample([{"t": 0.5}, {"t": 0.6}])
estimate()
An example that shows how to use estimate
and its variations:
from classiq import (
qfunc,
Output,
QBit,
CReal,
allocate,
create_model,
synthesize,
PauliTerm,
Pauli,
RX,
)
from classiq.execution import ExecutionSession
@qfunc
def main(x: Output[QBit], t: CReal):
allocate(1, x)
RX(t, x)
qmod = create_model(main)
qprog = synthesize(qmod)
execution_session = ExecutionSession(qprog)
hamiltonian = [
PauliTerm(pauli=[Pauli.I], coefficient=1),
PauliTerm(pauli=[Pauli.Z], coefficient=2),
]
estimate_result = execution_session.estimate(hamiltonian, {"t": 0.5})
batch_estimate_result = execution_session.batch_estimate(
hamiltonian, [{"t": 0.5}, {"t": 0.6}]
)
estimate_job = execution_session.submit_estimate(hamiltonian, {"t": 0.5})
batch_estimate_job = execution_session.submit_batch_estimate(
hamiltonian, [{"t": 0.5}, {"t": 0.6}]
)
Handling Long Jobs
When handling long-running jobs (jobs that are submitted to HW providers with potentially very long queues) it is advisable to retrieve and save the job ID for future reference:
...
session = ExecutionSession(qprog)
job = session.submit_sample()
job_ID = job.id
restored_job = ExecutionJob.from_id(job_ID)
results = restored_job.get_sample_result()
Alternatively, for scenarios requiring result polling, e.g., iterative hybrid algorithms, consider the following partial code example.
This example runs a series of quantum estimation jobs, step by step. It automatically submits each job, collects the results, checks they meet certain criteria (for example, does the cost exceed a certain threshold), and adjusts the parameters for the next job based on those results.
from classiq.execution import ExecutionSession, ExecutionJob
def get_data_from_file():
# read data from file
return data
def store_data_in_file(data):
# save data in file
return
session = ExecutionSession(qprog)
iterations_data = get_data_from_file() or []
for _ in range(max_iterations - len(iterations_data)):
if iterations_data and "result" not in iterations_data[-1]:
# continue the loop from the last iteration
last_job = ExecutionJob.from_id(iterations_data[-1]["job_id"])
else:
last_job = session.submit_estimate(hamiltonian, params)
iterations_data.append({"job_id": last_job.id, "params": params})
store_data_in_file(iterations_data)
result = last_job.get_estimate_result()
iterations_data[-1]["result"] = result
store_data_in_file(iterations_data)
if result_is_good_enough(result):
break
params = compute_the_next_parameters(result)
@cfunc
Note that Classiq does not support algorithms utilizing @cfunc
(or cscope
in Qmod native) for long job execution.