Amazon Braket Beginner Free 8/11 in series 10 min read

Amazon Braket Hello World

Run your first quantum circuit with Amazon Braket. Build a Bell state on the local simulator, then learn how to submit jobs to real quantum hardware on AWS.

What you'll learn

  • braket
  • aws
  • cloud quantum computing
  • bell state

Prerequisites

  • Basic Python (variables, functions, loops)
  • No quantum physics background needed

Amazon Braket is AWS’s managed quantum computing service. Its key value proposition is multi-hardware access through a single SDK. While Qiskit connects you to IBM hardware and Cirq targets Google hardware, Braket gives you unified access to quantum processors built on fundamentally different qubit technologies:

  • IonQ (trapped ions): long coherence times and all-to-all qubit connectivity, meaning any qubit can interact directly with any other qubit. This eliminates the need for SWAP gates that plague limited-connectivity hardware.
  • IQM and Rigetti (superconducting qubits): fast gate operations (nanosecond scale), but shorter coherence times and limited connectivity between qubits. Circuits that require non-adjacent qubit interactions need extra SWAP gates.
  • QuEra (neutral atoms): particularly well suited for analog quantum simulation, where you define a Hamiltonian and let the system evolve rather than specifying individual gates.

This multi-hardware model matters in practice. Different algorithms perform better on different qubit technologies, and Braket lets you benchmark the same circuit across hardware types with minimal code changes. You swap out a single device ARN string and rerun your code.

You can run the local simulator for free without an AWS account. Real hardware tasks are billed per shot, with pricing that varies by device.

Installation

pip install amazon-braket-sdk amazon-braket-default-simulator

The amazon-braket-sdk package provides the Circuit class and device interfaces. The amazon-braket-default-simulator package provides local simulators (statevector, density matrix) so you can develop and test without any AWS credentials.

Your First Circuit

Braket circuits use a fluent interface pattern (also called method chaining). Each gate method modifies the circuit in place and then returns the circuit object itself, so you can chain operations on a single line or stack them across multiple lines.

from braket.circuits import Circuit
from braket.devices import LocalSimulator

# Build a Bell state circuit
circuit = Circuit()
circuit.h(0)        # Hadamard on qubit 0
circuit.cnot(0, 1)  # CNOT: control=0, target=1

print(circuit)

The multi-line style above is identical to the chained style:

circuit = Circuit().h(0).cnot(0, 1)

Both produce the exact same circuit. Use whichever reads more clearly for your use case. For short circuits, chaining fits neatly on one line. For longer circuits with comments explaining each gate, the multi-line style is easier to follow.

Reading the Circuit Diagram

When you print a Braket circuit, you get an ASCII diagram:


T  : |0|1|

q0 : -H-C-
        |
q1 : ---X-

T  : |0|1|

Here is how to read it:

  • T : |0|1| shows the time steps (called “Moments”). Each numbered column represents a parallel gate layer. Moment 0 contains the Hadamard; moment 1 contains the CNOT.
  • q0, q1 label the qubit wires. Read each wire left to right to see the sequence of gates applied to that qubit.
  • H is the Hadamard gate. C and X connected by a vertical line represent the CNOT (controlled-NOT), where C is the control qubit and X is the target.
  • Gates in the same moment column execute in the same time step. The Braket compiler automatically groups independent gates into parallel moments when possible.

Running on the Local Simulator

from braket.circuits import Circuit
from braket.devices import LocalSimulator

circuit = Circuit().h(0).cnot(0, 1)
device = LocalSimulator()

task = device.run(circuit, shots=1000)
result = task.result()

print(result.measurement_counts)
# Counter({'00': 503, '11': 497})

The only outcomes are 00 and 11 in roughly equal proportions. That is the Bell state: the two qubits are maximally entangled, so they always produce the same measurement outcome. You never see 01 or 10. The roughly 50/50 split between 00 and 11 reflects the inherent quantum randomness of the superposition.

Braket vs Other Frameworks

If you are coming from another quantum framework, this comparison highlights the practical differences:

BraketQiskitCirq
Hardware accessIonQ, IQM, Rigetti, QuEraIBM QuantumGoogle Sycamore
Gate parameter orderqubit first, then angleangle first, then qubitgate then qubit
Free tierLocal simulator, no AWS account neededSimulators + free IBM hardwareLocal simulator
Variational algorithmsHybrid JobsQiskit RuntimeLocal only
Best forMulti-hardware comparison, AWS integrationIBM hardware, educationGoogle hardware, research

The most common source of confusion when switching between frameworks is the gate parameter ordering, covered in detail below.

Gate Parameter Ordering

Braket gate methods take the qubit index first, then any rotation angles. This is the opposite of Qiskit, where angles come first. Getting this wrong produces a circuit that compiles without errors but applies the wrong rotation to the wrong qubit.

Side-by-side comparison for an RX(pi/2) gate on qubit 0:

# Braket: qubit first, then angle
circuit.rx(0, 1.5708)     # RX(pi/2) on qubit 0

# Qiskit: angle first, then qubit
circuit.rx(1.5708, 0)     # RX(pi/2) on qubit 0

If you accidentally write circuit.rx(1.5708, 0) in Braket, the SDK interprets 1.5708 as a qubit index (rounded to qubit 1) and 0 as the angle. You get zero rotation on qubit 1, which is a silent, hard-to-debug error. Always double-check parameter order when porting circuits between frameworks.

More Gates

circuit = Circuit()
circuit.x(0)              # Pauli-X (NOT)
circuit.y(1)              # Pauli-Y
circuit.z(2)              # Pauli-Z
circuit.s(0)              # S gate
circuit.t(1)              # T gate
circuit.rx(0, 1.5708)     # Rx(pi/2) - qubit first, then angle
circuit.ry(1, 0.7854)     # Ry(pi/4)
circuit.rz(2, 3.1416)     # Rz(pi)
circuit.cz(0, 1)          # Controlled-Z
circuit.swap(0, 1)        # SWAP
circuit.ccnot(0, 1, 2)    # Toffoli

All gate methods follow the same convention: qubit indices first, then any parameters (angles, phases). Multi-qubit gates list the control qubits before the target qubit.

Statevector (No Shots)

Pass shots=0 to get the full statevector instead of sampled measurements. This runs a deterministic simulation that returns the exact quantum state, with no sampling noise:

circuit = Circuit().h(0).cnot(0, 1)
device = LocalSimulator()
result = device.run(circuit, shots=0).result()

# result.values[0] is the statevector
sv = result.values[0]
print(sv)
# [ 0.70710678+0.j  0.        +0.j  0.        +0.j  0.70710678+0.j]

The statevector has 2^n entries for n qubits. In this Bell state, the non-zero amplitudes at indices 0 (|00>) and 3 (|11>) each have magnitude 1/sqrt(2), confirming equal probability of measuring either outcome.

Statevector mode is useful for verifying circuit correctness, debugging algorithms, and computing expectation values analytically. Keep in mind that statevector simulation requires all operations to be unitary (standard quantum gates). If your circuit includes noise channels, you must use the density matrix simulator instead.

Parameterised Circuits

from braket.circuits import Circuit, FreeParameter

angle = FreeParameter('angle')
circuit = Circuit().h(0).rx(0, angle).cnot(0, 1)

device = LocalSimulator()
result = device.run(circuit, shots=500, inputs={'angle': 1.5708}).result()
print(result.measurement_counts)

FreeParameter creates a symbolic placeholder that you bind to a concrete value at runtime through the inputs dictionary. This is essential for variational algorithms like VQE and QAOA, where a classical optimizer repeatedly evaluates circuits at different parameter values. By defining the circuit once and rebinding parameters, you avoid the overhead of rebuilding the circuit object on each iteration.

AWS Cloud Execution

Running circuits on AWS (either managed simulators or real hardware) requires several setup steps before your first task:

  1. AWS account: Create one at aws.amazon.com if you do not already have one.
  2. Enable Braket: Open the Amazon Braket console in your region to activate the service.
  3. IAM credentials: Configure AWS credentials on your machine using aws configure or by attaching an IAM role if you are running on an EC2 instance or SageMaker notebook.
  4. S3 bucket: Create an S3 bucket (or designate an existing one) to store task results. Braket writes results to S3 because tasks run asynchronously; your circuit enters a queue and executes when the device is available, which can take seconds for simulators or minutes for real hardware.
import boto3
from braket.aws import AwsDevice

# AWS credentials must be configured (aws configure or IAM role)
device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")

circuit = Circuit().h(0).cnot(0, 1)

task = device.run(
    circuit,
    s3_destination_folder=('your-braket-bucket', 'results'),
    shots=100
)

print(task.id)        # task ARN
print(task.state())   # QUEUED, RUNNING, COMPLETED, FAILED

result = task.result()
print(result.measurement_counts)

Checking Task Status

Because tasks run asynchronously, you may want to poll for completion rather than blocking on task.result():

import time

task = device.run(circuit, s3_destination_folder=('your-braket-bucket', 'results'), shots=100)

while task.state() not in ['COMPLETED', 'FAILED', 'CANCELLED']:
    print(f"Status: {task.state()}")
    time.sleep(10)

if task.state() == 'COMPLETED':
    result = task.result()
    print(result.measurement_counts)
else:
    print(f"Task ended with status: {task.state()}")

Calling task.result() blocks until the task completes, which is simpler for interactive use. The polling approach is better for scripts where you want to log progress or implement timeouts.

Real Quantum Hardware

from braket.aws import AwsDevice

# IonQ Aria (trapped ion)
device = AwsDevice("arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1")

# IQM Garnet (superconducting)
device = AwsDevice("arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet")

task = device.run(circuit, shots=100)
result = task.result()

Tasks on real hardware queue and run when the machine is available. Small jobs typically take a few minutes. Note that each hardware provider has its own pricing, availability windows, and native gate set. Braket automatically transpiles your circuit to the target device’s native gates, but understanding the native gate set helps you write circuits that compile more efficiently.

Reading Results

The result object provides three views of the measurement data, each suited to different analysis needs:

result = task.result()

result.measurement_counts          # dict: bitstring -> count
result.measurements                # numpy array, shape (shots, n_qubits)
result.measurement_probabilities   # dict: bitstring -> probability

Here is what each attribute gives you:

  • measurement_counts: A dictionary (Python Counter) mapping bitstrings to integer counts. For example, {'00': 503, '11': 497} means bitstring 00 appeared 503 times out of 1000 shots. This is the most commonly used format for sampling experiments, histogram plots, and quick sanity checks.

  • measurements: A raw 2D NumPy array with shape (shots, n_qubits). Each row is a single shot, and each column is a qubit measurement (0 or 1). This format is useful when you need to post-process individual shots, compute correlations between specific qubits, or feed raw data into classical machine learning pipelines.

  • measurement_probabilities: A dictionary mapping bitstrings to float probabilities (normalized so they sum to 1.0). For example, {'00': 0.503, '11': 0.497}. This is useful when you want relative frequencies directly, for instance when computing expectation values or comparing against theoretical predictions.

Local Noise Simulation

Before running circuits on expensive real hardware, you can estimate how much noise affects your results using the local density matrix simulator. This helps you decide whether your circuit is shallow enough to produce meaningful results on a given device, or whether you need error mitigation techniques.

The density matrix simulator (braket_dm) propagates noise channels through the circuit, modeling realistic errors. Common noise channels include:

  • BitFlip: Applies a random X (bit-flip) error with a given probability. Models readout errors and classical bit corruption.
  • PhaseFlip: Applies a random Z (phase-flip) error. Models dephasing, where the relative phase between |0> and |1> is randomly flipped.
  • Depolarizing: Applies a random Pauli error (X, Y, or Z) with equal probability. This is the most commonly used noise model because it captures generic gate errors.
  • AmplitudeDamping: Models energy relaxation (T1 decay), where an excited |1> state spontaneously decays to |0>. This is a physically motivated noise model for superconducting qubits.
from braket.circuits import Circuit
from braket.circuits.noise import BitFlip, Depolarizing
from braket.devices import LocalSimulator

circuit = Circuit().h(0)
circuit.bit_flip(0, 0.01)   # 1% bit flip error
circuit.cnot(0, 1)
circuit.depolarizing(0, 0.005)

device = LocalSimulator('braket_dm')
result = device.run(circuit, shots=1000).result()
print(result.measurement_counts)

The string 'braket_dm' tells the LocalSimulator to use the density matrix backend instead of the default statevector backend. Density matrix simulation is slower (memory scales as 2^(2n) instead of 2^n), but it can represent mixed states and noise channels that statevector simulation cannot.

Common Mistakes

These are the issues that trip up most Braket beginners:

1. Reversed gate parameter order. Braket takes qubit first, then angle: circuit.rx(0, 1.5708). If you write it in Qiskit order (circuit.rx(1.5708, 0)), the SDK silently interprets the angle as a qubit index and the qubit as the angle. Always verify parameter order when porting code between frameworks.

2. Submitting to real hardware without an S3 bucket configured. All AWS task results are written to S3. If you forget the s3_destination_folder parameter, or if the bucket does not exist, the task submission fails. Create your S3 bucket before your first hardware submission.

3. Forgetting that the local simulator ignores hardware connectivity constraints. The local simulator treats all qubits as fully connected. A CNOT between any two qubits works fine locally, but the same circuit may require extra SWAP gates on real hardware with limited connectivity (like superconducting devices). If you want to validate connectivity, check the device’s topology using device.properties before submitting.

4. Using shots=0 with noise channels. Statevector mode (shots=0) requires all operations to be unitary. Noise channels (BitFlip, Depolarizing, etc.) are non-unitary, so they break statevector simulation. If your circuit includes noise, use the density matrix simulator (LocalSimulator('braket_dm')) with shots > 0.

What to Try Next

Was this tutorial helpful?