Quickstart: Submit a circuit with Cirq to Azure Quantum

Note

The examples in this topic use the Microsoft Quantum Development Kit (Classic QDK) and are not yet compatible with the Azure Quantum Development Kit Preview (Modern QDK). For more information about the Modern QDK, see the QSharp GitHub Wiki.

Learn how to use the azure-quantum Python package to submit Cirq quantum circuits to an IonQ or Quantinuum quantum computing target via the Azure Quantum service. For more information, see Quantum circuits.

For information on how to submit a circuit with Qiskit, see Quickstart: Submit a circuit with Qiskit to Azure Quantum.

Prerequisites

  • An Azure account with an active subscription. Create an account for free.

  • Create an Azure Quantum workspace and enable your preferred provider, Quantinuum or IonQ (or both), for this scenario. For more information, see Create an Azure Quantum workspace.

  • Install the latest azure-quantum Python package using the [cirq] tag.

    Tip

    If you are using Miniconda or Anaconda, you can optionally create a new environment by downloading environment.yml and running the following:

    conda env create -f environment.yml
    

    This creates a new conda environment that you can activate with the following:

    conda activate azurequantum
    
  • Start your favorite code editor or interactive Python tool, such as VS Code, Jupyter or iPython.

Load the required imports

First, run the following cell to load the required imports:

from azure.quantum.cirq import AzureQuantumService

Connect to the Azure Quantum service

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, navigate to your Azure Quantum workspace, and copy the values from the header.

How to retrieve the resource ID and location from an Azure Quantum workspace

Paste the values into the following AzureQuantumService constructor to create a service object that connects to your Azure Quantum workspace. Optionally, you can specify a default target, in this case the IonQ simulator:

service = AzureQuantumService(
    resource_id="",
    location="",
    default_target="ionq.simulator"
)

List all targets

Note

The target names for the Quantinuum Syntax Checkers, Emulators, and QPUs have recently changed. The updated names are used in this topic. For details, see the Quantinuum provider topic.

Use the targets()method to list all the targets in your workspace that can run your circuit, including the current queue time and availability.

Note

All the targets in your workspace may not be listed - only the targets that can accept a Cirq or OpenQASM circuit will be listed here.

print(service.targets())
[<Target name="quantinuum.qpu.h1-1", avg. queue time=0 s, Degraded>,
<Target name="quantinuum.sim.h1-1sc", avg. queue time=1 s, Available>,
<Target name="quantinuum.qpu.h1-2", avg. queue time=217300 s, Unavailable>,
<Target name="quantinuum.sim.h1-2sc", avg. queue time=0 s, Available>,
<Target name="quantinuum.sim.h1-1e", avg. queue time=40 s, Available>,
<Target name="quantinuum.sim.h1-2e", avg. queue time=64 s, Available>,
<Target name="ionq.qpu", avg. queue time=229 s, Available>,
<Target name="ionq.simulator", avg. queue time=3 s, Available>,
<Target name="ionq.qpu.aria-1", avg. queue time=1136774 s, Available>]

Run a simple circuit

Next, create a simple Cirq circuit to run. This circuit uses the square root of X gate, native to the IonQ hardware system.

import cirq

q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit(
    cirq.X(q0)**0.5,             # Square root of X
    cirq.CX(q0, q1),              # CNOT
    cirq.measure(q0, q1, key='b') # Measure both qubits
)
print(circuit)
0: ───X^0.5───@───M────────
              │   │
1: ───────────X───M────────

You can now run the program via the Azure Quantum service and get the result. The following cell submits a job (to the default IonQ simulator) that runs the circuit with 100 shots, waits until the job is complete, and returns the results.

result = service.run(program=circuit, repetitions=100)

This returns a cirq.Result object.

print(result)
    b=1001100101100001000011011101000011010100010111100011001000100100010000001110010010101110110000011010, 1001100101100001000011011101000011010100010111100011001000100100010000001110010010101110110000011010

Estimate job cost

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:

cost = service.estimate_cost(
    program=circuit,
    repetitions=100,
    target="ionq.qpu"
)

print(f"Estimated cost: {cost.estimated_total}")

This prints the estimated cost in USD.

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

The previous job ran on the default simulator, "ionq.simulator". However, you can also run it on IonQ's hardware processor (a Quantum Processor Unit (QPU)). To run on the IonQ QPU, provide "ionq.qpu" as the target argument:

result = service.run(
    program=circuit,
    repetitions=100,
    target="ionq.qpu",
    timeout_seconds=500 # Set timeout to accommodate queue time on QPU
)

Again, this returns a cirq.Result object.

print(result)
b=0101011011011111100001011101101011011110100010000000011110111000100100110110101100110001001111101111, 0101011011011111100001011101101011011110100010000000011110111000100100110110101100110001001111101111

Asynchronous model using Jobs

For long-running circuits, it can be useful to run them asynchronously. The service.create_job method returns a Job object, which you can use to get the results after the job has run successfully.

job = service.create_job(
    program=circuit,
    repetitions=100,
    target="ionq.simulator"
)

To check on the job status, use job.status():

print(job.status())
'completed'

To wait for the job to complete and then get the results, use the blocking call job.results():

result = job.results()
print(result)
00: 0.5
11: 0.5

Note that this does not return a cirq.Result object. Instead it returns a result object that is specific to the IonQ simulator and uses state probabilities instead of shot data.

type(result)
cirq_ionq.results.SimulatorResult

To convert this to a cirq.Result object, use result.to_cirq_result():

print(result.to_cirq_result())
b=1110101111111110111000011101011111001100010000001011011101001111001111001101100111010000001100011100, 1110101111111110111000011101011111001100010000001011011101001111001111001101100111010000001100011100

Load the required imports

First, run the following cell to load the required imports:

from azure.quantum.cirq import AzureQuantumService

Connect to the Azure Quantum service

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, navigate to your Azure Quantum workspace, and copy the values from the header.

How to retrieve the resource ID and location from an Azure Quantum workspace

Paste the values into the following AzureQuantumService constructor to create a service object that connects to your Azure Quantum workspace. Optionally, specify a default target:

from azure.quantum.cirq import AzureQuantumService
service = AzureQuantumService(
    resource_id="",
    location="",
    default_target="quantinuum.sim.h1-1sc"
)

List all targets

Note

The target names for the Quantinuum Syntax Checkers, Emulators, and QPUs have recently changed. The updated names are used in this topic. For details, see the Quantinuum provider topic.

Use the targets()method to list all the targets in your workspace that can run your circuit, including the current queue time and availability.

Note

All the targets in your workspace may not be listed - only the targets that can accept a Cirq or OpenQASM circuit will be listed here.

print(service.targets())
[<Target name="quantinuum.qpu.h1-1", avg. queue time=0 s, Degraded>,
<Target name="quantinuum.sim.h1-1sc", avg. queue time=1 s, Available>,
<Target name="quantinuum.qpu.h1-2", avg. queue time=217300 s, Unavailable>,
<Target name="quantinuum.sim.h1-2sc", avg. queue time=0 s, Available>,
<Target name="quantinuum.sim.h1-1e", avg. queue time=40 s, Available>,
<Target name="quantinuum.sim.h1-2e", avg. queue time=64 s, Available>,
<Target name="ionq.qpu", avg. queue time=229 s, Available>,
<Target name="ionq.simulator", avg. queue time=3 s, Available>,
<Target name="ionq.qpu.aria-1", avg. queue time=1136774 s, Available>]

Run a simple circuit on the API validator

Next, create a simple Cirq circuit to run.

import cirq

q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit(
    cirq.H(q0), # Hadamard
    cirq.CNOT(q0, q1), # CNOT
    cirq.measure(q0, q1, key='b') # Measure both qubits
)
print(circuit)
0: ───H───@───M────────
          │   │
1: ───────X───M────────

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, waits until the job is complete, and returns the results.

result = service.run(program=circuit, repetitions=100)

This returns a cirq.Result object.

print(result)
    b=0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

You can plot the results in a histogram:

import pylab as pl

pl.hist(result.data)
pl.ylabel("Counts")
pl.xlabel("Result")

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.

Estimate job cost

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.

Note

To run a cost estimate against a Quantinuum target, you must first reload the azure-quantum Python package with the [qiskit] parameter. For more information, see Update the azure-quantum Python package.

cost = service.estimate_cost(
    program=circuit,
    repetitions=100,
    target="quantinuum.qpu.h1-1"
)

print(f"Estimated cost: {cost.estimated_total}")

This prints the estimated cost in H-System Credits (HQCs).

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.

Asynchronous workflow using Jobs

For long-running circuits, it can be useful to run them asynchronously. The service.create_job method returns a Job object, which you can use to get the results after the job has run successfully.

job = service.create_job(
    program=circuit,
    repetitions=100,
    target="quantinuum.sim.h1-1sc"
)

To check on the job status, use job.status():

print(job.status())
'Waiting'

To wait for the job to complete and then get the results, use the blocking call job.results():

result = job.results()
print(result)
    {'m_b': ['00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00']}

Note that this does not return a cirq.Result object. Instead, it returns a dictionary of bitstring measurement results indexed by measurement key.

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())