author | description | ms.author | ms.date | ms.service | ms.subservice | ms.topic | no-loc | title | uid | ||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
bradben |
Learn how to submit specific formatted quantum circuits with QIR, OpenQASM, or Pulser SDK to the Azure Quantum service. |
brbenefield |
08/09/2024 |
azure-quantum |
qdk |
how-to |
|
Submit formatted quantum circuits |
microsoft.quantum.quickstarts.computing.provider |
Learn how to use the azure-quantum
Python package to submit circuits in specific formats to the Azure Quantum service. This article shows you how to submit circuits in the following formats:
For more information, see Quantum circuits.
To run your circuits in a Notebook in Azure portal, you need:
- An Azure account with an active subscription. If you don’t have an Azure account, register for free and sign up for a pay-as-you-go subscription.
- An Azure Quantum workspace. For more information, see Create an Azure Quantum workspace.
To develop and run your circuits in Visual Studio Code, you also need:
-
A Python environment with Python and Pip installed.
-
VS Code with the Azure Quantum Development Kit, Python, and Jupyter extensions installed.
-
The Azure Quantum
qsharp
,azure-quantum
, andipykernel
packages.python -m pip install --upgrade qsharp azure-quantum ipykernel
You can create a notebook in VS Code or directly in the Azure Quantum portal.
- Log in to the Azure portal and select the workspace from the previous step.
- In the left blade, select Notebooks.
- Click My Notebooks and click Add New.
- In Kernel Type, select IPython.
- Type a name for the file, and click Create file.
When your new Notebook opens, it automatically creates the code for the first cell, based on your subscription and workspace information.
from azure.quantum import Workspace
workspace = Workspace (
resource_id = "", # Your resource_id
location = "" # Your workspace location (for example, "westus")
)
-
In VS Code, select View > Command palette and select Create: New Jupyter Notebook.
-
To connect to the Azure Quantum service, your program will need the resource ID and the location of your Azure Quantum workspace.
-
Log in to your Azure account, https://portal.azure.com,
-
Select your Azure Quantum workspace, and navigate to Overview.
-
Copy the parameters in the fields.
:::image type="content" source="media/azure-portal-workspace-overview.png" alt-text="Screenshot of Visual Studio Code showing how to expand the overview pane of your Quantum Workspace." lightbox="media/azure-portal-workspace-overview.png":::
-
-
In the first cell of your notebook, paste the values into the following
Workspace
constructor to create aworkspace
object that connects to your Azure Quantum workspace.from azure.quantum import Workspace workspace = Workspace ( resource_id = "", # Add your resource_id location = "" # Add your workspace location (for example, "westus") )
Quantum Intermediate Representation (QIR) is an intermediate representation which serves as a common interface between quantum programming languages/frameworks and targeted quantum computation platforms. For more information, see Quantum Intermediate Representation.
-
Create the QIR circuit. For example, the following code creates a simple entanglement circuit.
QIR_routine = """%Result = type opaque %Qubit = type opaque define void @ENTRYPOINT__main() #0 { call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) #1 call void @__quantum__rt__tuple_record_output(i64 2, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) ret void } declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*) declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*) declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) declare void @__quantum__qis__rx__body(double, %Qubit*) declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__ry__body(double, %Qubit*) declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__rz__body(double, %Qubit*) declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__h__body(%Qubit*) declare void @__quantum__qis__s__body(%Qubit*) declare void @__quantum__qis__s__adj(%Qubit*) declare void @__quantum__qis__t__body(%Qubit*) declare void @__quantum__qis__t__adj(%Qubit*) declare void @__quantum__qis__x__body(%Qubit*) declare void @__quantum__qis__y__body(%Qubit*) declare void @__quantum__qis__z__body(%Qubit*) declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*) declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 declare void @__quantum__rt__result_record_output(%Result*, i8*) declare void @__quantum__rt__array_record_output(i64, i8*) declare void @__quantum__rt__tuple_record_output(i64, i8*) attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="4" "required_num_results"="2" } attributes #1 = { "irreversible" } ; module flags !llvm.module.flags = !{!0, !1, !2, !3} !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} """
-
Create a
submit_qir_job
helper function to submit the QIR circuit to a target. Note that the input and output data formats are specified asqir.v1
andmicrosoft.quantum-results.v1
, respectively.# Submit the job with proper input and output data formats def submit_qir_job(target, input, name, count=100): job = target.submit( input_data=input, input_data_format="qir.v1", output_data_format="microsoft.quantum-results.v1", name=name, input_params = { "entryPoint": "ENTRYPOINT__main", "arguments": [], "count": count } ) print(f"Queued job: {job.id}") job.wait_until_completed() print(f"Job completed with state: {job.details.status}") #if job.details.status == "Succeeded": result = job.get_results() return result
-
Select a target and submit the QIR circuit to Azure Quantum. For example, to submit the QIR circuit to the IonQ simulator target:
target = workspace.get_targets(name="ionq.simulator") result = submit_qir_job(target, QIR_routine, "QIR routine") result
{'Histogram': ['(0, 0)', 0.5, '(1, 1)', 0.5]}
Besides QIR languages, such as Q# or Qiskit, you can submit quantum circuits in provider-specific formats to Azure Quantum. Each provider has its own format for representing quantum circuits.
-
Create a quantum circuit using the language-agnostic JSON format supported by the IonQ targets, as described in the IonQ API documentation. For example, the following sample creates a superposition between three qubits:
circuit = { "qubits": 3, "circuit": [ { "gate": "h", "target": 0 }, { "gate": "cnot", "control": 0, "target": 1 }, { "gate": "cnot", "control": 0, "target": 2 }, ] }
-
Submit the circuit to the IonQ target. The following example uses the IonQ simulator, which returns a
Job
object.target = workspace.get_targets(name="ionq.simulator") job = target.submit(circuit)
-
Wait until the job is complete and then fetch the results.
results = job.get_results() print(results)
..... {'duration': 8240356, 'histogram': {'0': 0.5, '7': 0.5}}
-
You can then visualize the results using Matplotlib.
import pylab as pl pl.rcParams["font.size"] = 16 hist = {format(n, "03b"): 0 for n in range(8)} hist.update({format(int(k), "03b"): v for k, v in results["histogram"].items()}) pl.bar(hist.keys(), hist.values()) pl.ylabel("Probabilities")
-
Before running a job on the QPU, you can estimate how much it will cost to run. To estimate the cost of running a job on the QPU,you can use the
estimate_cost
method:target = workspace.get_targets(name="ionq.qpu.aria-1") cost = target.estimate_cost(circuit, shots=500) print(f"Estimated cost: {cost.estimated_total}")
This prints the estimated cost in US dollars.
[!NOTE] 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.
To submit a circuit to PASQAL, you can use the Pulser SDK to create pulse sequences and submit them to the PASQAL target.
Pulser is a framework for composing, simulating and executing pulse sequences for neutral-atom quantum devices. It's designed by PASQAL as a pass-through to submit quantum experiments to their quantum processors. For more information, see Pulser documentation.
To submit the pulse sequences, first install the Pulser SDK packages:
try:
import pulser
except ImportError:
!pip -q install pulser
!pip -q install pulser-core
-
First, load the required imports:
import numpy as np import pulser from pprint import pprint from pulser import Pulse, Sequence, Register
-
PASQAL's QPU is made of neutral atoms trapped at well-defined positions in a lattice. To define your quantum registers you create an array of qubits on a lattice. For example, the following code creates a 4x4 square lattice of qubits:
L = 4 square = np.array([[i, j] for i in range(L) for j in range(L)], dtype=float) square -= np.mean(square, axis=0) square *= 5 qubits = dict(enumerate(square)) reg = Register(qubits) reg.draw()
:::image type="content" source="media/provider-format-pasqal-array.png" alt-text="Plot of a 4x4 square lattice with 16 qubits.":::
The neutral atoms are controlled with laser pulses. The Pulser SDK allows you to create pulse sequences to apply to the quantum register.
-
First, you need to set up a pulse sequence, and declare the channels that will be used to control the atoms. For example, the following code declares two channels:
ch0
andch1
.from pulser.devices import Chadoq2 seq = Sequence(reg, Chadoq2) seq.declare_channel("ch0", "raman_local") print("Available channels after declaring 'ch0':") pprint(seq.available_channels) seq.declare_channel("ch1", "rydberg_local", initial_target=4) print("\nAvailable channels after declaring 'ch1':") pprint(seq.available_channels)
A few things to consider:
- A
Sequence
in Pulser is a series of operations that are to be executed on a quantum register. - The code sets up a sequence of operations to be executed on an
AnalogDevice
device.AnalogDevice
is a predefined device in Pulser that represents a Fresnel1-equivalent quantum computer.
- A
-
Create a pulse sequence. To do so, you create and add pulses to the channels you declared. For example, the following code creates a simple pulse and adds it to channel
ch0
, and then creates a complex pulse and adds it to channelch1
.seq.target(1, "ch0") # Target qubit 1 with channel "ch0" simple_pulse = Pulse.ConstantPulse(200, 2, -10, 0) seq.add(simple_pulse, "ch0") # Add the pulse to channel "ch0" seq.delay(100, "ch1") from pulser.waveforms import RampWaveform, BlackmanWaveform duration = 1000 # Create a Blackman waveform with a duration of 1000 ns and an area of pi/2 rad amp_wf = BlackmanWaveform(duration, np.pi / 2) # Create a ramp waveform with a duration of 1000 ns and a linear sweep from -20 to 20 rad/µs detuning_wf = RampWaveform(duration, -20, 20) # Create a pulse with the amplitude waveform amp_wf, the detuning waveform detuning_wf, and a phase of 0 rad. complex_pulse = Pulse(amp_wf, detuning_wf, phase=0) complex_pulse.draw() seq.add(complex_pulse, "ch1") # Add the pulse to channel "ch1"
The image shows the simple and the complex pulse.
:::image type="content" source="media/provider-format-pasqal-pulser.png" alt-text="Plot of the simple and the complex pulse.":::
To submit the pulse sequences, you need to convert the Pulser objects into a JSON string that can be used as input data.
import json
# Convert the sequence to a JSON string
def prepare_input_data(seq):
input_data = {}
input_data["sequence_builder"] = json.loads(seq.to_abstract_repr())
to_send = json.dumps(input_data)
#print(json.dumps(input_data, indent=4, sort_keys=True))
return to_send
-
First, you need to set the proper input and output data formats. For example, the following code sets the input data format to
pasqal.pulser.v1
and the output data format topasqal.pulser-results.v1
.# Submit the job with proper input and output data formats def submit_job(target, seq): job = target.submit( input_data=prepare_input_data(seq), # Take the JSON string previously defined as input data input_data_format="pasqal.pulser.v1", output_data_format="pasqal.pulser-results.v1", name="PASQAL sequence", shots=100 # Number of shots ) print(f"Queued job: {job.id}") job.wait_until_completed() print(f"Job completed with state: {job.details.status}") result = job.get_results() return result
[!NOTE] The time required to run a circuit on the QPU depends on current queue times. You can view the average queue time for a target by selecting the Providers blade of your workspace.
-
Submit the program to PASQAL. For example, you can submit the program to PASQAL Emu-TN target.
target = workspace.get_targets(name="pasqal.sim.emu-tn") submit_job(target, seq)
{'0000000000000000': 59, '0000100000000000': 39, '0100000000000000': 1, '0100100000000000': 1}
-
Create a quantum circuit in the OpenQASM representation. For example, the following example creates a Teleportation circuit:
circuit = """OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c0[3]; h q[0]; cx q[0], q[1]; cx q[1], q[2]; measure q[0] -> c0[0]; measure q[1] -> c0[1]; measure q[2] -> c0[2]; """
Optionally, you can load the circuit from a file:
with open("my_teleport.qasm", "r") as f: circuit = f.read()
-
Submit the circuit to the Quantinuum target. The following example uses the Quantinuum API validator, which returns a
Job
object.target = workspace.get_targets(name="quantinuum.sim.h1-1sc") job = target.submit(circuit, shots=500)
-
Wait until the job is complete and then fetch the results.
results = job.get_results() print(results)
........ {'c0': ['000', '000', '000', '000', '000', '000', '000', ... ]}
-
You can then visualize the results using Matplotlib.
import pylab as pl pl.hist(results["c0"]) pl.ylabel("Counts") pl.xlabel("Bitstring")
Looking at the histogram, you may notice that the random number generator returned 0 every time, which is not very random. This is because that, while the API Validator ensures that your code will run successfully on Quantinuum hardware, it also returns 0 for every quantum measurement. For a true random number generator, you need to run your circuit on quantum hardware.
-
Before running a job on the QPU, you can estimate how much it will cost to run. To estimate the cost of running a job on the QPU, you can use the
estimate_cost
method.target = workspace.get_targets(name="quantinuum.qpu.h1-1") cost = target.estimate_cost(circuit, shots=500) print(f"Estimated cost: {cost.estimated_total}")
This prints the estimated cost in H-System Quantum Credits (HQCs).
[!NOTE] To run a cost estimate against a Quantinuum target, you must first reload the azure-quantum Python package with the [qiskit] parameter, and ensure that you have the latest version of Qiskit. For more information, see Update the azure-quantum Python package.
[!NOTE] For the most current pricing details, see Azure Quantum pricing, or find your workspace and view pricing options in the "Provider" tab of your workspace via: aka.ms/aq/myworkspaces.
The easiest way to submit Quil jobs is using the pyquil-for-azure-quantum package, as it allows you to use the tools and documentation of the pyQuil library. Without this package, pyQuil can be used to construct Quil programs but not to submit them to Azure Quantum.
You can also construct Quil programs manually and submit them using the azure-quantum
package directly.
-
First, load the required imports.
from pyquil.gates import CNOT, MEASURE, H from pyquil.quil import Program from pyquil.quilbase import Declare from pyquil_for_azure_quantum import get_qpu, get_qvm
-
Use the
get_qvm
orget_qpu
function to get a connection to the QVM.qc = get_qvm() # For simulation
-
Create a Quil program. Any valid Quil program is accepted, but the readout must be named
ro
.program = Program( Declare("ro", "BIT", 2), H(0), CNOT(0, 1), MEASURE(0, ("ro", 0)), MEASURE(1, ("ro", 1)), ).wrap_in_numshots_loop(5) # Optionally pass to_native_gates=False to .compile() to skip the compilation stage result = qc.run(qc.compile(program)) data_per_shot = result.readout_data["ro"]
-
Here,
data_per_shot
is anumpy
array, so you can usenumpy
methods.assert data_per_shot.shape == (5, 2) ro_data_first_shot = data_per_shot[0] assert ro_data_first_shot[0] == 1 or ro_data_first_shot[0] == 0
-
Print out all the data.
print("Data from 'ro' register:") for i, shot in enumerate(data_per_shot): print(f"Shot {i}: {shot}")
-
First, load the required imports.
from azure.quantum import Workspace from azure.quantum.target.rigetti import Result, Rigetti, RigettiTarget, InputParams
-
Create a
target
object and select the name of the Rigetti's target. For example, the following code selects theQVM
target.target = Rigetti( workspace=workspace, name=RigettiTarget.QVM, )
-
Create a Quil program. Any valid Quil program is accepted, but the readout must be named
ro
.readout = "ro" bell_state_quil = f""" DECLARE {readout} BIT[2] H 0 CNOT 0 1 MEASURE 0 {readout}[0] MEASURE 1 {readout}[1] """ num_shots = 5 job = target.submit( input_data=bell_state_quil, name="bell state", shots=100, input_params=InputParams(skip_quilc=False) ) print(f"Job completed with state: {job.details.status}") result = Result(job) # This throws an exception if the job failed
-
You can index a Result with the name of the readout. In this case,
ro
. Here,data_per_shot
is a list of length num_shots, each entry is a list containing the data for the register for that shot.data_per_shot = result[readout] ro_data_first_shot = data_per_shot[0]
In this case, because the type of the register is BIT, the type will be integer and the value either 0 or 1.
assert isinstance(ro_data_first_shot[0], int) assert ro_data_first_shot[0] == 1 or ro_data_first_shot[0] == 0
-
Print out all the data
print(f"Data from '{readout}' register:") for i, shot in enumerate(data_per_shot): print(f"Shot {i}: {shot}")
Important
Submitting multiple circuits on a single job is currently not supported. 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())
- Submit a circuit with Qiskit to Azure Quantum.
- Submit a circuit with Cirq to Azure Quantum.