# 2026-04-16 — controllers scaffold and ctrl_operation rough-out First continuous controller. Established the controller contract, refactored the plant to thesis naming, and got a closed-loop sim running end-to-end with a meaningful comparison against the unforced baseline. ## Naming convention (locked in) Thesis-aligned. Applied to plant and controllers: - `t` — time - `x` — continuous state vector `[n; C1..C6; T_f; T_c; T_cold]` - `plant` — params struct (was `p`; renamed to avoid collision with `p_i` predicates from the thesis) - `u` — control input: external rod reactivity (scalar, `dk/k`) - `ref` — reference/setpoint struct (e.g. `ref.T_avg`) - `Q_sg` — bounded disturbance (SG heat removal, `W`), a function handle of time Captured in `plant-model/CLAUDE.md` under "Naming convention" — any future file in `plant-model/` or `controllers/` uses these names. ## Controller contract ```matlab u = ctrl_(t, x, plant, ref) ``` Pure function. Returns scalar `u`. All four args required even if unused (so `pke_solver` can swap controllers by function handle without branching). ## Plant refactor Four files touched. Non-cosmetic changes: - `pke_th_rhs.m` now takes `u` as an **explicit argument** instead of reading `p.rho_ext(t)` from the params struct. This decouples plant from controller and matches the thesis's `f(x, u)` form. Old `rho_ext` field on the params struct is gone. - `pke_solver.m` signature changed: `[t, X, U] = pke_solver(plant, Q_sg, ctrl_fn, ref, tspan)`. Returns `U` (the control trajectory), reconstructed post-hoc by re-evaluating `ctrl_fn` at each returned time step. - `plot_pke_results.m` takes `(t, X, U, plant, Q_sg, title)` — reactivity panel now shows `u`, feedback, and total separately rather than relying on a `p.rho_ext` handle. - `pke_initial_conditions.m` trivial rename `p → plant`, output `x0`. ## ctrl_operation Proportional-only for now. `Kp = 1e-4 /K`, chosen to roughly double the effective moderator coefficient (`alpha_c ≈ -1e-4 /K`). Not tuned for precision — this is rough-out. PI upgrade would require augmenting state with integral error; deferred until we actually need tighter tracking. **Validation run** (100% → 80% Q_sg step at t=30s, 600s horizon): | Metric | ctrl_null | ctrl_operation | |---|---|---| | Final T_avg drift from setpoint | +1.5 °F | +0.8 °F | | Final `u` | 0 $ | -0.0068 $ | | Final power | 800 MW ✓ | 800 MW ✓ | Sign polarity: T_avg > ref → e < 0 → u < 0 (rods in). Correct for a PWR. Controller reduces steady-state offset by ~47%. Expected result — gain is modest; the point is the plumbing works, not that the tuning is final. ## What's deferred (on purpose) - **Integral action in ctrl_operation.** P-only for now. If we want to hit `ref.T_avg` exactly (zero steady-state error), augment state with `int_e` and upgrade to PI. - **Gain config.** Gains hardcoded in the controller file. Fine for one mode; once we tune 3–4 modes we should lift to a `ctrl_params.m` or similar. - **Mode dispatcher.** No `ctrl_dispatch.m` yet because there's only one real controller. Will introduce when we add the second mode and need to switch based on discrete state. - **ctrl_heatup, ctrl_scram, ctrl_shutdown.** Next. Each is a separate design problem with its own verification method (reachability, parametric reach, trivial). - **Invariants and `X_safe` regions.** User explicitly said: rough out controllers first, then come back to invariants. Per the thesis, we're not trying to verify the whole hybrid system at once — one mode's reach set at a time. ## Things worth remembering - `addpath('controllers')` is required in `main.m` — MATLAB won't find `@ctrl_operation` otherwise. Putting controllers in a subfolder is ergonomically clean but the path has to be set. - `U` is reconstructed post-hoc in `pke_solver`. If a future controller has hysteresis or switching behavior, this reconstruction may not match the ODE's internal trajectory (the solver might have evaluated `ctrl_fn` at interior points that don't appear in the returned `t`). For smooth controllers (P, PI) this is fine; for event-driven / hybrid controllers we'll need to log `u` during the solve instead. - MATLAB `-batch` mode runs `main.m` cleanly without a display — figures are created off-screen but the script completes. Good for CI or sanity checks without opening the IDE. - `T_avg` is literally `T_c` in the state vector (`x(9)`). Don't let the two names confuse future debugging — `T_avg` is the nuclear-industry term, `T_c` is the code variable, they're the same quantity. ## Paths touched - Modified: `plant-model/pke_th_rhs.m`, `pke_solver.m`, `pke_initial_conditions.m`, `plot_pke_results.m`, `main.m`, `README.md`, `CLAUDE.md` - Created: `plant-model/controllers/ctrl_null.m`, `plant-model/controllers/ctrl_operation.m` - Nothing outside `plant-model/` was touched this session.