diff --git a/packages/extension/PICCOLISSIMO_INTEGRATION.md b/packages/extension/PICCOLISSIMO_INTEGRATION.md new file mode 100644 index 00000000..712ca324 --- /dev/null +++ b/packages/extension/PICCOLISSIMO_INTEGRATION.md @@ -0,0 +1,96 @@ +# Amicode → Piccolissimo (beta target) + +Amicode's beta will run solves through **Piccolissimo** (the Harmoniqs commercial +stack: Piccolissimo + Altissimo), not raw Piccolo. Piccolissimo bundles Piccolo +and adds exact integrators, so "everything Piccolo can do, and more." This doc is +the running record of what's verified and what the binary integration still needs. + +## Status + +- ✅ **Solve template verified** — `templates/solve_template_piccolissimo.jl` runs + end-to-end through `amico-run` and converges: single-qubit transmon **X gate**, + 3-level, **F = 0.99995** in 60 iters (~88 s cold), full run-dir contract + + `plot_pulse` frames. +- ⏳ **Not yet the live default.** `AGENTS.md` + the bundled julia project still + point at raw Piccolo (`solve_template.jl`), because a *source* install of + Piccolissimo is not distributable (see Altissimo below). The swap happens once + the Piccolissimo binary is consumable. +- 🔭 **Binary distribution is Jack's in-flight work** (Piccolissimo + `Build.yml` → tarball → Cloudflare R2 / GitHub release). + +## What the template needed (lessons from verification) + +1. **Piccolissimo does not reexport Piccolo.** Load **both** (`using Piccolo; + using Piccolissimo`). `using Piccolissimo` alone is missing `GATES`, + `TransmonSystem`, `SmoothPulseProblem`, `plot_pulse`, … +2. **Pin the timesteps.** `PiccoloOptions(timesteps_all_equal = true)` is required + — with a free Δt the dual infeasibility oscillates and the solve stalls at + F≈0.976; pinned, it reaches F≈0.99995. Same lesson as the raw-Piccolo template. +3. **Piccolo version differs.** Piccolissimo pins **Piccolo 1.10**; the raw + template was vetted on **Piccolo 1.19**. The symbols the template uses all + exist in 1.10 (`GATES`, `TransmonSystem`, `EmbeddedOperator`, + `UnitaryTrajectory`, `ZeroOrderPulse`, `SmoothPulseProblem`, `solve!`, + `plot_pulse`, `unitary_rollout`, `unitary_fidelity`, `iso_vec_to_operator`, + `get_trajectory`, `Piccolo.Callbacks`). + +## Why source isn't distributable (→ binary is the only path) + +Piccolissimo's Manifest pins **Altissimo** (the solver) to a local, unregistered +path dep: `path = "/Users/raghavchari/Altissimo.jl"`, `version 1.0.0-DEV`. An end +user can't `Pkg.add` Piccolissimo and resolve Altissimo. The prebuilt image, +which bakes Altissimo in, is the only delivery vehicle. + +## Integration contract for the binary (what amicode needs from the build) + +The current published artifact (`rel-0.3.0-test1`) is: +`piccolissimo-pkgimage-0.3.0-x86_64-linux-gnu-julia1.12.6.tar.zst`. For amicode to +consume it: + +1. **It's a `pkgimage`, not a `sysimage`.** It loads by extracting into the Julia + depot's compiled cache (`JULIA_DEPOT_PATH`), **not** via `julia --sysimage`. + amico-run's existing `--sysimage ` flag does **not** apply as-is — wiring + a pkgimage means an extract step + depot env, *or* the build also emits a true + sysimage we can point `--sysimage` at. (Decide which with Jack.) +2. **Per-platform builds.** Only `x86_64-linux-gnu` is published today. macOS lab + machines need `aarch64-apple-darwin` (the workflow has the matrix entry; the + asset just isn't published yet). +3. **Julia version must match.** The pkgimage is tied to **julia 1.12.6**; amicode + must ship/require exactly that Julia. (Dev machine here is 1.12.3 — mismatch.) +4. **CairoMakie is not in Piccolissimo's deps.** The pkgimage won't accelerate + `plot_pulse` — the ~35 s Makie first-plot JIT remains. If amicode wants fast + plotting too, **CairoMakie must be baked into the amicode image** (or we accept + the one-time plot warm-up). +5. **License gate.** `Piccolissimo.validate_license()` (checks + `PICCOLISSIMO_LICENSE` / `~/.config|Library/.../license.key` against a Harmoniqs + server) is currently **commented out** in `__init__`, with the note + *"re-enable once binaries are deployed."* When it turns on, amicode provisioning + (β.4 install.sh / healthcheck) must supply `PICCOLISSIMO_LICENSE` the same way + it handles Bedrock creds. + +## When the binary lands — the amicode swap + +1. Point `AGENTS.md` (template ref) at `solve_template_piccolissimo.jl`. +2. Bundle/provision a Piccolissimo julia project (or rely on the pkgimage depot). +3. Wire the pkgimage load path in amico-run / install.sh (per contract #1). +4. Add `PICCOLISSIMO_LICENSE` to provisioning + healthcheck (per contract #5). + +## Local verification recipe (reproduces F=0.99995) + +Requires the local commercial stack checked out (`~/Piccolissimo.jl`, +`~/Altissimo.jl`) with the license gate disabled (current state). + +```julia +using Pkg +Pkg.activate("/tmp/amicode-pcl-env") +Pkg.develop(path="/Users/raghavchari/Altissimo.jl") # unregistered path dep +Pkg.develop(path="/Users/raghavchari/Piccolissimo.jl") +Pkg.add("Piccolo"); Pkg.add("CairoMakie"); Pkg.add("JLD2") +Pkg.instantiate() +``` + +```sh +node packages/amico-run/dist/amico-run.js \ + packages/extension/templates/solve_template_piccolissimo.jl \ + --project /tmp/amicode-pcl-env --runs-root /tmp/pcl-runs +# → AMICODE_FINISHED status=completed exitCode=0, result.toml fidelity = 0.99995 +``` diff --git a/packages/extension/templates/solve_template_piccolissimo.jl b/packages/extension/templates/solve_template_piccolissimo.jl new file mode 100644 index 00000000..8f3b6776 --- /dev/null +++ b/packages/extension/templates/solve_template_piccolissimo.jl @@ -0,0 +1,96 @@ +#!/usr/bin/env julia +# Amicode solve template — PICCOLISSIMO variant. +# +# Single-qubit transmon X gate, solved with Piccolissimo's exact-propagation +# `HermitianExponentialIntegrator`. This is the amicode-beta target (we move the +# stack onto Piccolissimo), held parallel to `solve_template.jl` until the +# Piccolissimo binary is consumable — see PICCOLISSIMO_INTEGRATION.md. +# +# Verified end-to-end through amico-run against a local Piccolissimo checkout +# (Piccolo 1.10 + Piccolissimo + CairoMakie, julia 1.12.3): X gate on a 3-level +# transmon converges to subspace fidelity F = 0.99995 in 60 iters (~88s cold). +# +# Two things differ from the raw-Piccolo template: +# 1. Piccolissimo does NOT reexport Piccolo → load BOTH. +# 2. The exact `HermitianExponentialIntegrator` replaces the default integrator. +# Everything else (run-dir contract, plot_pulse frames) is identical. +using Piccolo +using Piccolissimo +using CairoMakie +using JLD2 +using TOML +using Printf + +# ── FILL IN ────────────────────────────────────────────────────────────── +δ = 0.2 # anharmonicity (GHz, positive convention) +levels = 3 # transmon levels (3 = qubit + 1 leakage; 4 for more realism) +gate = GATES[:X] +T = 10.0 # gate time (ns) +N = 50 # timesteps +drive_max = 0.2 # per-quadrature drive bound (GHz) +max_iter = 60 +# ───────────────────────────────────────────────────────────────────────── + +sys = TransmonSystem(; δ = δ, levels = levels, drive_bounds = fill(drive_max, 2)) +op = size(gate, 1) == sys.levels ? gate : EmbeddedOperator(gate, sys) + +times = collect(range(0.0, T, length = N)) +initial = 0.1 * randn(sys.n_drives, N) +qtraj = UnitaryTrajectory(sys, ZeroOrderPulse(initial, times), op) + +# Piccolissimo's exact propagator: eigendecomposition + analytic Jacobians. +integrator = HermitianExponentialIntegrator(qtraj, N) +qcp = SmoothPulseProblem(qtraj, N; integrator = integrator, + # Fix the timesteps. With a free Δt the dual infeasibility oscillates and the + # solve stalls at F≈0.976; pinning it converges to F≈0.99995. (Same lesson as + # the raw-Piccolo template — don't drop this when swapping the integrator.) + piccolo_options = PiccoloOptions(timesteps_all_equal = true), + Q = 100.0, R = 1e-2) +prob = hasproperty(qcp, :prob) ? qcp.prob : qcp + +# Per-iter callbacks via Piccolo's public `Callbacks` module. +const CB = Piccolo.Callbacks +const PLOT_EVERY = 6 # plot every 6 iters (live frames for the inspector) +iters = Ref(0) +function cb_log(optimizer, st; kwargs...) + k = Int(st.iter_count); iters[] = k + @printf("AMICODE_ITER iter=%d f=%.6e inf_pr=%.3e inf_du=%.3e\n", k, st.obj_value, st.inf_pr, st.inf_du) + flush(stdout) + (k > 0 && k % PLOT_EVERY == 0) && save_control_plot(k) # skip iter-0 (random init; defers Makie's first compile) + return true +end + +# Per-iter pulse plot via Piccolo's canonical `plot_pulse` (Makie). Returns a +# Figure we save as the run-dir frame. NOTE: Piccolissimo's deps do not include +# CairoMakie, so under a Piccolissimo pkgimage this first call still JIT-compiles +# Makie (~35s) unless CairoMakie is baked into the image — see the integration doc. +function save_control_plot(k::Int) + try + fig = plot_pulse(qcp; bounds = true, title = @sprintf("iter %d", k)) + CairoMakie.save(@sprintf("iter_%04d.png", k), fig) + catch e + @warn "iter plot failed" exception = e # never let plotting kill the solve + end +end + +t0 = time() +solve!(qcp; max_iter = max_iter, print_level = 1, + callback = CB.callback_factory(CB.callback_update_trajectory_factory(prob), cb_log)) +wall = time() - t0 + +# Subspace fidelity from a fresh rollout (see solve_template.jl for the rationale). +Uroll = iso_vec_to_operator(unitary_rollout(get_trajectory(qcp), sys)[:, end]) +fid = unitary_fidelity(Uroll, op.operator; subspace = op.subspace) + +isfile(@sprintf("iter_%04d.png", iters[])) || save_control_plot(iters[]) + +JLD2.save("pulse.jld2", "traj", prob.trajectory) # key "traj" for load_traj (warm-start) +open("result.toml.tmp", "w") do io + TOML.print(io, Dict( + "fidelity" => fid, "iterations" => iters[], "wall_seconds" => wall, + "params" => Dict("delta" => δ, "levels" => levels, "T" => T, "N" => N, + "drive_max" => drive_max, "max_iter" => max_iter), + )) +end +mv("result.toml.tmp", "result.toml"; force = true) +println("DONE fidelity=$(fid)"); flush(stdout)