Working with jobs¶
The job class obtained after running a QuantumCircuit has interesting properties that can be used.
[10]:
import os
from c12_callisto_clients.user_configs import UserConfigs
from c12_callisto_clients.qiskit.c12sim_provider import C12SimProvider
from qiskit import QuantumCircuit
[3]:
TOKEN = os.getenv("C12_TOKEN")
configs = UserConfigs.parse_obj({"token" : TOKEN})
c12_simulator_provider = C12SimProvider(configs)
c12_simulator_backend = c12_simulator_provider.get_backend('c12sim-iswap')
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.draw()
[3]:
┌───┐ q_0: ┤ H ├──■── └───┘┌─┴─┐ q_1: ─────┤ X ├ └───┘
Two main methods available for each Job instance are job_id() and status().
job_id()
method returns the unique identifier of a job as a UUID4 string (a universally unique identifier (UUID), version 4, is a 36-character alphanumeric random generated string). This id can be used for later assessment of the job properties.status()
method is used to get the status of a job execution as an instance of JobStatus class. Available statuses are:QUEUED
= Job is queued. It waits for executionRUNNING
= Job is currently running.CANCELLED
= Job has been canceled.DONE
= Job has been successfully done.ERROR
= There has been an error during the execution of a job.
The status of a running job can be changed depending on the current state of job execution.
[4]:
c12_job = c12_simulator_backend.run(circuit)
print(f"Job id: {c12_job.job_id()}") # Get a job UUID
print(f"Status: {c12_job.status()}") # Get a current job status
Job id: 25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9
Status: JobStatus.RUNNING
The result of a job execution can be obtained with the result()
method. This method can have one optional argument (timeout). The timeout
argument specifies how long the method will wait for the execution of the task. If the time limit is exceeded, the C12SimJobError
exception is raised. If we do not specify this argument, its default value is None, meaning the method will block until the simulation is finished.
Another way to get the results is to check the job status periodically until it is DONE
and then call the result()
function.
[5]:
from qiskit.providers import JobStatus
import time
job_final_states = [JobStatus.DONE, JobStatus.ERROR, JobStatus.CANCELLED]
c12_job_id = c12_job.job_id()
while True:
job_status = c12_job.status()
print(f'{c12_job_id}: {job_status}')
if job_status in job_final_states:
break
time.sleep(5) # Wait 5 s
if c12_job.status() == JobStatus.DONE:
c12_result = c12_job.result()
print(c12_result.get_counts())
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.RUNNING
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.RUNNING
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.RUNNING
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.RUNNING
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.RUNNING
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.RUNNING
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.RUNNING
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.RUNNING
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.RUNNING
25f3fecf-f0b2-4f03-bf4c-bbf35234c1e9: JobStatus.DONE
{'00': 507, '01': 2, '10': 1, '11': 514}
[6]:
# Running a job that will block until finished
c12_job = c12_simulator_backend.run(circuit)
c12_result = c12_job.result()
if c12_job.status() == JobStatus.DONE:
print(c12_result.get_counts())
{'00': 514, '11': 510}
[7]:
# Getting a job result with a timeout argument specified
from c12_callisto_clients.qiskit.exceptions import C12SimJobError
c12_job = c12_simulator_backend.run(circuit)
try:
c12_result = c12_job.result(timeout=15) # it will raise a C12SimJobError as it won't be finished in 15s
except C12SimJobError:
print("Timeout!")
print(f" Last status: {c12_job.status()}")
Timeout!
Last status: JobStatus.RUNNING
Getting previous jobs¶
Next, an extremely useful functionality is the possibility to obtain all jobs run on the system by a specific user. Using this functionality, the user can get the results of old jobs.
This can be achieved by calling the jobs()
function on the backend instance. This function accepts two arguments: limit
and offset
. We can specify how many jobs we want to get with a limit
argument. Likewise, we can set the offset
from the first job with an offset argument. With these arguments, pagination is easily achieved.
[5]:
number_of_records = 10
offset = 10
counter = 1
jobs = c12_simulator_backend.jobs(number_of_records, offset)
for job in jobs:
print(f"{counter}: {job.job_id()} Status: {job.status()}")
counter += 1
1: 8f1a598d-5efa-46df-89f5-fc4c69e38d46 Status: JobStatus.DONE
2: 46b62950-e1a3-4750-a8e1-75d738fe4d07 Status: JobStatus.DONE
3: 84af6995-ff3d-459f-ab9c-89fa90603f43 Status: JobStatus.DONE
4: ca23490d-c820-4529-abd2-801cf313eaf8 Status: JobStatus.DONE
5: 4e8ec2e8-1d16-4bd4-a87c-8bc8c9ea7420 Status: JobStatus.DONE
6: d609a2d1-de33-48ea-adfc-c4b4d0e1afd9 Status: JobStatus.DONE
7: ff507df5-65ff-410f-a20e-b4e86726dab8 Status: JobStatus.DONE
8: d79259bd-3304-41e2-98e8-3a1794cf6dff Status: JobStatus.DONE
9: 9bf33018-e427-4ae6-a4e8-698a21ab60b3 Status: JobStatus.DONE
10: dd2ae9e8-6e0c-4a1f-b753-da651d6a4011 Status: JobStatus.DONE
For each job, it is possible to get the circuit sent to the simulator for execution. But, it is even possible to get the transpiled version of this circuit to see what the circuit has been run after the transpilation process.
[6]:
# Also it is possible to retrieve the circuit that has been run for specific job
first_job = jobs[1]
first_job_qasm = first_job.get_qasm() # Get OpenQASM string
first_job_circuit = first_job.get_circuit()
first_job_circuit.draw('mpl')
[6]:
[7]:
# Getting the transpiled circuit
# NOTICE: It can be possible that due to a bug in Qiskit's transpile function some circuits are wrongly converted to QASM string
first_job_transpiled_qasm = first_job.get_qasm(transpiled=True) # Get transpiled OpenQASM string
first_job_transpiled_circuit = first_job.get_circuit(transpiled=True)
first_job_transpiled_circuit.draw('mpl')
[7]:
Query job by UUID¶
Jobs can be searched by their UUID using the get_job()
method.
[8]:
# Jobs can be queried by theirs UUID
job_uuid = first_job.job_id()
search_job = c12_simulator_backend.get_job(job_uuid)
print(f"Job id : {search_job.job_id()} {search_job.status()}")
Job id : 46b62950-e1a3-4750-a8e1-75d738fe4d07 JobStatus.DONE
Running multiple jobs at once¶
The run()
method of the backend class can also accept the list of QuantumCircuit. This can be an essential feature as it allows a user to run multiple circuits simultaneously and get an array of job instances as a result.
[9]:
# Array where the circuits will be stored
circuits_to_run = []
# Creating the circuits
circuit_1 = QuantumCircuit(2)
circuit_1.h(0)
circuit_1.cx(0, 1)
circuit_2 = QuantumCircuit(3)
circuit_2.h(0)
circuit_2.cx(0, 1)
circuit_2.x(2)
circuit_2.h(2)
circuits_to_run.append(circuit_1)
circuits_to_run.append(circuit_2)
# Running the jobs
c12_jobs = c12_simulator_backend.run(circuits_to_run)
# Printing the UUID of the jobs
for job in c12_jobs:
print(f"Job id: {job.job_id()} - > {job.status()}")
# Waiting for the results
for job in c12_jobs:
result = job.result()
print(result.get_statevector())
Job id: 15367226-5967-4231-93bd-e81df7da8c40 - > JobStatus.RUNNING
Job id: 4b93f8c1-7e9b-4053-98bc-a0948b59c39d - > JobStatus.QUEUED
[-7.07350024e-01+5.77469756e-05j 5.75368866e-05+2.69994702e-04j
5.74994563e-05+2.15881814e-04j -7.06863360e-01-5.72895393e-05j]
[ 5.00172138e-01-4.08332894e-05j -4.06847340e-05-1.90915138e-04j
-4.06582668e-05-1.52651537e-04j 4.99828015e-01+4.05098330e-05j
-5.00171860e-01+4.08332667e-05j 4.06847114e-05+1.90915032e-04j
4.06582441e-05+1.52651452e-04j -4.99827736e-01-4.05098104e-05j]
[ ]: