Tutorials
qcr:2607.14964.1

Local Emulation of Verbatim Circuits on Amazon Braket

This intermediate tutorial introduces Amazon Braket's local emulator, a tool that reproduces the behavior of a target QPU on a developer's own machine by combining device calibration data with a density-matrix noise model. Instantiating an emulator directly from the Rigetti Cepheus-1-108Q device fetches up-to-date calibration data, after which the notebook builds a four-qubit verbatim circuit from the device's native rx, rz, and cz gates and runs it locally to obtain realistic, noisy measurement statistics. It walks through the emulator's validation stage, which checks that the circuit uses only qubits that exist on the device, only native gates, only two-qubit gates on connected pairs, and only supported result types and observables, deliberately triggering each failure to illustrate the error messages. It then inspects the automatically constructed noise model of per-gate depolarizing channels and per-qubit readout bit-flip errors, compares emulator, noiseless-simulator, and real-QPU results by computing bit-string fidelities, and shows how to load historical or custom calibration data from an IQM Garnet properties file to emulate devices offline and sweep gate noise rates. It also emulates parameter-sweep program sets. It matters because it lets developers catch hardware-compatibility issues and iterate on noise-aware algorithms without incurring QPU charges.
Compilation
Qubit
Circuit-based
Uploaded 4 weeks ago
4
Views
GitHub587
Citing this entry? Use this QCR ID
Uploaded by
QL
QCR Librarian

Overview

amazon-braket/amazon-braket-examples
587261
In [ ]:
# --- Setup cell added by QCR (not part of the original tutorial) ---
# Source: amazon-braket/amazon-braket-examples @ 0c0818f315479aab9deebed7e7ed7533ac581923, Apache License 2.0.
# Installs the example's dependencies. If a later cell still reports a missing
# package, restart the runtime/kernel and run again from the top.
%pip install -q amazon-braket-sdk==1.117.3 matplotlib pandas

Local emulation for verbatim circuits on Amazon Braket

This notebook introduces the local emulator, a tool that enables quantum developers to emulate verbatim circuits locally based on device calibration data. By using the local emulator, you can:

  • Validate your verbatim circuits against specific device constraints, using either real-time or historical calibration data
  • Debug issues before submitting tasks to the target hardware device
  • Understand the effect of noise by comparing noiseless and noisy emulations to actual hardware results

This notebook will walk you through the steps to utilize the local emulator for these use cases, showing how it can help you accelerate quantum algorithm development.

Table of Contents

  • Emulating a verbatim circuit before submitting to a target QPU
  • Validating a verbatim circuit with the local emulator
  • Comparing emulator results to QPU results
  • Emulating a verbatim circuit with custom device properties
  • Summary
In [1]:
# Use Braket SDK Cost Tracking to estimate the cost to run this example
from braket.tracking import Tracker

t = Tracker().start()

Emulating a verbatim circuit before submitting to a target QPU

One of the benefits of using local emulator is that it allows one to more efficiently test their program before running on a target device. Here, we will be using the Rigetti Cepheus-1-108Q as an example.

In [2]:
from braket.aws.aws_device import AwsDevice

cepheus = AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Cepheus-1-108Q")
cepheus_emulator = cepheus.emulator()

Here, the local emulator is instantiated from the target device, which automatically fetches the up-to-date calibration data of the device for validation and noisy simulation. Let us examine the native gates supported by the device.

In [3]:
native_gates = cepheus.properties.paradigm.nativeGateSet
print(f"Native gates for {cepheus.name}: {native_gates}")
Native gates for Cepheus-1-108Q: ['rx', 'rz', 'cz', 'barrier']

Now we create and visualize a 4-qubit circuit made out of these native gates

In [4]:
from numpy import pi

from braket.circuits import Circuit

verbatim_circuit = Circuit().add_verbatim_box(
    Circuit()
    .rx(0, pi / 2)
    .rz(1, pi)
    .cz(0, 1)
    .rx(2, -pi / 2)
    .rz(3, pi / 2)
    .cz(2, 3)
    .cz(1, 2)
    .rx(0, -pi / 2)
    .rz(3, -pi)
)
print(verbatim_circuit)
T  : │        0        │      1      │  2  │      3      │       4       │
                        ┌──────────┐        ┌───────────┐                 
q0 : ───StartVerbatim───┤ Rx(1.57) ├────●───┤ Rx(-1.57) ├───EndVerbatim───
              ║         └──────────┘    │   └───────────┘        ║        
              ║         ┌──────────┐  ┌─┴─┐                      ║        
q1 : ─────────║─────────┤ Rz(3.14) ├──┤ Z ├───────●──────────────║────────
              ║         └──────────┘  └───┘       │              ║        
              ║         ┌───────────┐           ┌─┴─┐            ║        
q2 : ─────────║─────────┤ Rx(-1.57) ├───●───────┤ Z ├────────────║────────
              ║         └───────────┘   │       └───┘            ║        
              ║         ┌──────────┐  ┌─┴─┐ ┌───────────┐        ║        
q3 : ─────────╨─────────┤ Rz(1.57) ├──┤ Z ├─┤ Rz(-3.14) ├────────╨────────
                        └──────────┘  └───┘ └───────────┘                 
T  : │        0        │      1      │  2  │      3      │       4       │

You can use the emulator to run the circuit and retrieve results, just as you would with another device.

In [5]:
emulator_run = cepheus_emulator.run(verbatim_circuit, shots=1000)
emulator_result = emulator_run.result()
emulator_counts = emulator_result.measurement_counts

The result can be visualized as follows.

In [6]:
from matplotlib import pyplot as plt

plt.bar(
    sorted(emulator_counts.keys()), [emulator_counts[k] for k in sorted(emulator_counts.keys())]
)
plt.xlabel("Bitstrings")
plt.ylabel("Counts")
plt.xticks(rotation=45)
plt.show()

The noise in the device (or local emulator) manifests as the non-zero count in bitstrings, say 0100, which would otherwise be absent in a noiseless simulation result (see below). If one is satisfied with the emulation result, they can simply proceed to submit the circuit to the target QPU.

Note: To run the next cell, make sure the device is currently available and note that it will result in charges to your AWS account.
In [7]:
device_run = cepheus.run(verbatim_circuit, shots=1000)
device_result = device_run.result()
device_counts = device_result.measurement_counts

We have demonstrated an end-to-end workflow of the local emulator with a valid verbatim circuit. As noted above, running a circuit on the quantum device requires that the device to be available and will result in charges toyour AWS account. Suppose you are developing a noise-aware quantum algorithm and need to iterate multiple times on the device, in which case running on the actual quantum device could be inconvenient and costly. The local emulator provides another way to develop such algorithms, provided that the result from the emulator is close to those you'd get the target device. We will dive deeper into comparing QPU and emulater results for the above example in a later section. For now, we will take a detour and explain the validation behind the emulation.

Validating a verbatim circuit with the local emulator

Before performing the noisy emulation of the verbatim circuit, the local emulator will first validate the circuit against the calibration data of the target QPU. In particular, before performing the noisy simulation, the local emulator will validate the input circuit as follows.

In [8]:
cepheus_emulator.validate(verbatim_circuit)

Not surprisingly, the verbatim_circuit passes the validations against Cepheus-1-108Q's device properties, which check

  1. If the qubits used in the circuit exist on the QPU
  2. If the gates used in the circuit are native gates of the QPU
  3. If the two-qubit gates are applied to qubits that are connected in the device's topology
  4. If the result types are supported by the QPU

If the verbatim circuit fails any of the above validations, the local emulation will halt and output the corresponding error message. Below we will show some invalid circuits for each of the above cases.

1. Qubit doesn't exist on the device

Cepheus-1-108Q has 108 qubits, so applying a gate on a qubit index that doesn't exist on the device will throw an error

In [9]:
invalid_circuit = Circuit().add_verbatim_box(Circuit().rx(110, pi / 2))

try:
    result = cepheus_emulator.run(invalid_circuit, shots=1000)
    print("Success!")
except Exception as e:
    print(f"Failure with error message: {str(e)}")
Failure with error message: Qubit Qubit(110) does not exist in the device topology.

If one is only interested in validating the circuit and not the noisy simulation result, they could simply validate the circuit as follows.

In [10]:
try:
    cepheus_emulator.validate(invalid_circuit)
    print("Success!")
except Exception as e:
    print(f"Failure with error message: {str(e)}")
Failure with error message: Qubit Qubit(110) does not exist in the device topology.

2. Using non-native gates

As we have seen above, the native gate set for Cepheus-1-108Q is rx, rz, and cz. Thus, if we apply a Hadamard gate we will get an error

In [11]:
invalid_circuit = Circuit().add_verbatim_box(Circuit().h(0))

try:
    result = cepheus_emulator.validate(invalid_circuit)
    print("Success!")
except Exception as e:
    print(f"Failure with error message: {str(e)}")
Failure with error message: Gate H is not a native gate for this device.

3. Applying two-qubit gates on unconnected qubits

On Cepheus-1-108Q, qubits 0 and 2 are not connected, so we cannot apply a two-qubit gate on them

In [12]:
invalid_circuit = Circuit().add_verbatim_box(Circuit().cz(0, 2))

try:
    result = cepheus_emulator.validate(invalid_circuit)
    print("Success!")
except Exception as e:
    print(f"Failure with error message: {str(e)}")
Failure with error message: 0 is not connected to qubit 2 in this device.

4. The result types or the observables are not supported on the QPU.

On Cepheus-1-108Q (and other QPUs), the supported result types include Sample, Expectation, Variance and Probability, which can be inspected as follows

In [13]:
cepheus.properties.action['braket.ir.openqasm.program'].supportedResultTypes
[ResultType(name='Sample', observables=['x', 'y', 'z', 'h', 'i'], minShots=10, maxShots=50000),
 ResultType(name='Expectation', observables=['x', 'y', 'z', 'h', 'i'], minShots=10, maxShots=50000),
 ResultType(name='Variance', observables=['x', 'y', 'z', 'h', 'i'], minShots=10, maxShots=50000),
 ResultType(name='Probability', observables=None, minShots=10, maxShots=50000)]

Circuits with other result types, for example StateVector, will give an error

In [14]:
invalid_circuit = Circuit().add_verbatim_box(Circuit().rx(0, pi/2)).state_vector()

try:
    result = cepheus_emulator.validate(invalid_circuit)
    print("Success!")
except Exception as e:
    print(f"Failure with error message: {str(e)}")
Failure with error message: The result type StateVector is not a supported result type for this device. Check the device documentation for a list of supported result types.

Circuits with supported result type but unsupported observables, say TensorProduct, will also give an error

In [15]:
from braket.circuits.observables import X, Z

invalid_circuit = Circuit().add_verbatim_box(Circuit().rx(0, pi/2).cz(0,1)).sample(2.0 * Z(0) @ Z(1) - 3.0 * X(0))

try:
    result = cepheus_emulator.validate(invalid_circuit)
    print("Success!")
except Exception as e:
    print(f"Failure with error message: {str(e)}")
Failure with error message: Observable sum is not supported for result type Sample on this device. Supported observables are: ['x', 'y', 'z', 'h', 'i'].

5. Inspecting the noisy verbatim circuit

Once the verbatim circuit passes the validation, the local emulator will apply depolarizing noise to each of the gates in the circuit and readout error to each qubit at the end of the circuit to mimic the noise in the target QPU. The noise model is constructed based on the device properties, and more details can be found in this notebook. The noisy circuit will be then simulated using the local density matrix simulator (see this notebook for more details on noisy simulator). We can inspect the noisy circuit as following.

In [9]:
import os

os.environ["BRAKET_DIAGRAM_WIDTH"] = "135"

noisy_circ = cepheus_emulator.transform(verbatim_circuit)
print(noisy_circ)
T  : │        0        │              1               │          2           │              3               │  ╏
                        ┌──────────┐  ┌──────────────┐       ┌──────────────┐ ┌───────────┐ ┌──────────────┐   ╏
q0 : ───StartVerbatim───┤ Rx(1.57) ├──┤ DEPO(0.0024) ├───●───┤ DEPO(0.0057) ├─┤ Rx(-1.57) ├─┤ DEPO(0.0024) ├─  ╏
              ║         └──────────┘  └──────────────┘   │   └──────┬───────┘ └───────────┘ └──────────────┘   ╏
              ║         ┌──────────┐  ┌──────────────┐ ┌─┴─┐ ┌──────┴───────┐               ┌─────────────┐    ╏
q1 : ─────────║─────────┤ Rz(3.14) ├──┤ DEPO(0.0014) ├─┤ Z ├─┤ DEPO(0.0057) ├───────●───────┤ DEPO(0.013) ├──  ╏
              ║         └──────────┘  └──────────────┘ └───┘ └──────────────┘       │       └──────┬──────┘    ╏
              ║         ┌───────────┐ ┌──────────────┐       ┌─────────────┐      ┌─┴─┐     ┌──────┴──────┐    ╏
q2 : ─────────║─────────┤ Rx(-1.57) ├─┤ DEPO(0.0083) ├───●───┤ DEPO(0.013) ├──────┤ Z ├─────┤ DEPO(0.013) ├──  ╏
              ║         └───────────┘ └──────────────┘   │   └──────┬──────┘      └───┘     └─────────────┘    ╏
              ║         ┌──────────┐  ┌──────────────┐ ┌─┴─┐ ┌──────┴──────┐  ┌───────────┐ ┌──────────────┐   ╏
q3 : ─────────╨─────────┤ Rz(1.57) ├──┤ DEPO(0.0066) ├─┤ Z ├─┤ DEPO(0.013) ├──┤ Rz(-3.14) ├─┤ DEPO(0.0066) ├─  ╏
                        └──────────┘  └──────────────┘ └───┘ └─────────────┘  └───────────┘ └──────────────┘   ╏
T  : │        0        │              1               │          2           │              3               │  ╏
                                                                                                               ╏
╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸┳╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸┛
                                            ┃                                                                   
T  : │              4              │  5  │  ┃
                      ┌───────────┐ ┌───┐   ┃
q0 : ───EndVerbatim───┤ BF(0.037) ├─┤ M ├─  ┃
             ║        └───────────┘ └───┘   ┃
             ║        ┌───────────┐ ┌───┐   ┃
q1 : ────────║────────┤ BF(0.035) ├─┤ M ├─  ┃
             ║        └───────────┘ └───┘   ┃
             ║        ┌───────────┐ ┌───┐   ┃
q2 : ────────║────────┤ BF(0.078) ├─┤ M ├─  ┃
             ║        └───────────┘ └───┘   ┃
             ║        ┌───────────┐ ┌───┐   ┃
q3 : ────────╨────────┤ BF(0.045) ├─┤ M ├─  ┃
                      └───────────┘ └───┘   ┃
T  : │              4              │  5  │  ┃
                                            ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

We note that since the noise model will be applied by the local emulator, one should not supply noisy_circ as an input circuit for the local emulator or the local density matrix simulator. The noisy_circ is for visualization purpose only. We highlight that a set of measurements will be appended to the circuit if it has no measurement or result type, before which a set of bit-flip noise will be applied to emulate the readout noise. This is illustrated in the above circuit diagram.

6. Brief remark on readout noise

We remark that the noise model won't apply the readout error in two scenarios. The first case is a circuit with ObservableResultType but no target qubits specified.

In [10]:
from braket.circuits import Observable

verbatim_circuit_2 = Circuit().add_verbatim_box(
    Circuit()
    .rx(0, pi / 2)
    .cz(0, 1)
).sample(Observable.X())

noisy_circ_2 = cepheus_emulator.transform(verbatim_circuit_2)
print(noisy_circ_2)
T  : │        0        │              1              │          2           │       3       │Result Types │
                        ┌──────────┐ ┌──────────────┐       ┌──────────────┐                 ┌───────────┐ 
q0 : ───StartVerbatim───┤ Rx(1.57) ├─┤ DEPO(0.0024) ├───●───┤ DEPO(0.0057) ├───EndVerbatim───┤ Sample(X) ├─
              ║         └──────────┘ └──────────────┘   │   └──────┬───────┘        ║        └─────┬─────┘ 
              ║                                       ┌─┴─┐ ┌──────┴───────┐        ║        ┌─────┴─────┐ 
q1 : ─────────╨───────────────────────────────────────┤ Z ├─┤ DEPO(0.0057) ├────────╨────────┤ Sample(X) ├─
                                                      └───┘ └──────────────┘                 └───────────┘ 
T  : │        0        │              1              │          2           │       3       │Result Types │

The second case is qubits with only Probability result type.

In [11]:
verbatim_circuit_3 = Circuit().add_verbatim_box(
    Circuit()
    .rx(0, pi / 2)
    .cz(0, 1)
).probability(0).sample(Observable.X(), 1)

noisy_circ_3 = cepheus_emulator.transform(verbatim_circuit_3)
print(noisy_circ_3)
T  : │        0        │              1              │          2           │              3              │ Result Types  │
                        ┌──────────┐ ┌──────────────┐       ┌──────────────┐                               ┌─────────────┐ 
q0 : ───StartVerbatim───┤ Rx(1.57) ├─┤ DEPO(0.0024) ├───●───┤ DEPO(0.0057) ├───EndVerbatim─────────────────┤ Probability ├─
              ║         └──────────┘ └──────────────┘   │   └──────┬───────┘        ║                      └─────────────┘ 
              ║                                       ┌─┴─┐ ┌──────┴───────┐        ║        ┌───────────┐  ┌───────────┐  
q1 : ─────────╨───────────────────────────────────────┤ Z ├─┤ DEPO(0.0057) ├────────╨────────┤ BF(0.035) ├──┤ Sample(X) ├──
                                                      └───┘ └──────────────┘                 └───────────┘  └───────────┘  
T  : │        0        │              1              │          2           │              3              │ Result Types  │

For the first case, one needs explicitly specify the target qubits for the ObservableResultType, and the for second case, one can simply replace it with .sample(Observable.Z(), 0). For instance, the following circuit has the readout errors applied to both qubits as desired.

In [12]:
verbatim_circuit_4 = Circuit().add_verbatim_box(
    Circuit()
    .rx(0, pi / 2)
    .cz(0, 1)
).sample(Observable.Z(), 0).sample(Observable.X(), 1)

noisy_circ_4 = cepheus_emulator.transform(verbatim_circuit_4)
print(noisy_circ_4)
T  : │        0        │              1              │          2           │              3              │Result Types │
                        ┌──────────┐ ┌──────────────┐       ┌──────────────┐                 ┌───────────┐ ┌───────────┐ 
q0 : ───StartVerbatim───┤ Rx(1.57) ├─┤ DEPO(0.0024) ├───●───┤ DEPO(0.0057) ├───EndVerbatim───┤ BF(0.037) ├─┤ Sample(Z) ├─
              ║         └──────────┘ └──────────────┘   │   └──────┬───────┘        ║        └───────────┘ └───────────┘ 
              ║                                       ┌─┴─┐ ┌──────┴───────┐        ║        ┌───────────┐ ┌───────────┐ 
q1 : ─────────╨───────────────────────────────────────┤ Z ├─┤ DEPO(0.0057) ├────────╨────────┤ BF(0.035) ├─┤ Sample(X) ├─
                                                      └───┘ └──────────────┘                 └───────────┘ └───────────┘ 
T  : │        0        │              1              │          2           │              3              │Result Types │

Comparing emulator results to QPU results

Next, we want to see how well does the emulator capture the device results as compared to the noiseless simulator. Let's start by getting the noiseless simulation result.

In [20]:
from braket.devices import LocalSimulator

local_sim = LocalSimulator()

sim_run = local_sim.run(verbatim_circuit, shots=1000).result()
sim_counts = sim_run.measurement_counts

Since we have obtained the (noisy) emulation and QPU results above, we now plot all the results to visualize them.

In [21]:
import numpy as np

all_bitstrings = sorted(
    set(list(emulator_counts.keys()) + list(device_counts.keys()) + list(sim_counts.keys()))
)

fig, ax = plt.subplots(figsize=(12, 6))
bar_width = 0.25
x = np.arange(len(all_bitstrings))

emulator_bars = ax.bar(
    x - bar_width,
    [emulator_counts.get(b, 0) for b in all_bitstrings],
    bar_width,
    label="Emulator",
    alpha=0.7,
)

device_bars = ax.bar(
    x, [device_counts.get(b, 0) for b in all_bitstrings], bar_width, label="Device", alpha=0.7
)

sim_bars = ax.bar(
    x + bar_width,
    [sim_counts.get(b, 0) for b in all_bitstrings],
    bar_width,
    label="Simulator",
    alpha=0.7,
)

ax.set_xlabel("Bitstrings", fontsize=12)
ax.set_ylabel("Counts", fontsize=12)
ax.set_xticks(x)
ax.set_xticklabels(all_bitstrings, rotation=45)
ax.legend()
plt.tight_layout()
plt.show()

To quantify the discrepancy between the three results, we compute the fidelity between their respective bit-string probabilities, defined for two arrays and as

First, we get the probabilities for each result

In [24]:
import pandas as pd

emulator_probs_dict = emulator_result.measurement_probabilities
sim_probs_dict = sim_run.measurement_probabilities
device_probs_dict = device_result.measurement_probabilities

emulator_probs_df = pd.DataFrame.from_dict(emulator_probs_dict, orient="index").rename(
    columns={0: "emulator"}
)
sim_probs_df = pd.DataFrame.from_dict(sim_probs_dict, orient="index").rename(columns={0: "sim"})
device_probs_df = pd.DataFrame.from_dict(device_probs_dict, orient="index").rename(
    columns={0: "device"}
)
df = device_probs_df.join(sim_probs_df).join(emulator_probs_df)
df
device sim emulator
0000 0.487 0.493 0.428
0010 0.318 0.507 0.398
1011 0.002 NaN NaN
1010 0.046 NaN 0.016
1000 0.044 NaN 0.014
0011 0.018 NaN 0.045
1110 0.011 NaN 0.002
0001 0.027 NaN 0.051
1001 0.005 NaN 0.004
0100 0.010 NaN 0.015
0111 0.002 NaN 0.003
0110 0.017 NaN 0.016
1100 0.010 NaN 0.003
1111 0.002 NaN 0.001
0101 0.001 NaN 0.004

Then we define and compute the fidelity

In [25]:
import numpy as np


def fidelity(p, q):
    return np.sum(np.sqrt(p * q)) ** 2


print(
    f"\nFidelity between Cepheus-1-108Q and noise-free simulator is {fidelity(df['device'], df['sim']):.3f}"
)
print(f"\nFidelity between Cepheus-1-108Q and emulator is {fidelity(df['device'], df['emulator']):.3f}")
Fidelity between Cepheus-1-108Q and noise-free simulator is 0.795

Fidelity between Cepheus-1-108Q and emulator is 0.959

We see that, indeed, the local emulator produces results closer to the QPU result than that of the noiseless simulator.

Emulating a verbatim circuit with custom device properties

In the above example, we have seen how to instantiate a device emulator using up-to-date device properties from an AWS device. Another important application for the local emulator is to perform local validation and emulation using historical calibration data from the AWS devices or custom device properties. For that, let us first load the calibration data from the IQM Garnet device dated July 6th 2025.

In [13]:
import json

with open("garnet_device_properties_20250706.json", "r") as json_file:
    garnet_data_json = json.load(json_file)

The json file for the calibration data of AWS devices can be downloaded from the AWS console or using Braket SDK, as shown in the below commented code

In [14]:
# garnet = AwsDevice("arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet")
# garnet_properties = garnet.properties

# dt_string = garnet_properties.service.updatedAt.strftime("%Y%m%d")

# with open(f"garnet_device_properties_{dt_string}.json", "w") as f:
#     json.dump(garnet_properties.json(), f)

We can confirm the date of the calibration data as follows

In [15]:
garnet_data_dict = json.loads(garnet_data_json)
print(garnet_data_dict["service"]["updatedAt"])
2025-07-06T17:52:53.997626+00:00

A local device emulator can be instantiated using the device properties json as the following

In [16]:
from braket.emulation.local_emulator import LocalEmulator

garnet_emulator = LocalEmulator.from_json(garnet_data_json)

The local emulator can be used in exactly the same way as illustrated above. Let us build several verbatim circuits made of a series of CZ gates, the native two-qubit gate supported on the Garnet device, and check their validity.

In [17]:
def get_verbatim_circuit_garnet(q1, q2, num_cz):
    circ = Circuit()
    for _ in range(num_cz):
        circ.cz(q1, q2)

    circ = Circuit().add_verbatim_box(circ)
    try:
        garnet_emulator.validate(circ)
        print("Success!")
    except Exception as e:
        print(f"Failure with error message: {str(e)}")

    return circ


verbatim_circuit_garnet_1 = get_verbatim_circuit_garnet(0, 1, 10)
verbatim_circuit_garnet_2 = get_verbatim_circuit_garnet(2, 3, 10)
verbatim_circuit_garnet_3 = get_verbatim_circuit_garnet(1, 2, 10)
Failure with error message: 0 is not connected to qubit 1 in this device.
Failure with error message: 2 is not connected to qubit 3 in this device.
Success!

We see that the first two circuits do not comply with the device constraints and the local emulator was able to correctly spot the failures. For the third circuit, if there is no noise, the circuit is equivalent to an identity circuit and the only bitstring output would be 00. On the other hand, the presence of noise indicating that the fidelity of 00 would not be unity and we are interested in how its probability depends on the noise of the CZ gate of the device. For that, let us first visualize the noisy version of verbatim_circuit_garnet_3.

In [18]:
noisy_verbatim_circuit_garnet_3 = garnet_emulator.transform(verbatim_circuit_garnet_3)
print(noisy_verbatim_circuit_garnet_3)
T  : │        0        │          1          │          2          │          3          │          4          │  ╏
                              ┌─────────────┐       ┌─────────────┐       ┌─────────────┐       ┌─────────────┐   ╏
q1 : ───StartVerbatim─────●───┤ DEPO(0.022) ├───●───┤ DEPO(0.022) ├───●───┤ DEPO(0.022) ├───●───┤ DEPO(0.022) ├─  ╏
              ║           │   └──────┬──────┘   │   └──────┬──────┘   │   └──────┬──────┘   │   └──────┬──────┘   ╏
              ║         ┌─┴─┐ ┌──────┴──────┐ ┌─┴─┐ ┌──────┴──────┐ ┌─┴─┐ ┌──────┴──────┐ ┌─┴─┐ ┌──────┴──────┐   ╏
q2 : ─────────╨─────────┤ Z ├─┤ DEPO(0.022) ├─┤ Z ├─┤ DEPO(0.022) ├─┤ Z ├─┤ DEPO(0.022) ├─┤ Z ├─┤ DEPO(0.022) ├─  ╏
                        └───┘ └─────────────┘ └───┘ └─────────────┘ └───┘ └─────────────┘ └───┘ └─────────────┘   ╏
T  : │        0        │          1          │          2          │          3          │          4          │  ╏
                                                                                                                  ╏    
╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸┻╸╸╸┓
                                                                                                                      ╏
T  : │          5          │          6          │          7          │          8          │          9          │  ╏
            ┌─────────────┐       ┌─────────────┐       ┌─────────────┐       ┌─────────────┐       ┌─────────────┐   ╏
q1 : ───●───┤ DEPO(0.022) ├───●───┤ DEPO(0.022) ├───●───┤ DEPO(0.022) ├───●───┤ DEPO(0.022) ├───●───┤ DEPO(0.022) ├─  ╏
        │   └──────┬──────┘   │   └──────┬──────┘   │   └──────┬──────┘   │   └──────┬──────┘   │   └──────┬──────┘   ╏
      ┌─┴─┐ ┌──────┴──────┐ ┌─┴─┐ ┌──────┴──────┐ ┌─┴─┐ ┌──────┴──────┐ ┌─┴─┐ ┌──────┴──────┐ ┌─┴─┐ ┌──────┴──────┐   ╏
q2 : ─┤ Z ├─┤ DEPO(0.022) ├─┤ Z ├─┤ DEPO(0.022) ├─┤ Z ├─┤ DEPO(0.022) ├─┤ Z ├─┤ DEPO(0.022) ├─┤ Z ├─┤ DEPO(0.022) ├─  ╏
      └───┘ └─────────────┘ └───┘ └─────────────┘ └───┘ └─────────────┘ └───┘ └─────────────┘ └───┘ └─────────────┘   ╏
T  : │          5          │          6          │          7          │          8          │          9          │  ╏
                                                                                                                      ╏
╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸┳╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸┛
                                                                  ┃                                                    
T  : │         10          │             11              │ 12  │  ┃
            ┌─────────────┐                 ┌──────────┐  ┌───┐   ┃
q1 : ───●───┤ DEPO(0.022) ├───EndVerbatim───┤ BF(0.02) ├──┤ M ├─  ┃
        │   └──────┬──────┘        ║        └──────────┘  └───┘   ┃
      ┌─┴─┐ ┌──────┴──────┐        ║        ┌───────────┐ ┌───┐   ┃
q2 : ─┤ Z ├─┤ DEPO(0.022) ├────────╨────────┤ BF(0.019) ├─┤ M ├─  ┃
      └───┘ └─────────────┘                 └───────────┘ └───┘   ┃
T  : │         10          │             11              │ 12  │  ┃
                                                                  ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Note that the parameter in the depolarizing channel is extracted from the calibration data of Garnet dated July 6th 2025. For our purpose, we can modify the calibration data for the q1-q2 pair as follows

In [19]:
garnet_data_dict = json.loads(garnet_data_json)
garnet_data_dict["standardized"]["twoQubitProperties"]["1-2"]["twoQubitGateFidelity"][0][
    "fidelity"
] = 1.0
garnet_data_json_modified = json.dumps(garnet_data_dict)

Here we have set the CZ gate for the q1-q2 pair to be a noiseless gate with fidelity 1.0. We can instantiate the local emulator and inspect the noisy circuit again.

In [20]:
garnet_emulator = LocalEmulator.from_json(garnet_data_json_modified)
noisy_verbatim_circuit_garnet_3 = garnet_emulator.transform(verbatim_circuit_garnet_3)
print(noisy_verbatim_circuit_garnet_3)
T  : │        0        │        1        │        2        │        3        │        4        │        5        │        6        │  ╏
                              ┌─────────┐       ┌─────────┐       ┌─────────┐       ┌─────────┐       ┌─────────┐       ┌─────────┐   ╏
q1 : ───StartVerbatim─────●───┤ DEPO(0) ├───●───┤ DEPO(0) ├───●───┤ DEPO(0) ├───●───┤ DEPO(0) ├───●───┤ DEPO(0) ├───●───┤ DEPO(0) ├─  ╏
              ║           │   └────┬────┘   │   └────┬────┘   │   └────┬────┘   │   └────┬────┘   │   └────┬────┘   │   └────┬────┘   ╏
              ║         ┌─┴─┐ ┌────┴────┐ ┌─┴─┐ ┌────┴────┐ ┌─┴─┐ ┌────┴────┐ ┌─┴─┐ ┌────┴────┐ ┌─┴─┐ ┌────┴────┐ ┌─┴─┐ ┌────┴────┐   ╏
q2 : ─────────╨─────────┤ Z ├─┤ DEPO(0) ├─┤ Z ├─┤ DEPO(0) ├─┤ Z ├─┤ DEPO(0) ├─┤ Z ├─┤ DEPO(0) ├─┤ Z ├─┤ DEPO(0) ├─┤ Z ├─┤ DEPO(0) ├─  ╏
                        └───┘ └─────────┘ └───┘ └─────────┘ └───┘ └─────────┘ └───┘ └─────────┘ └───┘ └─────────┘ └───┘ └─────────┘   ╏
T  : │        0        │        1        │        2        │        3        │        4        │        5        │        6        │  ╏
                                                                                                                                      ╏
╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸┳╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸╸┛
                                                                                                                    ┃                  
T  : │        7        │        8        │        9        │       10        │             11              │ 12  │  ┃
            ┌─────────┐       ┌─────────┐       ┌─────────┐       ┌─────────┐                 ┌──────────┐  ┌───┐   ┃
q1 : ───●───┤ DEPO(0) ├───●───┤ DEPO(0) ├───●───┤ DEPO(0) ├───●───┤ DEPO(0) ├───EndVerbatim───┤ BF(0.02) ├──┤ M ├─  ┃
        │   └────┬────┘   │   └────┬────┘   │   └────┬────┘   │   └────┬────┘        ║        └──────────┘  └───┘   ┃
      ┌─┴─┐ ┌────┴────┐ ┌─┴─┐ ┌────┴────┐ ┌─┴─┐ ┌────┴────┐ ┌─┴─┐ ┌────┴────┐        ║        ┌───────────┐ ┌───┐   ┃
q2 : ─┤ Z ├─┤ DEPO(0) ├─┤ Z ├─┤ DEPO(0) ├─┤ Z ├─┤ DEPO(0) ├─┤ Z ├─┤ DEPO(0) ├────────╨────────┤ BF(0.019) ├─┤ M ├─  ┃
      └───┘ └─────────┘ └───┘ └─────────┘ └───┘ └─────────┘ └───┘ └─────────┘                 └───────────┘ └───┘   ┃
T  : │        7        │        8        │        9        │       10        │             11              │ 12  │  ┃
                                                                                                                    ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

In the above diagram DEPO(0) indidates a depolarizing channel with noise rate equal to 0, for which we expect that the circuit would return 00 with unit probability. Let us vary the noise rate of the CZ gate for the q1-q2 pair and plot the probability of 00 from the local emulator.

In [34]:
fidelity_list = [
    i / 100
    for i in range(
        90,
        101,
    )
]
shots = 1000
counts_list = []
for fidelity in fidelity_list:
    garnet_data_dict = json.loads(garnet_data_json_modified)
    garnet_data_dict["standardized"]["twoQubitProperties"]["1-2"]["twoQubitGateFidelity"][0][
        "fidelity"
    ] = fidelity
    garnet_data_json_modified = json.dumps(garnet_data_dict)

    garnet_emulator = LocalEmulator.from_json(garnet_data_json_modified)
    counts = garnet_emulator.run(verbatim_circuit_garnet_3, shots=shots).result().measurement_counts
    counts_list.append(counts.get("00", 0) / shots)
In [35]:
import matplotlib.pyplot as plt

plt.plot(fidelity_list, counts_list, marker="o")
plt.xlabel("CZ gate fidelity")
plt.ylabel("Probability of 00")
plt.title("Probability of 00 vs CZ gate fidelity")
plt.show()

Indeed, we see that the probability of obtaining 00 is increasing as we increase the CZ gate fidelity. This example shows how one could use custom device properties to perform local emulation, which could useful for developing noise-aware algorithms, or characterizing the target device.

Emulating program sets with the local emulator

When supported by the device, the local emulator also supports program sets, which allow you to batch multiple circuits into a single task. This is useful for parameter sweeps where you want to evaluate a circuit at many different parameter values under a realistic noise model.

Here we sweep the rotation angle theta of an Rx gate and observe how the measurement statistics change with noise.

In [36]:
from braket.program_sets import ProgramSet

theta_values = [i * pi / 8 for i in range(9)]  # 0 to pi in steps of pi/8

verbatim_circuits = [
    Circuit().add_verbatim_box(Circuit().rx(0, theta).rz(1, pi).cz(0, 1))
    for theta in theta_values
]

SHOTS_PER_EXECUTABLE = 200
program_set = ProgramSet(verbatim_circuits, shots_per_executable=SHOTS_PER_EXECUTABLE)
print(f"Program set: {program_set.total_executables} circuits, {program_set.total_shots} total shots")
Program set: 9 circuits, 1800 total shots
In [37]:
ps_result = cepheus_emulator.run(program_set, shots=program_set.total_shots).result()

# Extract P(00) for each theta value
p00_noisy = []
for i in range(len(theta_values)):
    counts = ps_result[i][0].counts
    total = sum(counts.values())
    p00_noisy.append(counts.get("00", 0) / total)

print("P(00) for each theta:")
for theta, p in zip(theta_values, p00_noisy):
    print(f"  theta={theta:.3f}: P(00)={p:.3f}")
P(00) for each theta:
  theta=0.000: P(00)=0.915
  theta=0.393: P(00)=0.875
  theta=0.785: P(00)=0.715
  theta=1.178: P(00)=0.635
  theta=1.571: P(00)=0.540
  theta=1.963: P(00)=0.360
  theta=2.356: P(00)=0.150
  theta=2.749: P(00)=0.050
  theta=3.142: P(00)=0.040

We can compare the noisy emulation results against the ideal (noiseless) simulation:

In [38]:
from braket.devices import LocalSimulator

ideal_sim = LocalSimulator()
ideal_result = ideal_sim.run(program_set, shots=program_set.total_shots).result()

p00_ideal = []
for i in range(len(theta_values)):
    counts = ideal_result[i][0].counts
    total = sum(counts.values())
    p00_ideal.append(counts.get("00", 0) / total)

plt.plot(theta_values, p00_ideal, "o-", label="Ideal (noiseless)")
plt.plot(theta_values, p00_noisy, "s--", label="Emulator (noisy)")
plt.xlabel(r"$\theta$")
plt.ylabel("P(00)")
plt.title(r"Effect of noise on Rx($\theta$)-Rz($\pi$)-iSWAP circuit")
plt.legend()
plt.show()

Summary

In summary, we have illustrated how to incorporate the local emulator into the workflow of quantum algorithm development, using either the real-time calibration data from Braket devices or custom device properties. As shown above, local device emulator allows one to catch compatibility issues earlier for verbatim circuits run on a target device. Apart from that, it enables developers to predict program behavior without the need to construct the noise model manually, simplifying the development process for noise-aware algorithms.

In [39]:
print("Quantum Task Summary")
print(t.quantum_tasks_statistics())
print(
    "Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2).",
)
print(
    f"Estimated cost to run this example: {t.qpu_tasks_cost() + t.simulator_tasks_cost():.3f} USD",
)
Quantum Task Summary
{'arn:aws:braket:us-west-1::device/qpu/rigetti/Cepheus-1-108Q': {'shots': 1000, 'tasks': {'COMPLETED': 1}}}
Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2).
Estimated cost to run this example: 0.725 USD
In [ ]:

Join the Discussion

Comments (0)

No comments yet. Be the first to share your thoughts!

Indexed by QCR Librarian

This entry was created automatically from publicly available records. QCR links to public sources and only stores repository content where the license permits redistribution.

Versions

v1 Latest
Jul 14, 2026
qcr:2607.14964.1

Cite all versions? Use the base QCR ID to always reference the latest version of this entry.

Tools used

Amazon Braket SDK

Keywords

braket
local-emulator
verbatim-circuits
device-calibration
noise-aware
qpu-emulation
program-sets

You may also like5