Skip to content

Execute

The last step of the quantum algorithm development process with Classiq is to execute the quantum program on a quantum computer or a simulator. This can be done in the IDE or through the Python SDK. Classiq offers access to a wide variety of quantum computers with different hardware modalities from several companies including: IonQ, Quantinuum, IBM, OQC and Rigetti, as well as to several simulators.

The execution phase is composed of configuration and access to the results. In the following, we cover these parts through a concrete example. First we start with the default execution options of Classiq, the execution we have encountered in previous example of this 101 guide.

Default Execution

Continuing with the algorithm from previous chapters, we want to create a quantum algorithm that calculates in a superposition the arithmetic expression \(y=x^2+1\). The following algorithm written in Qmod implements the desired task:

from classiq import *


@qfunc
def main(x: Output[QNum], y: Output[QNum]):

    allocate(4, x)
    hadamard_transform(x)  # creates a uniform superposition
    y |= x**2 + 1

And the algorithm can then be synthesized:

quantum_program = synthesize(create_model(main))

The quantum program at hand can now be executed directly and the results can be analyzed:

job = execute(quantum_program)
results = job.result()[0].value.parsed_counts
print(results)
[{'x': 0.0, 'y': 1.0}: 91, {'x': 5.0, 'y': 26.0}: 72, {'x': 10.0, 'y': 101.0}: 71, {'x': 2.0, 'y': 5.0}: 71, {'x': 15.0, 'y': 226.0}: 68, {'x': 14.0, 'y': 197.0}: 67, {'x': 12.0, 'y': 145.0}: 66, {'x': 11.0, 'y': 122.0}: 64, {'x': 9.0, 'y': 82.0}: 61, {'x': 1.0, 'y': 2.0}: 60, {'x': 13.0, 'y': 170.0}: 60, {'x': 4.0, 'y': 17.0}: 59, {'x': 6.0, 'y': 37.0}: 49, {'x': 8.0, 'y': 65.0}: 47, {'x': 3.0, 'y': 10.0}: 47, {'x': 7.0, 'y': 50.0}: 47]

The above demonstrates how the execution can be done with the default configuration of Classiq which is an execution on a simulator (with up to 25 qubits) with 1000 shots. Let's see how we can execute with other configurations.

Configuring the Execution

Configuration in the IDE

From the IDE, the execution configuration is done via the Execution tab, which is automatically accesses when the Execute button is pressed from the visualization of the quantum program:

On the left-hand side one can see a list of all the available simulators that can be accessed. On the top part, one can tick the Hardware box to see a lost of all the available hardwares one can access via the platform. The hardwares as well as some of the simulators require further credentials that needs to be entered once the Run button is pressed. These credentials are the vendor's credentials for accessing the relevant compute resources (more details can be found in the reference manual).

On the right-hand side of the screen, one can configure the specific execution. The Num Shots can be used to change the number of shots the quantum program will be sampled, and one can enter an indicative Job Name. The Random Seed can be used in order to receive the exact same results when a simulator is used (as well as for more advanced execution options on real hardware such as variational quantum algorithms and advanced transpilation methods).

After the configuration is done, all that remains is to press to Run button.

Configuration in the SDK

One can change the execution configuration from the SDK as well. This is done by adapting the quantum model with the desired execution preferences, similarly to the constraints and preferences from the optimization phase.

The following code demonstrates how to change the number of shots, to set a name for the job and to set a random seed for the simulator:

from classiq.execution import ExecutionPreferences

quantum_model = create_model(main)
quantum_model_with_execution_preferences = set_execution_preferences(
    quantum_model,
    ExecutionPreferences(
        num_shots=2048, job_name="classiq 101 - execute", random_seed=767
    ),
)

And then the adapted quantum model can be synthesized and executed:

quantum_program_with_execution_preferences = synthesize(
    quantum_model_with_execution_preferences
)
job = execute(quantum_program_with_execution_preferences)

The full list of configuration options for the execution preferences can be found in the reference manual.

Accessing the Results

Results in the IDE

Once you press the Run button from the Execution tab you will automatically be transitioned to the Jobs tab of the platform. All your jobs are listed on the left-hand side, and by clicking on a specific job, its information is depicted below the job name.

On the right-hand side, the histogram of the results is depicted. Each bin represent a specific measurement result and its hight represents the counts of that measurement. By hovering over a specific bin, the variables of that measurement results are depicted, as well as the counts of that bin.

At the top-right corner there are two buttons. The right one allows downloading the results of the job in a specific format like .csv, and the left button enables further options for analyzing the histogram results.

Results in the SDK

The job variable which is the output of the execute command contains all the data about the execution job. Some of the meta data can be accessed directly from this object:

print(
    f"The job on the provider {job.provider} on the backend {job.backend_name} with {job.num_shots} shots is {job.status} can be accessed in the IDE with this URL: {job.ide_url}"
)
The job on the provider Classiq on the backend simulator with 2048 shots is QUEUED can be accessed in the IDE with this URL: https://platform.classiq.io/jobs/ea0fa2c8-515a-48cf-b8ce-c5e6f6c74803

In addition to the meta data, we see the option to open the job also in the IDE. This can be done directly with:

job.open_in_ide()

The actual results of the job can be accessed with:

results = job.result()[0].value

The results variable contains a dictionary of the measured variables of our algorithm and their respective counts:

print(results.parsed_counts)
[{'x': 6.0, 'y': 37.0}: 146, {'x': 3.0, 'y': 10.0}: 143, {'x': 12.0, 'y': 145.0}: 141, {'x': 9.0, 'y': 82.0}: 137, {'x': 15.0, 'y': 226.0}: 134, {'x': 13.0, 'y': 170.0}: 133, {'x': 14.0, 'y': 197.0}: 133, {'x': 2.0, 'y': 5.0}: 133, {'x': 7.0, 'y': 50.0}: 129, {'x': 11.0, 'y': 122.0}: 128, {'x': 10.0, 'y': 101.0}: 126, {'x': 1.0, 'y': 2.0}: 123, {'x': 4.0, 'y': 17.0}: 117, {'x': 8.0, 'y': 65.0}: 115, {'x': 5.0, 'y': 26.0}: 111, {'x': 0.0, 'y': 1.0}: 99]

The raw bit strings can also be accessed directly with:

print(results.counts)
{'000000100001': 123, '010100101001': 137, '000100010100': 117, '111000101111': 134, '000000010000': 99, '001100100111': 129, '000010100011': 143, '101010101101': 133, '000110100101': 111, '010000011000': 115, '001001010110': 146, '011110101011': 128, '100100011100': 141, '110001011110': 133, '000001010010': 133, '011001011010': 126}

And the direct mapping between the bit strings and the measured variables can aldo be extracted:

print(results.parsed_states)
{'000000100001': {'x': 1.0, 'y': 2.0}, '010100101001': {'x': 9.0, 'y': 82.0}, '000100010100': {'x': 4.0, 'y': 17.0}, '111000101111': {'x': 15.0, 'y': 226.0}, '000000010000': {'x': 0.0, 'y': 1.0}, '001100100111': {'x': 7.0, 'y': 50.0}, '000010100011': {'x': 3.0, 'y': 10.0}, '101010101101': {'x': 13.0, 'y': 170.0}, '000110100101': {'x': 5.0, 'y': 26.0}, '010000011000': {'x': 8.0, 'y': 65.0}, '001001010110': {'x': 6.0, 'y': 37.0}, '011110101011': {'x': 11.0, 'y': 122.0}, '100100011100': {'x': 12.0, 'y': 145.0}, '110001011110': {'x': 14.0, 'y': 197.0}, '000001010010': {'x': 2.0, 'y': 5.0}, '011001011010': {'x': 10.0, 'y': 101.0}}

The notation of wether the bit strings are interpreted with the least significant bit (LSB) on the right or on the left can also be extracted:

print(results.counts_lsb_right)
True

All the above should enable one to analyze the results and post-process them in Python as needed.

write_qmod(quantum_model_with_execution_preferences, "execute")