Edit

Share via


How to submit a circuit with Qiskit to Azure Quantum

Learn how to submit a Qiskit quantum circuit with the Azure Quantum Development Kit (QDK). You can submit Qiskit circuits to Azure Quantum using the Azure Quantum Development Kit (QDK) and Jupyter Notebook in Visual Studio Code (VS Code). You can also test your circuits using the local sparse simulator. The QDK supports versions 1 and 2 of Qiskit.

For more information, see Quantum circuits.

Prerequisites

For installation details, see Set up the QDK extension.

Create a new Jupyter Notebook

  1. In VS Code, open the View menu and choose Command Palette.
  2. Enter and select Create: New Jupyter Notebook.
  3. VS Code detects and displays the version of Python and the virtual Python environment that was selected for the notebook. If you have multiple Python environments, then you might need to select a kernel using the kernel picker in the top right. If no environment was detected, see Jupyter Notebooks in VS Code for setup information.

Load the required imports

In the first cell of your notebook, run the following code to load the required imports:

from qdk.azure import Workspace
from qdk.azure.qiskit import AzureQuantumProvider
from qiskit import QuantumCircuit
from qiskit.visualization import plot_histogram

Connect to the Azure Quantum service

To connect to the Azure Quantum service, your need the resource ID and the location of your Azure Quantum workspace.

  1. Log in to your Azure account, https://portal.azure.com.
  2. Select your Azure Quantum workspace, and go to Overview.
  3. Copy the Resource ID and Location parameters.

Add a new cell in your notebook and use your account information to create Workspace and AzureQuantumProvider objects to connect to your Azure Quantum workspace.

workspace = Workspace(  
    resource_id = "", # Add the resourceID of your workspace
    location = "" # Add the location of your workspace (for example "westus")
    )

provider = AzureQuantumProvider(workspace)

List all backends

You can now print all of the quantum computing backends that are available on your workspace:

print("This workspace's targets:")
for backend in provider.backends():
    print("- " + backend.name)
This workspace's targets:
- ionq.simulator
- ionq.qpu.aria-1
- ionq.qpu.forte-1
- ionq.qpu.forte-enterprise-1
- quantinuum.sim.h2-1sc
- quantinuum.sim.h2-2sc
- quantinuum.sim.h2-1e
- quantinuum.sim.h2-2e
- quantinuum.qpu.h2-1
- quantinuum.qpu.h2-2
- rigetti.sim.qvm
- rigetti.qpu.ankaa-3
- rigetti.qpu.cepheus-1-36q

Run a simple circuit

In a new cell, create a simple Qiskit circuit.

# Create a Quantum Circuit acting on the q register
circuit = QuantumCircuit(3, 3)
circuit.name = "Qiskit Sample - 3-qubit GHZ circuit"
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.measure([0,1,2], [0, 1, 2])

# Print out the circuit
circuit.draw()
     ┌───┐          ┌─┐      
q_0: ┤ H ├──■───────┤M├──────
     └───┘┌─┴─┐     └╥┘┌─┐   
q_1: ─────┤ X ├──■───╫─┤M├───
          └───┘┌─┴─┐ ║ └╥┘┌─┐
q_2: ──────────┤ X ├─╫──╫─┤M├
               └───┘ ║  ║ └╥┘
c: 3/════════════════╩══╩══╩═
                     0  1  2 

Select a target to run your program

Run on the IonQ simulator

Before you run your circuit on real hardware, test your circuit in the simulator. Use get_backend to create a Backend object to connect to the IonQ Simulator backend:

simulator_backend = provider.get_backend("ionq.simulator")

IonQ backends support gates from a defined gateset, which are compiled to run optimally on the hardware. If your circuit contains gates that aren't in this list, you need to transpile into the supported gateset using the transpile function provided by Qiskit:

from qiskit import transpile
circuit = transpile(circuit, simulator_backend)

The transpile function returns a new circuit object where gates are decomposed into gates that are supported on the specified backend.

You can now run the program via the Azure Quantum service and get the result. The following cell submits a job that runs the circuit with 100 shots:

job = simulator_backend.run(circuit, shots=8)
job_id = job.id()
print("Job id", job_id)
Job id 00000000-0000-0000-0000-000000000000

To wait until the job is complete and return the results, run:

result = job.result()
print(result)
Result(backend_name='ionq.simulator', backend_version='1', qobj_id='Qiskit Sample - 3-qubit GHZ circuit', job_id='00000000-0000-0000-0000-000000000000', success=True, results=[ExperimentResult(shots=8, success=True, meas_level=2, data=ExperimentResultData(counts={'000': 4, '111': 4}, memory=['000', '000', '000', '000', '111', '111', '111', '111'], probabilities={'000': 0.5, '111': 0.5}), header=QobjExperimentHeader(name='Qiskit Sample - 3-qubit GHZ circuit', num_qubits=3, metadata={}), status=JobStatus.DONE, name='Qiskit Sample - 3-qubit GHZ circuit')], date=None, status=None, header=None, error_data=None)

Because the result is an object type specific to the Qiskit package, use result.get_counts and plot_histogram to visualize the results. To represent all possible bitstring labels, add the labels to counts.

counts = {format(n, "03b"): 0 for n in range(8)}
counts.update(result.get_counts(circuit))
print(counts)
plot_histogram(counts)
{'000': 4, '001': 0, '010': 0, '011': 0, '100': 0, '101': 0, '110': 0, '111': 4}

Qiskit circuit result on IonQ Simulator

You can also use the get_memory function to display individual shot data from the job:

result.get_memory(circuit)
['000', '000', '000', '000', '111', '111', '111', '111']

Note

On IonQ targets, if you submit a job with an odd number of shots, then the number of shots is rounded down to the nearest even number. For example, if you specify 9 shots, then the results display data for 8 shots.

Estimate job cost

Estimate how much a job costs to run before you run a job on the QPU.

For the most current pricing details, see IonQ Pricing, or find your workspace and view pricing options in the Provider tab of your workspace via: aka.ms/aq/myworkspaces.

Run on IonQ QPU

To connect to real hardware (a Quantum Processor Unit (QPU)), simply provide the name of the target "ionq.qpu.aria-1" to the get_backend method:

qpu_backend = provider.get_backend("ionq.qpu.aria-1")

Submit the circuit to run on Azure Quantum, get the results, and then run plot_histogram to plot the results.

Note

The time required to run a circuit on the QPU depends on current queue times.

# Submit the circuit to run on Azure Quantum
job = qpu_backend.run(circuit, shots=100)
job_id = job.id()
print("Job id", job_id)

# Get the job results (this method waits for the Job to complete):
result = job.result()
print(result)
counts = {format(n, "03b"): 0 for n in range(8)}
counts.update(result.get_counts(circuit))
print(counts)
plot_histogram(counts)
Job id 00000000-0000-0000-0000-000000000000
Job Status: job has successfully run
Result(backend_name='ionq.qpu.aria-1', backend_version='1', qobj_id='Qiskit Sample - 3-qubit GHZ circuit', job_id='00000000-0000-0000-0000-000000000000', success=True, results=[ExperimentResult(shots=1024, success=True, meas_level=2, data=ExperimentResultData(counts={'0': 505, '1': 6, '2': 1, '3': 1, '4': 1, '5': 10, '6': 11, '7': 488}, probabilities={'0': 0.4932, '1': 0.0059, '2': 0.001, '3': 0.001, '4': 0.001, '5': 0.0098, '6': 0.0117, '7': 0.4766}), header=QobjExperimentHeader(name='Qiskit Sample - 3-qubit GHZ circuit', num_qubits='3', qiskit='True'))])
{'000': 505, '001': 6, '010': 1, '011': 1, '100': 1, '101': 10, '110': 11, '111': 488}

Qiskit circuit result on IonQ QPU

Important

You can't submit multiple circuits on a single job. As a workaround, you can call the backend.run method to submit each circuit asynchronously, then fetch the results of each job. For example:

jobs = []
for circuit in circuits:
    jobs.append(backend.run(circuit, shots=N))

results = []
for job in jobs:
    results.append(job.result())

Prerequisites

For installation details, see Set up the QDK extension.

Run a basic circuit

In VS Code, open a new Python file to create and run a basic circuit with the built-in sparse simulator from the qsharp module.

# load the required imports 
from qdk import qsharp
from qsharp.interop.qiskit import QSharpBackend
from qiskit.circuit.random import random_circuit

# define and display the circuit
circuit = random_circuit(2, 2, measure=True)
print(circuit)

# run the circuit using the built-in sparse simulator
backend = QSharpBackend()
job = backend.run(circuit)
counts = job.result().get_counts()

print(counts)

To run the program, select the Run icon in the upper right, and select Run Python file. The output displays in a new terminal window.

                  ┌─────────────────────────┐┌─┐
q_0: ─■───────────┤0                        ├┤M├───
      │P(0.79983) │  (XX-YY)(1.9337,1.7385) │└╥┘┌─┐
q_1: ─■───────────┤1                        ├─╫─┤M├
                  └─────────────────────────┘ ║ └╥┘
c: 2/═════════════════════════════════════════╩══╩═
                                              0  1
{'11': 680, '00': 344}

Generate QIR for the circuit

From that same circuit, you can generate QIR that's used to run on quantum hardware.

Note

To generate QIR, all registers must be measured into. If there are any unused registers, then an error is raised. Additionally, you get an error when you attempt to generate QIR with an Unrestricted target profile. The Unrestricted profile is only valid for simulation. You must use TargetProfile.Base, TargetProfile.Adaptive_RI, or TargetProfile.Adaptive_RIF. You can override the target_profile in the backend.qir(...) call to switch profiles.

  1. Import QSharpError and TargetProfile

    from qdk.qsharp import QSharpError, TargetProfile
    
  2. To generate QIR, modify the output:

    print(backend.qir(circuit, target_profile=TargetProfile.Adaptive_RI))
    

Your code should now look like this:

# load the required imports 
from qdk import qsharp
from qsharp import QSharpError, TargetProfile
from qsharp.interop.qiskit import QSharpBackend
from qiskit.circuit.random import random_circuit

# define and display the circuit
circuit = random_circuit(2, 2, measure=True)
print(circuit)

# generate QIR for the circuit
print(backend.qir(circuit, target_profile=TargetProfile.Adaptive_RI))

Your code's output should look like this:

     ┌────────────┐             ┌─┐   
q_0: ┤ Rx(2.7195) ├─■───────────┤M├───
     └──┬─────┬───┘ │U1(5.5924) └╥┘┌─┐
q_1: ───┤ Tdg ├─────■────────────╫─┤M├
        └─────┘                  ║ └╥┘
c: 2/════════════════════════════╩══╩═
                                 0  1
%Result = type opaque
%Qubit = type opaque

define void @ENTRYPOINT__main() #0 {
block_0:
  call void @__quantum__qis__rx__body(double 2.7194945105768586, %Qubit* inttoptr (i64 0 to %Qubit*))
  call void @__quantum__qis__rz__body(double 2.796204066686262, %Qubit* inttoptr (i64 0 to %Qubit*))
  call void @__quantum__qis__t__adj(%Qubit* inttoptr (i64 1 to %Qubit*))
  call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*))
  call void @__quantum__qis__rz__body(double -2.796204066686262, %Qubit* inttoptr (i64 1 to %Qubit*))
  call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*))
  call void @__quantum__qis__rz__body(double 2.796204066686262, %Qubit* inttoptr (i64 1 to %Qubit*))
  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
  call void @__quantum__rt__array_record_output(i64 2, i8* null)
  call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
  call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
  ret void
}

declare void @__quantum__qis__rx__body(double, %Qubit*)

declare void @__quantum__qis__rz__body(double, %Qubit*)

declare void @__quantum__qis__t__adj(%Qubit*)

declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)

declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1

declare void @__quantum__rt__array_record_output(i64, i8*)

declare void @__quantum__rt__result_record_output(%Result*, i8*)

attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="2" }
attributes #1 = { "irreversible" }

; module flags

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}

!0 = !{i32 1, !"qir_major_version", i32 1}
!1 = !{i32 7, !"qir_minor_version", i32 0}
!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
!3 = !{i32 1, !"dynamic_result_management", i1 false}
!4 = !{i32 1, !"classical_ints", i1 true}
!5 = !{i32 1, !"qubit_resetting", i1 true}
!6 = !{i32 1, !"classical_floats", i1 false}
!7 = !{i32 1, !"backwards_branching", i1 false}
!8 = !{i32 1, !"classical_fixed_points", i1 false}
!9 = !{i32 1, !"user_functions", i1 false}
!10 = !{i32 1, !"multiple_target_branching", i1 false}

Not all programs can run on all hardware. Here, if you try to target the Base profile, then you get detailed errors about which parts of the program aren't supported.

try:
    backend.qir(qc, target_profile=TargetProfile.Base)
except QSharpError as e:
    print(e)

Next steps