Local Emulation of Verbatim Circuits on Amazon Braket
Overview
# --- 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
# 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.
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.
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
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.
emulator_run = cepheus_emulator.run(verbatim_circuit, shots=1000)
emulator_result = emulator_run.result()
emulator_counts = emulator_result.measurement_countsThe result can be visualized as follows.
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.
device_run = cepheus.run(verbatim_circuit, shots=1000)
device_result = device_run.result()
device_counts = device_result.measurement_countsWe 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.
cepheus_emulator.validate(verbatim_circuit)Not surprisingly, the verbatim_circuit passes the validations against Cepheus-1-108Q's device properties, which check
- If the qubits used in the circuit exist on the QPU
- If the gates used in the circuit are native gates of the QPU
- If the two-qubit gates are applied to qubits that are connected in the device's topology
- 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
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.
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
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
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
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
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
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.
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.
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.
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.
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.
from braket.devices import LocalSimulator
local_sim = LocalSimulator()
sim_run = local_sim.run(verbatim_circuit, shots=1000).result()
sim_counts = sim_run.measurement_countsSince we have obtained the (noisy) emulation and QPU results above, we now plot all the results to visualize them.
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
First, we get the probabilities for each result
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
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.
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
# 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
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
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.
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.
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
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.
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.
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)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.
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
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:
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.
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
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
Cite all versions? Use the base QCR ID to always reference the latest version of this entry.
Join the Discussion
Comments (0)
No comments yet. Be the first to share your thoughts!