File size: 12,847 Bytes
55a628f
 
 
 
 
 
 
2fb5b49
55a628f
 
 
 
 
 
d7214cb
ed74525
d7214cb
 
 
 
 
 
 
 
 
 
ed74525
 
 
 
 
0e83549
 
 
 
 
 
 
ed74525
 
 
 
 
 
d7214cb
 
 
 
ed74525
 
 
0e83549
d7214cb
 
 
ed74525
 
 
 
 
 
d7214cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed74525
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55a628f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0e83549
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed74525
 
0e83549
 
 
 
 
 
 
 
 
 
 
d7214cb
 
ed74525
 
 
d7214cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed74525
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0e83549
ed74525
0e83549
ed74525
0e83549
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
---
title: QCal Copilot
emoji: ⚛️
colorFrom: blue
colorTo: purple
sdk: gradio
sdk_version: 4.44.0
python_version: "3.12"
app_file: app.py
pinned: false
license: mit
short_description: AI-assisted quantum calibration + CUDA-Q + Ising decoder
---

# QCal Copilot

AI-assisted quantum calibration. Point it at a raw `.npy` trace (or image, or
CSV) and it renders a plot, auto-fits the standard calibration model (Rabi,
Ramsey, T1, T2-echo), hands both to NVIDIA's Ising Calibration VLM, and emits a
ready-to-run CUDA-Q script seeded with the recommended tuning.

Ships three ways:

- **`pip install qcal-copilot`** — CLI + Python API (`qcal analyze`, `from qcal.data import from_npy`).
- **Gradio web app**`qcal serve` or [the hosted Space](https://huggingface.co/spaces/athurlow/qcal).
- **Jupyter** — see `examples/` for Rabi, Ramsey-drift, and readout-IQ notebooks.

```
┌─────────┐   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│  Upload │──▶│   Analyzer   │──▶│  Code gen    │──▶│  Simulator   │
│ (img/csv│   │ (Ising VLM)  │   │  (CUDA-Q)    │   │ (cudaq.sample│
└─────────┘   └──────┬───────┘   └──────────────┘   └──────────────┘


             ┌───────────────────────────────┐
             │   Decoder  (optional)         │
             │   Ising 3D CNN → PyMatching   │
             └───────────────────────────────┘
```

## Layout

```
app.py                 # Gradio UI + pipeline wiring
pyproject.toml         # package metadata + CLI entry point
src/qcal/
  data.py              # image/CSV/.npy/.npz preprocessing + plot rendering
  fit.py               # scipy-backed curve fits (Rabi/Ramsey/T1/T2)
  analyzer.py          # Ising VLM (local HF or NIM)
  codegen.py           # CUDA-Q script generator
  simulator.py         # executes the generated script
  decoder.py           # Ising 3D CNN pre-decoder + MWPM
  config.py            # persists NIM API key to ~/.config/qcal/config.toml
  cli.py               # `qcal ...` Typer commands
examples/              # Rabi / Ramsey-drift / readout-IQ notebooks
requirements.txt
```

The analyzer and simulator are decoupled, so adding a later-stage 3D CNN
decoder or swapping in a different model is a one-file change.

## Install (pip)

```bash
pip install qcal-copilot                   # CLI + NIM backend
pip install "qcal-copilot[decoder]"        # + PyMatching
pip install "qcal-copilot[gui]"            # + Gradio (for `qcal serve`)
pip install "qcal-copilot[ml]"             # + torch + transformers (local 35B VLM)
pip install "qcal-copilot[all]"            # everything
```

Store your NIM API key (or set `NVIDIA_API_KEY` in your shell):

```bash
qcal login           # prompts for the key, saves to ~/.config/qcal/config.toml (0600)
```

### CLI

```bash
# Rabi trace stored as a 1-D .npy with a matching time axis
qcal analyze rabi.npy --experiment rabi --out report.md --script rabi.py

# .npz archive with x, y arrays
qcal analyze ramsey.npz --experiment ramsey --json out.json

# Regenerate the CUDA-Q script from a saved analysis
qcal generate out.json --out rabi.py

# Run the Ising 3D CNN decoder on a synthetic syndrome volume
qcal decode --variant fast --distance 5 --rounds 5 --p 0.005 --shots 128

# Launch the Gradio UI locally (needs [gui])
qcal serve
```

### Python

```python
from qcal.data import from_npy
from qcal.analyzer import analyze_payload
from qcal.codegen import generate_script

payload = from_npy("rabi.npy", experiment_type="rabi",
                   x_path="rabi_time.npy", x_unit="s")
payload.fit             # FitResult: {amplitude, freq_per_s, tau_s, offset, phase_rad, R^2}

result = analyze_payload(payload, backend="auto")   # "nim" if key present, else local
print(result.markdown())
print(generate_script(result.parsed))
```

Both `payload.fit` and `result` render as rich markdown in Jupyter.

## Quick start

### 1. System requirements

- NVIDIA GPU (≥ 24 GB VRAM recommended for local 35B inference; otherwise use
  the NIM endpoint, which runs the model remotely).
- CUDA 12.x drivers.
- Python 3.10 – 3.12.

### 2. Install Python dependencies

```bash
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
```

### 3. Install CUDA-Q

CUDA-Q ships from NVIDIA as a standalone package. Pick one:

```bash
# Option A — pip wheels (Linux x86_64, CUDA 12):
pip install cudaq

# Option B — NVIDIA container (recommended for reproducibility):
docker pull nvcr.io/nvidia/nightly/cuda-quantum:latest
```

Confirm the install:

```bash
python -c "import cudaq; print(cudaq.__version__)"
```

### 4. Get access to the Ising Calibration model

You have two options.

**A. Hosted inference via NVIDIA NIM (easiest, no local download)**

1. Sign in at <https://build.nvidia.com/> and request access to
   `nvidia/ising-calibration-1-35b-a3b`.
2. Create an API key.
3. Export it before starting the app:

   ```bash
   export NVIDIA_API_KEY="nvapi-…"
   ```

**B. Local Hugging Face weights (needs substantial VRAM)**

1. Request access to `nvidia/Ising-Calibration-1-35B-A3B` on Hugging Face.
2. Authenticate:

   ```bash
   huggingface-cli login
   ```
3. The analyzer will download weights on first use. Override the model id with
   `QCAL_MODEL_ID` if you want to try a different checkpoint.

### 5. Run the app

```bash
python app.py
```

Open <http://localhost:7860>. Upload a calibration plot, click
**Analyze calibration**, inspect the generated CUDA-Q script, then click
**Run simulation** to execute it on the `cudaq` simulator.

## Deploy to Hugging Face Spaces

This repo is ready to deploy as a Gradio Space (e.g. `athurlow/qcal`). The
YAML frontmatter at the top of this README tells Spaces which SDK to use and
which file to run.

1. Push the repo to the Space:

   ```bash
   git remote add space https://huggingface.co/spaces/athurlow/qcal
   git push space claude/qcal-copilot-mvp-OZ9wj:main
   ```
2. In the Space **Settings → Variables and secrets**, add:
   - `NVIDIA_API_KEY` — required; the hosted Space can't download the 35B
     VLM locally, so the app should call the NIM endpoint.
3. (Optional) Override model ids via Space secrets if you have custom
   deployments: `QCAL_NIM_MODEL`, `QCAL_DECODER_FAST_ID`,
   `QCAL_DECODER_ACCURATE_ID`.
4. **Hardware:** a free CPU Space runs the decoder's small CNN (~1.8M params)
   and the NIM-backed analyzer fine. A GPU Space (T4 or better) is only
   needed if you want to host the calibration VLM locally; `cudaq` requires
   an NVIDIA GPU Space to run the simulation stage.

The app falls back gracefully when dependencies are missing: no
`NVIDIA_API_KEY` → analyzer reports the missing key; no `cudaq` → simulator
button surfaces the install hint; no `pymatching` → decoder shows density
metrics without MWPM timing.

## Error-correction decoder (optional stage)

After a successful calibration analysis, expand the
**"Error-correction decoder (Ising 3D CNN)"** panel to run an NVIDIA Ising
surface-code pre-decoder on a synthetic syndrome volume. The panel lets you:

- Pick the `fast` (~912k params) or `accurate` (~1.79M params) variant.
- Set code distance (d), syndrome rounds (T), physical error rate (p),
  and shot count.
- See before/after metrics: syndrome density, CNN inference time, MWPM
  decode time on raw vs denoised syndromes (via PyMatching), and a
  logical-error-rate proxy.
- View a side-by-side plot of the raw and denoised syndrome slices plus a
  before/after bar chart.

The `p` slider auto-populates from the calibration analysis (larger drive
amplitude mismatch → larger `p`). Running the decoder regenerates the
CUDA-Q script with a header block documenting the decoder variant and
improvement metrics.

**What's synthetic and what's real:** syndromes are sampled from Bernoulli(p)
with a few injected correlated chains, the matching graph is a toy 6-nearest-
neighbor graph (not a stim-generated DEM), and "LER" is a syndrome-weight
proxy. If the Ising decoder weights aren't reachable, the module falls back
to a 3D neighbor-support sparsifier so the pipeline still demos end-to-end.

**Swapping in real data:** call
`qcal.decoder.run_decoder(...)` directly with your own `numpy` volume in place
of the generated one, or replace `generate_syndromes()` with a stim-backed
sampler.

## Environment variables

| Variable                    | Purpose                                          |
| --------------------------- | ------------------------------------------------ |
| `NVIDIA_API_KEY`            | API key for the NIM endpoint (backend = `nim`)   |
| `QCAL_MODEL_ID`             | Override local HF calibration VLM id             |
| `QCAL_NIM_MODEL`            | Override NIM model name                          |
| `QCAL_NIM_ENDPOINT`         | Override NIM base URL                            |
| `QCAL_DECODER_FAST_ID`      | Override HF id for the fast decoder variant      |
| `QCAL_DECODER_ACCURATE_ID`  | Override HF id for the accurate decoder variant  |
| `QCAL_HOST`                 | Gradio bind host (default `0.0.0.0`)             |
| `QCAL_PORT`                 | Gradio port (default `7860`)                     |
| `QCAL_SHARE`                | Set to `1` to enable Gradio public share link    |
| `QCAL_CONFIG_PATH`          | Override config file path (default `~/.config/qcal/config.toml`) |
| `NIM_API_KEY`               | Alias for `NVIDIA_API_KEY`                       |

## Input formats

- **`.npy` / `.npz`** (preferred for real-hardware workflows) — Raw measurement
  arrays from your control stack. Pass `--experiment` (or `experiment_type=`)
  so `qcal` knows the expected shape and fit model:

  | `experiment_type`      | Array shape   | Auto-fit model                               |
  |------------------------|---------------|----------------------------------------------|
  | `rabi`                 | `(N,)`        | damped sine → `{amplitude, freq, tau, offset, phase}` |
  | `ramsey`               | `(N,)`        | damped cosine → `{amplitude, detuning, t2star, offset, phase}` |
  | `t1`, `t2_echo`        | `(N,)`        | exponential decay → `{amplitude, tau, offset}` |
  | `rabi_chevron`         | `(F, A)`      | heatmap (no fit)                             |
  | `readout_iq`           | `(N, 2)`      | scatter (no fit)                             |
  | `iq_trace`, `resonator_spec`, `unknown` | `(N,)` | plot only |

  `.npz` should contain at least a `y` key and optionally an `x` key. Disable
  fitting for air-gapped installs without `scipy` via `--no-fit` (CLI) or
  `from_npy(..., fit=False)` (Python).
- **Images**`.png`, `.jpg`, `.jpeg`, `.bmp`, `.tiff`, `.webp`. Any
  calibration artifact the VLM understands: Rabi chevrons, T1/T2 decays,
  Ramsey fringes, readout histograms, resonator spectroscopy, oscilloscope
  traces.
- **CSV / TSV** — We render a preview image of the first 25 rows so the VLM
  can still reason about tabular sweeps. Large tables are truncated.

## Roadmap (post-MVP)

- 3D CNN surface-code decoder stage wired in after analysis.
- Persist analyses + scripts per session.
- Real Rabi fit instead of the linear `suggested_theta` mapping.
- Auth + multi-user deployment.
- Regression-test golden plots against expected analyses.

## Development

Module boundaries to keep the MVP clean:

- `qcal.data` — file I/O and normalization only.
- `qcal.analyzer` — model calls; returns a strict JSON dict.
- `qcal.codegen` — pure function: analysis dict (+ optional decoder info) → script text.
- `qcal.simulator` — executes script text; never imports `cudaq` itself.
- `qcal.decoder` — Ising 3D CNN sparsifier + optional PyMatching MWPM.
- `app.py` — UI glue only; no ML logic.

### Testing the decoder stage

Without launching the UI:

```python
from qcal.decoder import run_decoder

r = run_decoder(variant="fast", distance=5, rounds=5, error_rate=0.01, n_shots=64)
print(r.markdown())
print("density reduction:", round(r.density_reduction * 100, 1), "%")
```

Through the UI:

1. Upload any calibration plot and click **Analyze calibration**.
2. Expand **Error-correction decoder (Ising 3D CNN)**.
3. Pick `fast` or `accurate`, adjust `d` / `T` / `p`, click **Run decoder**.
4. Inspect the metrics panel (density reduction, MWPM timing, LER proxy
   improvement) and the side-by-side plot.
5. The CUDA-Q script auto-refreshes with a decoder header block.