Quickstart: Submit a circuit with Cirq using an Azure Quantum notebook

Learn how to use the Azure Quantum service to submit a Cirq quantum circuits to an IonQ or Quantinuum quantum computing target via the Azure Quantum service. This example uses an Azure Quantum notebook and the built-in azure-quantum Python package - no installation or configuration is required. For more information, see Quantum circuits.

Prerequisites

Create a new Notebook in your workspace

  1. Log in to the Azure portal and select the workspace from the previous step.
  2. In the left blade, select Notebooks.
  3. Click My Notebooks and click Add New.
  4. In Kernel Type, select IPython.
  5. Type a name for the file, for example Cirq.ipynb, 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 (
    subscription_id = <your subscription ID>, 
    resource_group = <your resource group>,   
    name = <your workspace name>,          
    location = <your location>        
    )

Note

Unless otherwise noted, you should run each cell in order as you create it to avoid any compilation issues.

Click the triangular "play" icon to the left of the cell to run the code.

Load the required imports

First, you'll need to import an additional module.

Click + Code to add a new cell, then add and run the following code:

from azure.quantum.cirq import AzureQuantumService

Connect to the Azure Quantum service

Next, use an AzureQuantumService constructor to create a service object that connects to your Azure Quantum workspace. Add a new cell, but don't run it yet, with the following code:

service = AzureQuantumService(
  resource_id="",
  location=""
)

Before running this cell, your program needs the resource ID and the location of your Azure Quantum workspace:

  1. Click Save to save your notebook.
  2. Click Overview to view the workspace properties.
  3. Hover over the Resource ID field and click the Copy to clipboard icon.
  4. Click Notebooks and open your Cirq notebook.
  5. Paste the resource ID into the value for resource_id, and then add the location string from the first cell to location.
  6. Run the cell.

Screenshot of the overview pane showing the details of your Azure Quantum workspace.

Define 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────────

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("This workspace's targets:")
for target in service.targets():
     print(target)
This workspace's 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>

Select a target and run your program

To check your circuit before running it on actual quantum hardware, you can use the IonQ simulator, ionq.simulator.

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,
    target="ionq.simulator"
)

This returns a cirq.Result object.

print(result)
    b=1001100101100001000011011101000011010100010111100011001000100100010000001110010010101110110000011010, 1001100101100001000011011101000011010100010111100011001000100100010000001110010010101110110000011010

You can plot the results in a histogram:

import pylab as pl

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

Estimate job cost

Before running a job on actual quantum hardware, or a quantum processing unit (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}")
Estimated cost: 1

This prints the estimated cost in USD.

For the most current pricing details, see IonQ Pricing, or view pricing options in the Providers blade of your workspace. To see your current credit status and usage, select Credits and quotas.

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, or 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
)

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.

Again, this returns a cirq.Result object.

print(result)
    b=1001100101100001000011011101000011010100010111100011001000100100010000001110010010101110110000011010, 1001100101100001000011011101000011010100010111100011001000100100010000001110010010101110110000011010

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

The job.results() function 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

Define a simple circuit

Next, create a simple Cirq circuit to run. In a new cell, add and run the following code to create and display a random number generator circuit.

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────────

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("This workspace's targets:")
for target in service.targets():
     print(target)
This workspace's 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>

Select a target and run your program

To check your circuit before running it on actual quantum hardware, you can use the Quantinuum API Validator, quantinuum.sim.h1-1sc.

Add the following cell that submits a job to run the circuit with 100 shots, or repetitions, waits until the job is complete, and returns the results:

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

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 actual quantum hardware, or a quantum processing unit (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="quantinuum.qpu.h1-1"
)

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

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

For the most current pricing details, see Azure Quantum pricing, or view pricing options in the Providers blade of your workspace. To see your current credit status and usage, select Credits and quotas.

Run on a Quantinuum QPU

After running successfully on the API validator and estimating the QPU cost, it's time to run your circuit on the hardware.

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.

Use the same run method and operations that you used previously with the API Validator to submit your job and display the results:

result = service.run(
    program=circuit,
    repetitions=100,
    target="quantinuum.qpu.h1-1"
)
pl.hist(result.data)
pl.ylabel("Counts")
pl.xlabel("Result")

You can see that the results now are roughly divided between 0 and 1.

Cirq circuit result on Quantinuum QPU

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.qpu.h1-1"
)

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': ['11', '11', '00', '11', '00', '00', '00', '00', '11', '00', '00', '11', '00', '00', '00', '00', '11', '11', '11', '00', '11', '00', '00', '11', '11', '11', '11', '00', '00', '00', '00', '00', '00', '11', '11', '00', '11', '00', '11', '11', '00', '00', '00', '11', '11', '00', '00', '11', '11', '11', '00', '00', '11', '11', '11', '11', '00', '00', '11', '11', '00', '11', '11', '00', '00', '00', '11', '11', '11', '11', '11', '00', '00', '11', '00', '11', '11', '11', '11', '00', '11', '00', '00', '00', '01', '11', '11', '00', '00', '11', '11', '11', '11', '00', '00', '00', '11', '00', '11', '00']}

Note

The job.results() function 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())