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 therun()
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 differentQClient
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.
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 of the job. |
|
|
Time that the job took. |
|
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 theQClient
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.