overnight wrap: summary notes + tighter-entry heatup PJ stub

- OVERNIGHT_NOTES.md: read-this-first TL;DR for morning review.
  Points at journal.pdf (32 pages, latest entry has all results),
  validate_pj_heatup.png, and the Pluto app.  Lists priority-1 actions
  (look at data, decide on refinement vs accept-300s-tube) and
  priority-2 followups (scram reach ingestion, entry refinement,
  saturation hybrid, SOS barriers, alpha parametric).

- reach_heatup_pj_tight.jl: script committed but not yet run tonight.
  Tighter X_entry on T_c (width 6 K vs baseline 14 K) to test whether
  the low-T_avg-trip tube-looseness is entry-box-width driven or
  reach-growth driven.

Scram PJ still compiling as of this commit; will land separately
when it completes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dane Sabo 2026-04-21 14:49:48 -04:00
parent 0a8348e5d8
commit aba017c5b1
2 changed files with 218 additions and 0 deletions

109
OVERNIGHT_NOTES.md Normal file
View File

@ -0,0 +1,109 @@
# Overnight session summary
**Date:** 2026-04-20 → 2026-04-21 (overnight autonomous session)
Read this first when you open the laptop. Full details in
`journal/journal.pdf` (newest entry: `2026-04-20-overnight-prompt-jump.tex`).
## TL;DR
1. **Implemented the prompt-jump (singular-perturbation) PKE reduction**
in `code/src/pke_th_rhs_pj.jl`. State drops 10 → 9 (n algebraic).
Validated against full 10-state: max 0.1% relative error on n, max
7 mK on temperatures over 50 minutes of heatup. See
`docs/figures/validate_pj_heatup.png`.
2. **Nonlinear reach on heatup PJ: 30× horizon improvement.**
- Before: full-state hit prompt-neutron stiffness wall at T=10s.
- After PJ: T=60s and T=300s reach sound, clean.
- T=1800s+ runs out of 100k step budget but returns a valid partial
tube.
- 5 of 6 `inv1_holds` halfspaces discharged at T=300s.
- The `t_avg_low_trip` (T_c ≥ 280 °C) is violated by the TUBE
(envelope dips to 272.4), not by the plant itself — this is
over-approximation looseness and the nominal trajectory stays
above 280. Refinement options listed in the journal.
3. **Scram PJ reach** — script written and running as of commit time.
Result will land in the journal; check
`reachability/reach_scram_pj_result.mat` + the latest git commit.
4. **App v2 (Pluto)** — three new cells in the predicate explorer:
- §9b: live ingestion of `reach_operation_result.mat`; per-halfspace
margins computed from JSON, rendered as a table with ✅/❌ badges.
- §9c: 2D projection chooser (pick any two of {n, T_f, T_c, T_cold}
and see the operating polytope + reach tube envelope).
- §9d: placeholder for PJ heatup reach overlay once the app learns
to read `reach_heatup_pj_result.mat`.
5. **Journal:** now 32 pages, rebuilds cleanly via `latexmk -pdf` in
`journal/`. Fixed a couple of LaTeX gotchas — `\degreeFahrenheit`,
`\microsecond`, and UTF-8 passthrough in `lstlisting` blocks.
## What's in this session's commits (most recent first)
- `0a8348e` walkthrough: PJ reach results (30× horizon win)
- `3fdf5ee` PJ reach 30× improvement (commits envelope summaries)
- `(earlier tonight)` prompt-jump model + app v2 + journal scaffold
## Things to look at in the morning
### Priority-1 (actual scientific content)
1. **Pull up `journal/journal.pdf`.** 32 pages, all the deep stuff.
Most relevant entry: `2026-04-20-overnight-prompt-jump.tex`.
2. **Look at `docs/figures/validate_pj_heatup.png`** — the visual
evidence that PJ is a sound reduction for slow heatup.
3. **Pluto app:**
```bash
cd app && julia --project=. -e 'using Pluto; Pluto.run()'
# open predicate_explorer.jl
```
§9b should show live margins from the refreshed reach result.
4. **Decide:** accept the 300s PJ tube as the headline heatup reach
artifact, or invest in refinement (tighter entry box, entry
splitting) to get past 1800s with the low-trip discharged.
### Priority-2 (followup ideas)
- `code/scripts/reach_heatup_pj_tight.jl` exists (committed but not
run tonight). Tighter T_c entry box; if a run has time, see
whether the low-trip tube bound rises above 280.
- `code/scripts/reach_scram_pj.jl` — scram reach. Either already in
the commit log by morning, or still running.
- Refinement (entry splitting into sub-boxes, unioning reach results)
is the textbook way to tighten tubes. Haven't implemented.
- Saturation-as-hybrid-sub-mode — still the open item for heatup
soundness.
- Polytopic/SOS barrier — still the open item for analytic
certificate.
- Parametric α uncertainty — still the open item for robust reach.
### What NOT to forget
- The PJ reduction introduces a ≤0.1% error. It's documented and
bounded, but it's an approximation. "Sound w.r.t. PJ dynamics"
≠ "sound w.r.t. physical plant." A Tikhonov-style closed-form
error bound would close this gap rigorously.
- Mode boundary numbers are still engineering guesses — pin to real
tech-spec values before defense.
- `reach_heatup_pj_result.mat` has *envelope summaries* at the probe
horizons, not the full time-varying tube. The app §9d overlay
placeholder needs the full tube if you want an animated plot.
## Context for whoever's picking this up
Starting commit tonight (pre-PJ work): `83c5cb8`.
Current HEAD: `0a8348e` + whatever scram produced.
Clean working tree expected (commits are atomic per task).
MATLAB is gone. Everything is Julia. Predicates JSON is the single
source of truth for numerical halfspaces. Journal is the narrative
source of truth. `claude_memory/` has short pointers.
Was fun to work on. Go read the overnight entry, grab coffee, pick
the next lever. 🦎
---
*— Hacker-Split, overnight 2026-04-20 → 2026-04-21*

View File

@ -0,0 +1,109 @@
#!/usr/bin/env julia
#
# reach_heatup_pj_tight.jl — heatup PJ reach with a tighter X_entry.
#
# The default X_entry (from predicates.json) has T_c ∈ [281, 295] — 14 K
# wide. The baseline PJ reach at T=300s produced T_c envelope
# [272.4, 295.0], violating the low-T_avg trip at 280.
#
# Hypothesis: entry-box width is contributing to tube growth. Try
# T_c ∈ [285, 291] (6 K) and T_f matched, see if the lower envelope
# rises above 280.
using Pkg
Pkg.activate(joinpath(@__DIR__, ".."))
using LinearAlgebra
using ReachabilityAnalysis, LazySets
using MAT
# Same constants as reach_heatup_pj.jl.
const LAMBDA = 1e-4
const BETA_1, BETA_2, BETA_3, BETA_4, BETA_5, BETA_6 =
0.000215, 0.001424, 0.001274, 0.002568, 0.000748, 0.000273
const BETA = BETA_1 + BETA_2 + BETA_3 + BETA_4 + BETA_5 + BETA_6
const LAM_1, LAM_2, LAM_3, LAM_4, LAM_5, LAM_6 =
0.0124, 0.0305, 0.111, 0.301, 1.14, 3.01
const P0 = 1e9
const M_F, C_F, M_C, C_C, HA, W_M, M_SG =
50000.0, 300.0, 20000.0, 5450.0, 5e7, 5000.0, 30000.0
const T_COLD0 = 290.0
const DT_CORE = P0 / (W_M * C_C)
const T_HOT0 = T_COLD0 + DT_CORE
const T_C0 = (T_HOT0 + T_COLD0) / 2
const T_F0 = T_C0 + P0 / HA
const T_STANDBY = T_C0 - 33.333333
const RAMP_RATE_CS = 28.0 / 3600
const KP_HEATUP = 1e-4
@taylorize function rhs_heatup_pj_tight!(dx, x, p, t)
rho = KP_HEATUP * (T_STANDBY + RAMP_RATE_CS * x[10] - x[8])
sum_lam_C = LAM_1*x[1] + LAM_2*x[2] + LAM_3*x[3] +
LAM_4*x[4] + LAM_5*x[5] + LAM_6*x[6]
denom = BETA - rho
n = LAMBDA * sum_lam_C / denom
inv_factor = sum_lam_C / denom
dx[1] = BETA_1 * inv_factor - LAM_1 * x[1]
dx[2] = BETA_2 * inv_factor - LAM_2 * x[2]
dx[3] = BETA_3 * inv_factor - LAM_3 * x[3]
dx[4] = BETA_4 * inv_factor - LAM_4 * x[4]
dx[5] = BETA_5 * inv_factor - LAM_5 * x[5]
dx[6] = BETA_6 * inv_factor - LAM_6 * x[6]
dx[7] = (P0 * n - HA * (x[7] - x[8])) / (M_F * C_F)
dx[8] = (HA * (x[7] - x[8]) - 2 * W_M * C_C * (x[8] - x[9])) / (M_C * C_C)
dx[9] = (2 * W_M * C_C * (x[8] - x[9])) / (M_SG * C_C)
dx[10] = one(x[1])
return nothing
end
# Tighter X_entry on T_c and T_f specifically.
n_lo, n_hi = 1.0e-3, 2.0e-3 # narrower n
T_f_lo, T_f_hi = 285.0, 291.0 # was 275295
T_c_lo, T_c_hi = 285.0, 291.0 # was 281295
T_cold_lo, T_cold_hi = 278.0, 285.0 # was 270281 (shifted up)
n_mid = 0.5 * (n_lo + n_hi)
C_mid = [BETA_1/(LAM_1*LAMBDA), BETA_2/(LAM_2*LAMBDA),
BETA_3/(LAM_3*LAMBDA), BETA_4/(LAM_4*LAMBDA),
BETA_5/(LAM_5*LAMBDA), BETA_6/(LAM_6*LAMBDA)] .* n_mid
x_lo = [C_mid[1]*(n_lo/n_mid), C_mid[2]*(n_lo/n_mid),
C_mid[3]*(n_lo/n_mid), C_mid[4]*(n_lo/n_mid),
C_mid[5]*(n_lo/n_mid), C_mid[6]*(n_lo/n_mid),
T_f_lo, T_c_lo, T_cold_lo, 0.0]
x_hi = [C_mid[1]*(n_hi/n_mid), C_mid[2]*(n_hi/n_mid),
C_mid[3]*(n_hi/n_mid), C_mid[4]*(n_hi/n_mid),
C_mid[5]*(n_hi/n_mid), C_mid[6]*(n_hi/n_mid),
T_f_hi, T_c_hi, T_cold_hi, 0.0]
X0 = Hyperrectangle(low=x_lo, high=x_hi)
println("\n=== Heatup PJ reach with TIGHTENED X_entry ===")
println(" T_c ∈ [$(T_c_lo), $(T_c_hi)] (width 6 K, was 14 K)")
println(" T_f ∈ [$(T_f_lo), $(T_f_hi)]")
println(" n-implied ∈ [$(n_lo), $(n_hi)]")
for T_probe in (60.0, 300.0)
println("\n--- Probe T = $T_probe s ---")
sys = BlackBoxContinuousSystem(rhs_heatup_pj_tight!, 10)
prob = InitialValueProblem(sys, X0)
try
alg = TMJets(orderT=4, orderQ=2, abstol=1e-9, maxsteps=100000)
t_start = time()
sol = solve(prob; T=T_probe, alg=alg)
elapsed = time() - t_start
flow = flowpipe(sol)
flow_hr = overapproximate(flow, Hyperrectangle)
Tc_lo_env = minimum(low(set(R), 8) for R in flow_hr)
Tc_hi_env = maximum(high(set(R), 8) for R in flow_hr)
Tf_lo_env = minimum(low(set(R), 7) for R in flow_hr)
Tf_hi_env = maximum(high(set(R), 7) for R in flow_hr)
println(" $(length(flow_hr)) sets in $(round(elapsed; digits=1))s")
println(" T_c envelope: [$(round(Tc_lo_env; digits=2)), $(round(Tc_hi_env; digits=2))] °C")
println(" T_f envelope: [$(round(Tf_lo_env; digits=2)), $(round(Tf_hi_env; digits=2))] °C")
println(" Low-T_avg trip (T_c ≥ 280): $(Tc_lo_env >= 280 ? "✅ DISCHARGED" : "× still loose")")
catch err
println(" FAILED: ", first(sprint(showerror, err), 200))
end
end