# SETUP (common to all tutorials)
import fermioniq
client = fermioniq.Client() # Initialize the client (assumes environment variables are set)

import rich # Module for pretty printing
import nest_asyncio # Necessary only for Jupyter, can be omitted in .py files
nest_asyncio.apply()

9) Asynchronous jobs#

Sometimes it is not desirable for the program to block and wait for the emulation to finish, especially for larger jobs. It is possible to submit a job and then continue with program execution, and later on retrieve the results of the job.

Let’s create a qiskit circuit as usual.

from qiskit import QuantumCircuit

def qiskit_bell_pair():
    circuit = QuantumCircuit(2)
    circuit.h(0)
    circuit.cx(0, 1)
    return circuit

circuit = qiskit_bell_pair()

# Create an emulator job
emulator_job = fermioniq.EmulatorJob(circuit=circuit)

We can schedule an asynchronous job using the schedule_async() method of the client.

# Asynchronously schedule the job
job = client.schedule_async(emulator_job)

# Print the job ID and status
print(f"Job id {job.id}. Status {job.status_code}: {job.status}.")

At any time we can block and wait for the emulation to finish, using the subscribe_to_events() method of the client. While waiting, the client polls the job status and calls the optional on_msg_callback function every time an event occurs.

Only two event types are emitted: STARTED, when the client observes the job moving from scheduled to running, and FINISHED, when the job has completed. For short jobs you may well see only FINISHED: STARTED is skipped if the job is already running at the first poll, and if the job has already finished by the time subscribe_to_events() is called, the method returns immediately without invoking the callback at all.

# Block and wait for a job to finish. Here we pass the job id to the subscribe_to_events( ) method,
# along with a callback that is invoked for every event we receive.
def on_event(message):
    print(f"Event {message.event_type} for job {message.job_id}.")

client.subscribe_to_events(job.id, on_msg_callback=on_event)

Alternatively, we can check whether the job has finished at a later time. If it has, we can retrieve the results.

status = client.get_status(job.id)

if status == 'finished':
    # Retrieve and print results
    rich.print(client.get_results(job.id))

If you lose the job ID or just want to view the status of your jobs, you can list them using the jobs() method of the client. Printing the jobs object with rich.print renders a table. To get the jobs as a list, use the job_list attribute.

# Limit: (maximum) N=number of jobs to retrieve
# Offset: From which point to list jobs (leave at 0 to list the most recent jobs)
jobs = client.jobs(limit=10, offset=0)
rich.print(jobs)

# Print the results of the most recent finished job (results are only
# available for jobs that finished successfully)
last_finished = next(j for j in jobs.job_list if j.status == 'finished')
last_result = client.get_results(last_finished.id)
rich.print(last_result)

Finally, it is possible to cancel a job by using the cancel() method of the client. A job can also be cancelled automatically when you interrupt the client (Ctrl-C) while waiting for it: pass cancel_on_interrupt=True to subscribe_to_events(), which defaults to False there (and to True in schedule_and_wait()).

# Schedule a job and immediately cancel it
job = client.schedule_async(emulator_job)
client.cancel(job.id)