cunqa.qjob.QJob

class cunqa.qjob.QJob(qclient: cunqa.qclient.QClient, backend: Backend, circuit: dict | CunqaCircuit | qiskit.QuantumCircuit, **run_parameters: Any)

Bases: object

Class to handle jobs sent to virtual QPUs.

A QJob object is created as the output of the run() method. The quantum job not only contains the circuit to be simulated, but also simulation instructions and information of the virtual QPU to which the job is sent.

One would want to save the QJob resulting from sending a circuit in a variable. Let’s say we want to send a circuit to a QPU and get the result and the time taken for the simulation:

>>> qjob = qpu.run(circuit)
>>> result = qjob.result
>>> print(result)
<cunqa.result.Result object at XXXXXXXX>
>>> time_taken = qjob.time_taken
>>> print(time_taken)
0.876

Note that the time_taken is expressed in seconds. More instructions on obtaining data from Result objects can be found on its documentation.

Handling QJobs sent to the same QPU

Let’s say we are sending two different jobs to the same QPU. This would not result on a parallelization, each QPU can only execute one simulation at the time. When a virtual QPU recieves a job while simulating one, it would wait in line untill the earlier finishes. Because of how the client-server comunication is built, we must be careful and call for the results in the same order in which the jobs where submited. The correct workflow would be:

>>> qjob_1 = qpu.run(circuit_1)
>>> qjob_2 = qpu.run(circuit_2)
>>> result_1 = qjob_1.result
>>> result_2 = qjob_2.result

This is because the server follows the rule FIFO (First in first out), if we want to recieve the second result, the first one has to be out.

Warning

In the case in which the order is not respected, everything would work, but results will not correspond to the job. A mix up would happen.

Handling QJobs sent to different QPUs

Here we can have parallelization since we are working with more than one virtual QPU. Let’s send two circuits to two different QPUs:

>>> qjob_1 = qpu_1.run(circuit_1)
>>> qjob_2 = qpu_2.run(circuit_2)
>>> result_1 = qjob_1.result
>>> result_2 = qjob_2.result

This way, when we send the first job, then inmediatly the sencond one is sent, because run() does not wait for the simulation to finish. In this manner, both jobs are being run in both QPUs simultaneously! Here we do not need to perserve the order, since jobs are managed by different QClient objects, there can be no mix up.

In fact, the function gather() is designed for recieving a list of qjobs and return the results; therefore, let’s say we have a list of circuits that we want to submit to a group of QPUs:

>>> qjobs = []
>>> for circuit, qpu in zip(circuits, qpus):
>>> ... qjob = qpu.run(circuit)
>>> ... qjobs.append(qjob)
>>> results = gather(qjobs)

In this workflow, all circuits are sent to each QPU an simulated at the same time. Then, when calling for the results, the program is blocked waiting for all of them to finish. Other examples of classical parallelization of quantum simulation taks can be found at the Examples Gellery.

Note

The function gather() can also handle QJob objects sent to the same virtual QPU, but the order must be perserved in the list provided.

Upgrading parameters from QJobs

In some ocassion, especially working with variational quantum algorithms (VQAs) [1], they need of changing the parameters of the gates in a circuit arises. These parameters are optimzied in order to get the circuit to output a result that minimizes a problem. In this minimization process, parameters are updated on each iteration (in general).

Our first thought can be to update the parameters, build a new circuit with them and send it to the QPU. Nevertheless, since the next circuit will have the same data but for the value of the parameters in the gates, a lot of information is repeated, so cunqa has a more efficient and simple way to handle this cases: a method to send to the QPU a list with the new parameters to be assigned to the circuit, upgrade_parameters().

Let’s see a simple example: creating a parametric circuit and uptading its parameters:

>>> # building the parametric circuit
>>> circuit = CunqaCircuit(3)
>>> circuit.ry(0.25, 0)
>>> circuit.rx(0.5, 1)
>>> circuit.p(0.98, 2)
>>> circuit.measure_all()
>>> # sending the circuit to a virtual QPU
>>> qjob = qpu.run(circuit)
>>> # defining the new set of parameters
>>> new_parameters = [1,1,0]
>>> # upgrading the parameters of the job
>>> qjob.upgrade_parameters(new_parameters)
>>> result = qjob.result

From this simple workflow, we can build loops that update parameters according to some rules, or by a optimizator, and upgrade the circuit until some stoping criteria is fulfilled.

Warning

Before sending the circuit or upgrading its parameters, the result of the prior job must be called. It can be done manually, so that we can save it and obtain its information, or it can be done automatically as in the example above, but be aware that once the upgrade_parameters() method is called, this result is discarded.

References:

Attributes

result

Result of the job.

time_taken

Time that the job took.

qclient

Client linked to the server that listens at the virtual QPU.

Methods

QJob.__init__(qclient: cunqa.qclient.QClient, backend: Backend, circuit: dict | CunqaCircuit | qiskit.QuantumCircuit, **run_parameters: Any)

Initializes the QJob class.

Possible instructions to add as **run_parameters can be: shots, method, parameter_binds, meas_level, … For further information, check run() method.

Warning

At this point, circuit is asumed to be translated into the native gates of the backend. Otherwise, simulation will fail and an error will be returned as the result. For further details, checkout transpile.

Parameters:
  • qclient (QClient) – client linked to the server that listens at the virtual QPU.

  • backend (Backend) – gathers necessary information about the simulator.

  • circ (qiskit.QuantumCircuit | dict | CunqaCircuit) – circuit to be run.

  • **run_parameters – any other simulation instructions.

QJob.result()

Result of the job. If no error occured during simulation, a Result object is retured. Otherwise, ResultError will be raised.

Note

Since to obtain the result the simulation has to be finished, this method is a blocking call, which means that the program will be blocked until the QClient has recieved from the corresponding server the outcome of the job. The result is not sent from the server to the QClient until this method is called.

QJob.submit() None

Asynchronous method to submit a job to the corresponding QClient.

Note

Differently from result(), this is a non-blocking call. Once a job is summited, there is no wait, the python program continues at the same time that the corresponding server recieves and simualtes the circuit.

QJob.upgrade_parameters(parameters: list[float | int]) None

Method to upgrade the parameters in a previously submitted job of parametric circuit. By this call, first it is checked weather if the prior simulation’s result was called. If not, it calls it but does not store it, then sends the new set of parameters to the server to be reasigned to the circuit and to simulate it.

This method can be used on a loop, always being careful if we want to save the intermediate results.

Examples of usage are shown above and on the Examples Gallery. Also, this method is used by the class QJobMapper, checkout its documentation for a extensive description.

Warning

In the current version, parameters will be assigned to ALL parametric gates in the circuit. This means that if we want to make some parameters fixed, it is on our responsibility to pass them correctly and in the correct order in the list. If the number of parameters is less than the number of parametric gates in the circuit, an error will occur at the virtual QPU, on the other hand, if more parameters are provided, there will only be used up to the number of parametric gates.

Also, only rx, ry and rz gates are supported for this functionality, that is, they are the only gates considered parametric for this functionality.

Parameters:

parameters (list[float | int]) – list of parameters to assign to the parametrized circuit.