
# Appendix A — AWS Braket Setup & Cost Guardrails

> Do this **once, early** — before [Term 1 · The Qubit's first hands-on code](../term-1-quantum-mechanics/c02-qubits-bloch/03-bases-and-expectation.md).
> Everything in this program runs on the **free local simulator** by default; you only need an AWS
> account when you progress to on-demand simulators (SV1/DM1/TN1) and QPUs in
> [Term 5](../term-5-braket/README.md).

## Learning Objectives

By the end of this appendix you will be able to:

1. Install `amazon-braket-sdk-python` and run a circuit on the **local simulator** with **no AWS account**.
2. Create and configure AWS credentials, choose a Braket-supported region, and enable the service.
3. Set up an **IAM** identity with least-privilege Braket permissions.
4. Explain Braket's **pricing model** and put **cost guardrails** (budgets, alarms, tags) in place.
5. List available devices programmatically and read their properties.

---

## 1. Install the SDK (no AWS account needed)

`amazon-braket-sdk` requires **Python 3.11 or greater**. Always work inside a virtual environment.

```bash
# Create and activate an isolated environment
python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate

# Install the SDK (pulls in braket-default-simulator, boto3, etc.)
pip install --upgrade pip
pip install amazon-braket-sdk

# Optional extras you will use later in the program:
pip install amazon-braket-pennylane-plugin   # Term 5.6 (PennyLane integration)
pip install matplotlib networkx              # plotting + graph problems (QAOA)
```

Verify the install and run your **first quantum circuit** — a Bell state on the **local
simulator**. This costs nothing and touches no AWS service:

```python
# bell_local.py — runs entirely on your machine, free, no AWS account required.
from braket.circuits import Circuit
from braket.devices import LocalSimulator

# Build a 2-qubit Bell circuit: H on qubit 0, then CNOT(control=0, target=1).
bell = Circuit().h(0).cnot(0, 1)
print(bell)                       # ASCII circuit diagram

# The default LocalSimulator backend is the state-vector simulator ("braket_sv").
device = LocalSimulator()
result = device.run(bell, shots=1000).result()

# Expect roughly half "00" and half "11" — the signature of an entangled Bell state.
print(result.measurement_counts)  # e.g. Counter({'11': 507, '00': 493})
```

```text
T  : |0|1|
q0 : -H-C-
        |
q1 : ---X-

Counter({'00': 503, '11': 497})
```

> ✅ **Checkpoint:** if you see roughly-balanced `00`/`11` counts and **no** `01`/`10`, your SDK
> works. You can complete all of Terms 0–4's hands-on code on `LocalSimulator` alone.

### Local simulator backends

`LocalSimulator` accepts a backend name; you will meet all three in this program:

| Constructor | Backend | Use it for |
|---|---|---|
| `LocalSimulator()` or `LocalSimulator("braket_sv")` | State vector | Noiseless circuits (Terms 2–3). |
| `LocalSimulator("braket_dm")` | Density matrix | Noisy circuits (Term 4). |
| `LocalSimulator("braket_ahs")` | Analog Hamiltonian | Neutral-atom analog programs (Term 4.4 / 5). |

---

## 2. AWS Account & Credentials (needed for cloud devices only)

You only need this section when you reach [Term 5](../term-5-braket/README.md) (or want to run
SV1/DM1/TN1/QPUs earlier). Local-simulator lessons need none of it.

### 2.1 Create an account and an IAM user

1. Create an AWS account at <https://aws.amazon.com/> (a payment method is required even for the
   free tier).
2. **Do not use your root account for day-to-day work.** In the **IAM** console, create a user (or
   an IAM Identity Center user) for yourself.
3. Attach a Braket policy. The AWS-managed **`AmazonBraketFullAccess`** policy is the simplest
   starting point; it grants Braket actions plus the S3/CloudWatch/IAM-passrole permissions Braket
   needs to store results and run jobs. For least privilege in shared accounts, scope a custom
   policy down to specific device ARNs and a single results bucket.

### 2.2 Install and configure the AWS CLI

```bash
# macOS (Homebrew); see docs for other platforms
brew install awscli

# Configure a named profile with your IAM access key, secret, and a Braket region.
aws configure --profile braket
# AWS Access Key ID     [None]: AKIA....
# AWS Secret Access Key [None]: ....
# Default region name   [None]: us-east-1
# Default output format [None]: json
```

This writes `~/.aws/credentials` and `~/.aws/config`. The Braket SDK uses **boto3**, which picks
up these credentials automatically. To select your named profile in a session:

```bash
export AWS_PROFILE=braket        # Windows: set AWS_PROFILE=braket
```

> 🔐 **Security:** never hard-code keys in source. Prefer short-lived credentials via IAM Identity
> Center / SSO (`aws sso login`) or, on EC2/SageMaker, an attached IAM role. Rotate keys regularly.

### 2.3 Regions

Braket is available in a subset of AWS regions, and **specific devices are tied to specific
regions** (a QPU offered in `us-east-1` may not exist in `eu-west-2`). Pick a region that hosts the
device you need. Because availability changes, **enumerate devices programmatically** rather than
hard-coding (see §4). The on-demand simulators SV1/DM1/TN1 are offered in several regions.

### 2.4 Enable the service and run remotely

First-time use: open the [Braket console](https://console.aws.amazon.com/braket/) and accept the
terms to enable the service. Then the same Bell circuit on the **on-demand state-vector simulator
SV1** (⚠️ this one is billed — see §5):

```python
# bell_sv1.py — ⚠️ INCURS AWS CHARGES (SV1 is a paid managed simulator).
from braket.aws import AwsDevice
from braket.devices import Devices
from braket.circuits import Circuit

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

# The Devices enum gives readable, current references to managed devices.
device = AwsDevice(Devices.Amazon.SV1)        # equivalently AwsDevice(<SV1 ARN>)

# Managed/QPU runs are asynchronous: run() returns a task you poll for results.
task = device.run(bell, shots=1000)
print("Task ARN:", task.id)                   # also visible in the console
result = task.result()                        # blocks until the task completes
print(result.measurement_counts)
```

> Results are written to a **default Amazon S3 bucket** that Braket manages for you
> (`amazon-braket-<region>-<accountId>`); you no longer need to pre-create or pass a bucket for
> basic runs. You can override the destination with the `s3_destination_folder` argument if your
> org requires a specific bucket.

---

## 3. Quotas, Tasks, and Asynchrony (mental model)

- A **quantum task** is one submission of a circuit (with a shot count) to a device.
- **Local** runs are synchronous and free. **Managed** runs (SV1/DM1/TN1/QPU) are **asynchronous**:
  `run()` returns immediately with a task you later `.result()` on; results persist in S3.
- **QPUs run only during device-specific availability windows** and may queue. Check a device's
  status and window in the console or via its properties (see §4) before submitting.
- Default result-polling timeout is **5 days**; adjust with `poll_timeout_seconds`.

---

## 4. Discovering Devices Programmatically

Never hard-code device ARNs from a tutorial — availability drifts. Ask the service:

```python
# list_devices.py — read-only metadata calls; listing/searching devices is not billed.
from braket.aws import AwsDevice

# All ONLINE devices visible to your account/region (simulators + QPUs).
for dev in AwsDevice.get_devices(statuses=["ONLINE"]):
    print(f"{dev.name:24s}  type={dev.type:10s}  arn={dev.arn}")

# Inspect a specific device's capabilities, native gates, connectivity, and shot limits.
from braket.devices import Devices
sv1 = AwsDevice(Devices.Amazon.SV1)
print(sv1.properties.action)        # supported program/result types
print(sv1.status)                   # ONLINE / OFFLINE / RETIRED
```

For a QPU you will also inspect `properties.paradigm` (qubit count, connectivity graph),
`properties.provider` (calibration/fidelity data), and the device's execution window. We do this in
detail in [Term 5.3 · Devices & Paradigms](../term-5-braket/c03-qpus/01-devices-and-paradigms.md).

---

## 5. Cost Guardrails

This section is **mandatory reading** before you run anything outside `LocalSimulator`.

### 5.1 How Braket bills (the shape, not the prices)

Pricing changes; confirm current numbers on the
[Braket pricing page](https://aws.amazon.com/braket/pricing/). The **structure** is stable:

| Resource | Billing shape |
|---|---|
| `LocalSimulator` | **Free** (runs on your machine). |
| On-demand simulators (**SV1**, **DM1**, **TN1**) | Per **minute of simulation time** (a small per-minute rate, billed by the millisecond, with a per-task minimum). Big circuits / many shots = more minutes. |
| **QPUs** | **Per-shot** fee **plus** a **per-task** fee. Cost scales with `shots`. A few thousand shots on a QPU is real money. |
| **Hybrid Jobs** | The above device costs **plus** the cost of the classical instance running your job. |

**Implications for how you work in this program:**
- Develop and debug on `LocalSimulator` (free). Only promote to a managed device when the circuit is
  correct and you actually need it.
- Treat `shots` as a budget knob. Don't run 100,000 shots on a QPU "to be safe."
- TN1 is only economical for circuits with favorable tensor-network structure; SV1 for general
  circuits up to ~34 qubits; DM1 for noisy circuits up to ~17 qubits. (Limits are approximate and
  evolve — verify in the console.)

### 5.2 Put hard guardrails in place (do this now)

1. **AWS Budgets:** create a monthly cost budget (e.g. \$20) with email alerts at 50%/80%/100%.
   Console → Billing → Budgets → *Create budget*.
2. **CloudWatch billing alarm:** add a billing-metric alarm as a second safety net.
3. **Cost allocation tags:** tag every task/job (e.g. `Project=qc-degree`) so you can attribute and
   filter spend in Cost Explorer:
   ```python
   task = device.run(bell, shots=100, tags={"Project": "qc-degree", "Lesson": "appendixA"})
   ```
4. **Confirm before paid runs:** keep a habit (and we enforce it in lessons) of a guard like:
   ```python
   import os
   ALLOW_PAID = os.environ.get("BRAKET_ALLOW_PAID") == "1"
   assert ALLOW_PAID, "Refusing to submit a paid task. Set BRAKET_ALLOW_PAID=1 to override."
   ```

> ⚠️ **The single most common surprise bill** is a large shot count on a QPU inside a loop (e.g. a
> variational optimizer calling the device hundreds of times). When you reach
> [Term 5.5 · Hybrid Jobs](../term-5-braket/c05-hybrid-jobs/01-hybrid-jobs.md), you will learn to
> estimate cost *before* launching such loops.

---

## 6. Optional: Braket-Hosted Notebooks

Amazon Braket offers managed Jupyter notebook instances (a SageMaker notebook with the SDK
pre-installed). They are convenient but the **notebook instance itself is billed** while running —
stop it when idle. For this self-paced program, a **local virtual environment is the cheapest and
recommended** setup; use hosted notebooks only if you prefer not to manage Python locally.

---

## Checkpoint

1. Can you run the local Bell circuit and explain why only `00`/`11` appear?
2. What are the three `LocalSimulator` backends and when do you use each?
3. Which two cost components dominate a **QPU** task, and which knob most directly controls cost?
4. Why should you enumerate devices with `AwsDevice.get_devices()` instead of copying an ARN?
5. Name two guardrails you put in place before running any paid task.

<details>
<summary>Answers</summary>

1. The Bell state is $\frac{1}{\sqrt2}(\lvert 00\rangle + \lvert 11\rangle)$ — the two qubits are
   perfectly correlated, so measurement yields `00` or `11` with ~50% each and never `01`/`10`.
2. `braket_sv` (state vector, noiseless), `braket_dm` (density matrix, for noise), `braket_ahs`
   (analog Hamiltonian simulation for neutral-atom programs).
3. A **per-shot** fee and a **per-task** fee; **`shots`** most directly controls cost.
4. Device availability and ARNs change over time and by region; querying the service avoids stale,
   broken, or region-mismatched ARNs.
5. Any two of: an AWS Budget with alerts, a CloudWatch billing alarm, cost-allocation tags, and an
   explicit `BRAKET_ALLOW_PAID` guard before submission.

</details>

---

## Further Reading

- [AWS] Amazon Braket Developer Guide — *Get started* and *Pricing* sections: <https://docs.aws.amazon.com/braket/>
- `amazon-braket-sdk-python` README & examples: <https://github.com/amazon-braket/amazon-braket-sdk-python>
- Amazon Braket pricing: <https://aws.amazon.com/braket/pricing/>

---

← Back to [Program Index](../README.md) · Next: [Appendix B — Python/NumPy Refresher](B-python-numpy-refresher.md) →
