Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 364 additions & 0 deletions exercises/module3_compilation_execution_EXERCISES.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,364 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4168bf47",
"metadata": {},
"source": [
"# QoolQit Exercises — Module 3\n",
"## Compilation and Execution\n",
"\n",
"In Module 2 we assembled device-agnostic `QuantumProgram`s. This module makes\n",
"them run: we **compile** programs to devices with real physical constraints,\n",
"and **execute** them on an emulator. Along the way you will run your first\n",
"genuinely quantum experiments: a **π-pulse** and the **Rydberg blockade**.\n",
"\n",
"### In this module you will learn\n",
"- The built-in devices (`MockDevice`, `AnalogDevice`, ...) and their\n",
" constraints\n",
"- How `compile_to` adapts a dimensionless program to hardware limits\n",
"- How to run a compiled program on the `LocalEmulator` and read out the\n",
" measured bitstrings\n",
"- What a `CompilationError` means and how to reason about it\n",
"\n",
"\n",
"> **How to use this notebook.** \n",
"> - Cells marked **✏️ Exercise** contain gaps\n",
"> indicated by `...` or `# TODO` — replace them with working code following\n",
"> the instructions. \n",
"> - Cells marked **✅ Check** verify your answer: run them\n",
"> after completing the exercise. Everything else is provided and runs as-is.\n",
"> A separate **solution notebook** will be published.\n",
">\n",
"> **API note:** we use qoolqit version 1.4"
]
},
{
"cell_type": "markdown",
"id": "a1a0339c",
"metadata": {},
"source": [
"## 1. Devices\n",
"\n",
"A `Device` bundles the physical constraints of a machine: maximum amplitude,\n",
"maximum sequence duration, minimum atom spacing, maximum radial distance.\n",
"QoolQit ships default devices you can use offline:\n",
"\n",
"- **`MockDevice`** — a *virtual* device with (almost) no constraints, for\n",
" unconstrained prototyping;\n",
"- **`AnalogDevice`** — a *realistic* analog device;\n",
"- **`AnalogDeviceWithDMM`**, **`DigitalAnalogDevice`** — variants with extra\n",
" capabilities.\n",
"\n",
"Real remote devices (e.g. Pasqal's FRESNEL) can be fetched with\n",
"`Device.from_connection(connection=PasqalCloud(), name=\"FRESNEL\")` — same\n",
"interface, specs downloaded from the cloud."
]
},
{
"cell_type": "markdown",
"id": "cba9bf8e",
"metadata": {},
"source": [
"### ✏️ Exercise 3.1 — Meet the devices\n",
"\n",
"1. Call `available_default_devices()` (imported from `qoolqit`) to list the\n",
" built-in devices and their constraints.\n",
"2. Instantiate `device = AnalogDevice()` and print it.\n",
"3. Read the printout and note down (mentally or in a comment): its\n",
" **max_duration**, **max_amplitude** and **max_radial_distance**. Compare\n",
" with `MockDevice()` — what is different?"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7e3954d5",
"metadata": {},
"outputs": [],
"source": [
"from qoolqit import MockDevice\n",
"\n",
"# TODO: list the default devices\n",
"\n",
"# TODO: instantiate and print the realistic analog device\n",
"device = ...\n",
"print(device)\n",
"\n",
"print(MockDevice())"
]
},
{
"cell_type": "markdown",
"id": "22037080",
"metadata": {},
"source": [
"## 2. Compilation: your first physical experiment, the π-pulse\n",
"\n",
"Compilation translates the dimensionless program into a concrete pulse\n",
"sequence in physical units, rescaling amplitude, duration and atom spacing to\n",
"fit the device (by default with the `MAX_ENERGY` profile, which uses the\n",
"device's maximum capabilities while preserving the program's ratios).\n",
"\n",
"**The experiment.** Drive a *single atom* with a constant amplitude\n",
"$\\Omega$ and zero detuning. The atom oscillates between $|0\\rangle$ and\n",
"$|1\\rangle$ (*Rabi oscillation*), and is fully flipped to $|1\\rangle$ when\n",
"\n",
"$$\n",
"\\Omega \\cdot t = \\pi \\qquad \\text{(a \"π-pulse\")}.\n",
"$$"
]
},
{
"cell_type": "markdown",
"id": "19943521",
"metadata": {},
"source": [
"### ✏️ Exercise 3.2 — Compile a π-pulse\n",
"\n",
"1. Build a **single-atom** register at the origin.\n",
"2. Build a drive with a `ConstantWaveform` amplitude of value `1.0` and a\n",
" duration realizing a π-pulse. No detuning needed.\n",
"3. Assemble the program, compile it to the `AnalogDevice` with\n",
" `program.compile_to(device=..., profile=\"max_energy\")`, check `is_compiled`, and draw the\n",
" compiled sequence with `program.draw(compiled=True)`.\n",
"\n",
"Note the units in the drawing: compilation has translated dimensionless time\n",
"and amplitude into nanoseconds and rad/µs."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fbafe7f0",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from qoolqit import ConstantWaveform, Drive, QuantumProgram, Register\n",
"\n",
"# TODO: single atom at the origin\n",
"register_1atom = ...\n",
"\n",
"# TODO: constant pi-pulse: Omega = 1.0, duration such that Omega*t = pi\n",
"pi_pulse = ConstantWaveform(..., ...)\n",
"drive_pi = Drive(amplitude=pi_pulse)\n",
"\n",
"# TODO: assemble, compile to the AnalogDevice, and draw compiled\n",
"program_pi = ...\n",
"program_pi.compile_to(device=..., profile=\"max_energy\")\n",
"print(\"Compiled?\", program_pi.is_compiled)\n",
"program_pi.draw(compiled=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39a2adba",
"metadata": {},
"outputs": [],
"source": [
"# ✅ Check\n",
"assert program_pi.is_compiled\n",
"assert abs(drive_pi.duration - np.pi) < 1e-9\n",
"print(\"π-pulse compiled!\")"
]
},
{
"cell_type": "markdown",
"id": "d3483f79",
"metadata": {},
"source": [
"## 3. Execution on the LocalEmulator\n",
"\n",
"Executing follows a simple, backend-independent workflow:\n",
"\n",
"```\n",
"emulator = LocalEmulator() # from qoolqit.execution\n",
"job = emulator.run(program)\n",
"results = job.results()\n",
"counts = results.final_bitstrings # a Counter of measured bitstrings\n",
"```\n",
"\n",
"Remote backends (`RemoteEmulator`, `QPU`) expose exactly the same `run` /\n",
"`results` interface — only the construction differs (they need a cloud\n",
"connection)."
]
},
{
"cell_type": "markdown",
"id": "b748f67f",
"metadata": {},
"source": [
"### ✏️ Exercise 3.3 — Run the π-pulse\n",
"\n",
"Run `program_pi` on a `LocalEmulator` and print the measured bitstring\n",
"counts. If your pulse is a true π-pulse, (almost) **all shots should return**\n",
"`'1'` — the atom is deterministically flipped."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72154f94",
"metadata": {},
"outputs": [],
"source": [
"# TODO: run the program and get the bitstring counts\n",
"emulator = ...\n",
"job = ...\n",
"results = ...\n",
"counts = ...\n",
"\n",
"print(counts)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5a587bc",
"metadata": {},
"outputs": [],
"source": [
"# ✅ Check — at least 95% of shots in '1'\n",
"total = sum(counts.values())\n",
"assert counts.get(\"1\", 0) / total > 0.95, \"Expected (almost) all shots in '1'\"\n",
"print(f\"π-pulse verified: {counts.get('1', 0)}/{total} shots measured '1'.\")"
]
},
{
"cell_type": "markdown",
"id": "f9c4e178",
"metadata": {},
"source": [
"## 4. A two-atom experiment: the Rydberg blockade\n",
"\n",
"Now the same π-pulse on **two atoms**. The interaction $J = 1/r^6$ changes\n",
"everything:\n",
"\n",
"- **Far apart** ($J \\ll \\Omega$): the atoms don't feel each other and are\n",
" *independently* flipped → you measure `'11'`.\n",
"- **Close together** ($J \\gg \\Omega$): exciting *both* atoms costs a huge\n",
" interaction energy, so the doubly-excited state is **blockaded** → `'11'`\n",
" is (almost) never measured. This *Rydberg blockade* is the fundamental\n",
" mechanism behind unit-disk connectivity (Module 1) and neutral-atom\n",
" entanglement."
]
},
{
"cell_type": "markdown",
"id": "cc51d1ec",
"metadata": {},
"source": [
"### ✏️ Exercise 3.4 — Observe the blockade\n",
"\n",
"1. `register_far`: two atoms at distance **3.0** (so $J = 1/3^6 \\approx\n",
" 0.0014 \\ll 1$). Apply the same π-pulse drive, compile to the\n",
" `AnalogDevice`, run, and print the counts.\n",
"2. `register_close`: two atoms at distance **0.7** (so $J = 1/0.7^6 \\approx\n",
" 8.5 \\gg 1$). Same pulse, compile, run, print.\n",
"3. Compare the frequency of `'11'` in the two cases. Blockade in action!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8d5ff774",
"metadata": {},
"outputs": [],
"source": [
"# TODO 1: two distant atoms -> independent flips\n",
"register_far = Register.from_coordinates([...])\n",
"program_far = QuantumProgram(register_far, Drive(amplitude=pi_pulse))\n",
"program_far.compile_to(device=device, profile=\"max_energy\")\n",
"counts_far = ...\n",
"print(\"far (r=3.0):\", counts_far)\n",
"\n",
"# TODO 2: two close atoms -> blockade\n",
"register_close = Register.from_coordinates([...])\n",
"program_close = ...\n",
"counts_close = ...\n",
"print(\"close(r=0.7):\", counts_close)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4ed1447",
"metadata": {},
"outputs": [],
"source": [
"# ✅ Check\n",
"tot_far = sum(counts_far.values())\n",
"tot_close = sum(counts_close.values())\n",
"assert counts_far.get(\"11\", 0) / tot_far > 0.9, (\n",
" \"Far atoms should (almost) always give '11'\"\n",
")\n",
"assert counts_close.get(\"11\", 0) / tot_close < 0.05, (\n",
" \"Close atoms should (almost) never give '11'\"\n",
")\n",
"print(\"Rydberg blockade observed!\")"
]
},
{
"cell_type": "markdown",
"id": "cd39b586",
"metadata": {},
"source": [
"## 5. When compilation fails: `CompilationError`\n",
"\n",
"Compilation rescales amplitude, duration and spacing *together* to fit the\n",
"device. Sometimes no consistent rescaling exists — e.g. bringing a large\n",
"amplitude down to the device maximum stretches the duration beyond the device\n",
"limit. QoolQit then raises a **`CompilationError`** with a message explaining\n",
"which constraint broke."
]
},
{
"cell_type": "markdown",
"id": "d3e0cabd",
"metadata": {},
"source": [
"### ✏️ Exercise 3.5 — Trigger and read a CompilationError\n",
"\n",
"1. Build `program_bad`: the **close** two-atom register with a\n",
" `ConstantWaveform(400, 1.0)` amplitude — a very long pulse.\n",
"2. Try to compile it to the `AnalogDevice` inside a\n",
" `try/except CompilationError` block (import it from `qoolqit.exceptions`)\n",
" and print the message. Read it: which device limit was violated?\n",
"3. Now compile the **same** program to a `MockDevice()` — it succeeds! Why is\n",
" that both useful and dangerous?"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1166e97c",
"metadata": {},
"outputs": [],
"source": [
"from qoolqit.exceptions import CompilationError\n",
"\n",
"# TODO: an over-long program\n",
"program_bad = QuantumProgram(\n",
" register_close, Drive(amplitude=ConstantWaveform(..., ...))\n",
")\n",
"\n",
"try:\n",
" ...\n",
"except CompilationError as err:\n",
" print(\"CompilationError:\", err)\n",
"\n",
"# TODO: same program on the unconstrained MockDevice\n",
"print(\"Compiled on MockDevice?\", program_bad.is_compiled)"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading