Skip to content

feat: noise-native measurement layer + chassis policy seams (QILC port, public substrate)#7

Merged
aarontrowbridge merged 13 commits into
mainfrom
feat/qilc-noise-native
Jul 3, 2026
Merged

feat: noise-native measurement layer + chassis policy seams (QILC port, public substrate)#7
aarontrowbridge merged 13 commits into
mainfrom
feat/qilc-noise-native

Conversation

@krish-suresh

Copy link
Copy Markdown
Contributor

feat: noise-native measurement layer + chassis policy seams (QILC port, public substrate)

Implements the public half of the QILC stanford port per the approved spec:

  • Noise statistics + whitening: noise_floor/debiased_cost/cost_std/diff_std
    (diagonal fast path) and whiten(model, y_exp; W_task, var_floor)
    GLS W = W_task·Σ^{-1/2} from the (previously inert) measurement noise
    types, plug-in per iteration. Deviation: default var_floor = 0.05
    (not the plan's 0.25 — the plan's own acceptance test requires the y=0.9
    wigner element unfloored at defaults).
  • Seeded noise sampling: SimulatedExperiment(...; rng) draws
    ε ~ N(0, Σ(y)) per noisy element; run_experiment(...; n_shots) value-copy
    override on both experiment types (TopKRemeasure contract).
  • Composite-space bosonic observables (public measurement_functions/):
    displaced_parity(x, α; Nq, Nc) (kwarg-routed beside the cavity-only path),
    qubit_sigma_z, ρ triangle encode/decode, reconstruct_rho_from_parity
    (docstring: MLE reconstruction is diagnostics-only, never ILC feedback).
  • Chassis policy seams on PulseTuningProblem:
    • acceptance::AcceptancePolicyLineSearchAcceptance (behavior-preserving
      extraction of the Armijo + γ-schedule path, pinned by hard-coded pre-seam
      traces) and OneShotAcceptance (β-damped apply, σ_Δ-aware
      reject-and-revert, shrink-only trust scaling, stall anneal).
    • selector::IterateSelectorFinalIterate, NoiseCorrectedBestJ
      (argmin debiased J̃), TopKRemeasure(k, reps_factor), PolyakAverage
      (subsumes the deprecated polyak_avg kwarg).
    • explicit fixed y_goal kwarg (resolved once; the chained-loop-drift
      invariant) and W_task (task importance composed into every whitened cost).
    • IterationRecord extended (J̃ + Ĵ, accepted, F_model via the new
      last_f_model strategy hook, θ measurement-time snapshot, timings) +
      solve!(...; logger) seam.
    • adapt!(device_model, …) now runs BEFORE the strategy step (recal must
      precede Jacobian assembly).
    • mid-campaign measurement-dimension drop (lab σ_z fallback): goal/model/
      W_task restriction + acceptance reset + warning.
  • Unit-consistency fix: armijo_line_search gains a cost closure so
    probes compare in the same (whitened) units as J_ref — a raw trial cost
    against a whitened reference silently accepts every step once shot noise is
    declared (caught by the Intonatissimo campaign cells).

Suite: 270/270. Every extraction is pinned by a behavior-preservation
regression recorded on the pre-seam chassis.

Verification: suite 270/270 at 58b9ecd. Behavior-preserving extractions
are pinned by hard-coded traces recorded on the pre-seam chassis (reject
cascade, partial-α accept, line_search=false, Polyak). Downstream, the full
Intonatissimo suite (which consumes every seam here) runs 427 pass / 0 fail /
2 expected B-spline skips, and the end-to-end acceptance gate through the new
stack reached F_truth 0.99718 with ≥ 0.99 sustained over the final 20 of 30
iterations under χ×1.15 + K_c×2.2 + 1000-shot noise + 1 %/trial drift.

Stack: this is the base of the QILC-port stack — Intonatissimo
feat/qp-ilc-strategy (harmoniqs/Intonatissimo.jl#44) and stanford-bosonics
port/intonatissimo-chassis (harmoniqs/stanford-bosonics#6) build on these
seams. Merge order: this PR first.

Spec: armonissima/specs/spec-20260701-161246-qilc-stanford-port-noise-native.md ·
Plan: armonissima/plans/plan-20260701-235146-qilc-stanford-port-noise-native.md

🤖 Generated with Claude Code

krish-suresh and others added 12 commits July 2, 2026 08:11
Diagonal fast path for the whitened-cost statistics from the QILC
noise-native spec: noise_floor = tr(WΣWᵀ), debiased_cost J̃ = Ĵ − floor,
cost_std via Var[Ĵ] = 2tr((WΣWᵀ)²) + 4rᵀWᵀWΣWᵀWr (plug-in r), and the
difference scale σ_Δ = hypot(σ_A, σ_B) for acceptance/stall comparisons.
Also adds test/run_filtered.jl, a local helper for name-filtered testitem
runs (this TestItemRunner version has no run_tests function).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…se models

W = W_task·Σ^{-1/2} assembled per element from the model's noise types
(ShotNoise → plug-in covariance_fn(y,n) floored at var_floor/n;
KnownCovariance → diag(Σ), off-diagonals dropped by the diagonal fast
path; Deterministic → σ²=0, identity scaling). Returns (w, σ2) flattened
in model element order; Σ is returned because acceptance statistics need
tr(WΣWᵀ), not just W.

Deviation from plan: default var_floor is 0.05, not 0.25 — the plan's own
acceptance test requires the y=0.9 wigner element (σ²=(1−y²)/n=0.19/n) to
be UNfloored at defaults, which contradicts a 0.25 default (0.25/n floor).
0.05 engages only for |y| ≳ 0.975, matching the stated intent (cap weights
as |y|→1); callers can raise it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…E parity reconstruction

Ports the stanford-bosonics ILC measurement observables into the package:

- displaced_parity/displaced_parity_at gain a composite transmon⊗cavity
  variant via SINGLE-ENTRY-POINT kwarg routing: Julia does not dispatch on
  keyword arguments, so one method accepts n_max (cavity-only, exact
  pre-existing behavior) XOR (Nq, Nc) (composite kron(I_q, D†ΠD)) and raises
  ArgumentError unless exactly one is given.
- qubit_sigma_z(_at): ⟨kron(σz_eff, I_c)⟩ with σz_eff = diag(+1, −1, −1, …),
  the leakage-defense convention (−1 for ALL excited transmon levels).
- density_encoding.jl: rho_triangle canonical order, rho_to_measvec (type
  generic, ForwardDiff-safe) / measvec_to_rho, reduced_cavity_rho, and
  rho_measurement_functions closures for reconstruction-mode campaigns.
- parity_reconstruction.jl: reconstruct_rho_from_parity, an emulation of the
  lab's MLE cavity-state reconstruction. DIAGNOSTICS-ONLY contract: the
  PSD/trace projection is a nonlinear state-dependent bias the linear ILC
  Jacobian cannot track, so it must never be a feedback observable.
- test/run_filtered.jl: TEST_FILTER-driven filtered @run_package_tests entry.

Coherent-state anchor note: the package/demo convention op = D†(α) Π D(α)
displaces the state by +α, so P(α) = exp(−2|β+α|²) for coherent |β⟩; the
tests assert this and the equivalent Wigner-point form at −α.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…_shots override

SimulatedExperiment gains an rng slot (constructor kwarg, default nothing =
exactly the legacy deterministic behavior). With an AbstractRNG, each noisy
measurement element gets ε ~ N(0, Σ(y)) drawn from its noise model at the
evaluated y (variances floored as in whiten); deterministic elements draw
nothing, keeping the rng stream layout-stable.

run_experiment gains an additive n_shots kwarg on both experiment paths:
SimulatedExperiment rebuilds ShotNoiseMeasurement value-copies with the
boosted n (noise scales exactly 1/√n for a fixed seed); HardwareExperiment
forwards to run closures that accept the kwarg, else warns once and ignores.

Replaces the runner's deleted add_measurement_noise/SIM_NOISE contract and
gives fixtures seeded reproducibility. Full suite green (193/193).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
y_goal::Union{Nothing,Vector{Measurement}} constructor kwarg: nothing
(default) keeps today's behavior — solve! resolves the goal once at solve
start via the strategy's tuning_goal hook; supplied → used verbatim. The
resolved goal is computed exactly once per solve and never mid-solve (the
chained-loop-drift invariant: per-round y_goal recomputation from the
current command made real campaigns diverge). Campaign scripts pass the
ideal target built from ψ_goal via the public observables.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…avior-preserving)

Extracts the chassis's Armijo accept/interpolate block and the γ/tr_scale
schedule into an AcceptancePolicy seam: decide(policy, ctx) returns
(α, accepted, revert, tr_scale, n_evals); the chassis keeps run-experiment →
whitened Ĵ/J̃ (via the noise-stats helpers; identical to raw SSR for
deterministic models) → decide → apply α-interpolation (or revert to the
last-accepted snapshot) → record. LineSearchAcceptance(γ, line_search)
reproduces the pre-seam behavior verbatim — pinned by a hard-coded
(α, tr_scale, n_experiments, J) trace regression recorded on the pre-seam
chassis across the reject cascade, partial-α accept, and line_search=false
paths. PulseTuningProblem gains an acceptance slot (nothing ⇒ legacy kwargs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…trust scaling

The hardware-hardened acceptance policy: no probing (α is always exactly β,
one experiment per iteration); reject-and-revert iff
Ĵ − J_base > max(ρ_rej·J_base, k·σ_Δ) with σ_Δ = √(σ_J²(trial)+σ_J²(base))
from the plug-in cost statistics — the catastrophe cascade cannot fire on a
noise fluctuation, and an all-deterministic model (σ_Δ = 0) reduces to the
legacy pure ratio test; tr_scale shrink-only (halve on reject — permanent —
and after stall_patience flat trials), floored at tr_scale_min; the first
measured trial always establishes the base. The chassis reverts to the
last-accepted snapshot on rejection and discards the candidate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ep, dimension-change reset

IterateSelector seam: FinalIterate (legacy default), NoiseCorrectedBestJ
(argmin J̃ over accepted iterates — debiased selection removes the documented
best-J bias), TopKRemeasure(k, reps_factor) (re-measures finalists via the
n_shots override at n·reps_factor and picks the re-measured winner; fewer
than k accepted ⇒ re-measure all), PolyakAverage (reproduces the former
polyak_avg behavior exactly; the kwarg now forwards with a deprecation note).

IterationRecord gains J_hat, accepted, F_model (new last_f_model strategy
hook), and θ — the measurement-time trajectory snapshot selectors restore.
solve! gains a logger kwarg and calls record!(logger, rec) once per
iteration (InMemoryExperimentLogger collects them; other loggers opt in).

adapt!(device_model, …) now runs BEFORE the strategy step — recalibration
must precede Jacobian assembly (ordering inversion per spec).

A mid-campaign measurement-dimension drop (lab σ_z fallback) now restricts
the active goal/model, resets the acceptance base, and warns.

Full suite green (267/267).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
armijo_line_search gains a cost::Function kwarg (default: legacy raw
measurement_error). LineSearchAcceptance passes the plug-in whitened cost
so backtracking compares like units with its whitened Ĵ reference — a raw
trial cost against a whitened J_ref silently accepts every α = 1 step the
moment a ShotNoiseMeasurement is declared, and the campaign diverges
(caught by the Intonatissimo shot-noise campaign cells). Deterministic
models are unchanged (w ≡ 1 reduces the closure to measurement_error).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n strategy ctx

Task-importance weights (flattened per element, e.g. the σ_z×40 leakage
defense) become a chassis knob: composed into every whitened cost as
W = W_task·Σ^{-1/2}, restricted alongside the goal/model on a mid-campaign
dimension change, and forwarded to TopKRemeasure's re-measure whitening —
chassis, strategies, records, and selectors speak one unit. The strategy
ctx now also carries the ACTIVE measurement model and the chassis (w, σ2)
so strategies can whiten their Jacobians consistently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…TuningProblem

Model and experiment must share operator structure (frame); violation
silently null-spaces the Jacobian and stalls the loop (the feat/lab_frame
campaign lesson). Companion of the Intonatissimo QP-ILC loop docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aarontrowbridge

Copy link
Copy Markdown
Member

Review (adversarial pass against the approved spec/plan; statistics and behavior-preservation traces re-derived independently — they check out). Verdict: mergeable after fixes.

Blocking

  1. W_task is dropped from line-search probe costs. The chassis computes the reference cost with task weights (pulse_tuning_problem.jl:402-404) but LineSearchAcceptance's probe closure whitens without them (acceptance.jl:80) — the decide-ctx doesn't carry W_task. With per-element task weights + line_search=true, probe costs are deflated relative to the reference and backtracking accepts α=1 essentially unconditionally. Fix: thread W_task through the decide-ctx and closure; add one test combining task weights + line search (the existing weight test uses line_search=false, so this path is uncovered).
  2. Formatter CI: default-profile JuliaFormatter flags acceptance.jl, acceptance_test.jl, pulse_tuning_problem.jl, selectors_test.jl, experiments_noise_test.jl (over-margin lines). One format(".") commit.

Recommended
3. debiased_cost can go negative under measurement noise, and J_exp ≤ tol (pulse_tuning_problem.jl:405-408) then false-converges on a single downward fluctuation. Gate on J̃ + k·σ_J ≤ tol (or floor at 0).
4. The measurement-dimension-change guard is one-way and prefix-only (:377-396): a transient dropout permanently restricts, and a recovered full-length result then errors. Document or tolerate recovery.
5. var_floor is hardcoded separately in sampling (experiments.jl) and unexposed on the chassis — share a constant / plumb a kwarg. (The 0.05 default itself is right; the earlier 0.25 figure was self-inconsistent.)
6. Generic log labels: several @info strings in pulse_tuning_problem.jl (:288, :406, :560, :592) name a specific strategy family — this chassis is strategy-generic; use neutral wording ("pulse tuning iter …").
7. Q_meas docstrings should point to W_task + note deprecation (the chassis cost ignores Q_meas; dual-weighting surface is confusing as documented).
8. API hygiene: consider not exporting decide (generic verb); export observe!/reset_selector! alongside select_iterate! (stateful third-party selectors need them); note the IterationRecord field-layout break in the PR body.

Also suggest bumping the version (the adapt!-before-step ordering change is behavioral, and downstream compat needs to distinguish this chassis from the released 0.2.0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aarontrowbridge aarontrowbridge merged commit 27c5df5 into main Jul 3, 2026
7 checks passed
@aarontrowbridge aarontrowbridge deleted the feat/qilc-noise-native branch July 3, 2026 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants