# CLAUDE.md Guidance for Claude Code (and any AI agent) working in this subdirectory. See also: the parent repo's `CLAUDE.md` (living-documentation rule — update this file when new knowledge is created) and `claude_memory/` for session notes that haven't yet earned a place here. ## What this is A 10-state point kinetic equation (PKE) model of a PWR coupled with lumped-parameter thermal-hydraulics, plus mode-specific continuous controllers. Implements the *plant* and the *tactical* layer of the HAHACS methodology — see `../thesis/3-research-approach/approach.tex` sections on transitory, stabilizing, and expulsory modes. The model is being developed for hybrid-systems reachability analysis. The end goal is barrier-certificate and reachability methods (in `../reachability/`) to prove each continuous mode satisfies its obligations under bounded `Q_sg(t)` variations. ## Naming convention (thesis-aligned) Continuous state and control-theory notation matches the thesis: | Symbol | Meaning | |---|---| | `t` | time [s] | | `x` | continuous state vector, `[n; C1..C6; T_f; T_c; T_cold]` | | `plant` | parameter struct from `pke_params()` (was `p` — renamed to avoid collision with `p_i` predicates) | | `u` | control input: external rod reactivity [dk/k] | | `Q_sg` | bounded disturbance: SG heat removal [W], as a function handle `Q_sg(t)` | | `ref` | reference/setpoint struct (e.g. `ref.T_avg`) | `T_avg = T_c` (algebraic alias); `T_hot = 2*T_c - T_cold` (linear coolant profile assumption). ## Running From inside MATLAB: ```matlab main % runs the default closed-loop scenario ``` From a terminal (figures appear as their own windows): ```bash matlab -nodesktop -nosplash -r "main" # drops at >> prompt; `exit` to quit matlab -r "main" & # full IDE, backgrounded matlab -batch "main" # headless sanity check (no figures shown) ``` `main.m` configures the scenario (plant, `Q_sg`, ref, tspan), picks a controller, and calls `pke_solver`. Tested on MATLAB R2025b; works on any MATLAB with `ode15s` (R2020b or newer). ## Architecture ``` plant-model/ pke_params.m plant parameters and derived steady state pke_initial_conditions.m analytic equilibrium x0 from plant pke_th_rhs.m dynamics: dx/dt = f(t, x, plant, Q_sg, u) pke_linearize.m numerical (A, B, B_w) at any x_star pke_solver.m closed-loop driver (optional x0 arg) plot_pke_results.m 4-panel results plot load_profile.m canned Q_sg demand shapes main.m entry point — single-mode scenarios main_mode_sweep.m runs all DRC modes back-to-back test_linearize.m sanity-check Jacobians vs nonlinear sim controllers/ ctrl_null.m u = 0 (baseline, no feedback) ctrl_shutdown.m hot standby: u = -5*beta (constant) ctrl_heatup.m ramp T_avg: feedback lin. + P on ramp, saturated u ctrl_operation.m stabilizing: P on T_avg ctrl_operation_lqr.m full-state LQR around x_op (persistent-cached K) ctrl_scram.m emergency: u = -8*beta (constant) ``` **Controller contract:** ```matlab u = ctrl_(t, x, plant, ref) ``` Pure function. Returns scalar `u` (external rod reactivity in `dk/k`). All four signature args are required even if unused — lets the solver swap controllers by function handle without caring which one it is. **Solver contract:** ```matlab [t, X, U] = pke_solver(plant, Q_sg, ctrl_fn, ref, tspan) ``` Builds the closed-loop RHS internally, calls `ode15s`, then reconstructs the control trajectory `U` by re-evaluating `ctrl_fn` at each returned time step. For event-driven or hysteretic controllers, this reconstruction may miss interior solver evaluations — revisit if / when that matters. ## Key design decisions - **`Q_sg` is the disturbance, never an actuator.** Cold leg temperature is a dynamic state resulting from SG heat removal — the reachability formulation needs `Q_sg ∈ [Q_min, Q_max]` as a bounded disturbance, not a control input. - **`u` is explicit.** `pke_th_rhs` takes `u` directly instead of reading `p.rho_ext(t)` from the params struct. This decouples plant from controller and matches the thesis's `f(x, u)` formulation. - **Controller gains live in the controller file.** Rough-out phase — we'll lift to a config struct once we're tuning more than one mode. The gain in `ctrl_operation` (`Kp = 1e-4 /K`) is chosen to roughly double the effective moderator coefficient, not for precision tracking. - **`ode15s` is required** because the system is stiff: `Lambda` (~10⁻⁴ s) vs thermal time constants (~10–100 s). ## Conventions - One function per file, each file has a single responsibility. - Internal model uses SI (W, kg, °C); all printed/plotted temperatures in °F. - Reference setpoints in SI (so `ref.T_avg` is in °C). - Controllers are pure functions — no persistent state. If a mode ever needs integral action, augment `x` rather than hiding state in globals. ## Model validity range The feedback coefficients `alpha_f`, `alpha_c` are *linear* slopes taken about the full-power operating point `(T_f0, T_c0)`. They become unphysical when extrapolated far from that reference — e.g. at true cold shutdown (~50 C everywhere), the model predicts ~+5 $ intrinsic reactivity that real reactors do not exhibit because temperature coefficients saturate and flip sign in the cold-unborated state. **Trust range:** roughly ±50 C around the operating point. "Shutdown" in this demo means hot standby (~290 C, n≈0), not cold shutdown. Extending to cold-shutdown semantics requires piecewise-linear or table-lookup temperature coefficients and is out of scope for the running example. ## For reachability work - `pke_th_rhs(t, x, plant, Q_sg, u)` is the `ẋ = f(x, u)` with `Q_sg` as the disturbance `w`. For a given mode, substitute `u = ctrl_(t, x, plant, ref)` to get the closed-loop `ẋ = f_cl(x, w)`. - Use `pke_linearize(plant, x_star, u_star, Q_star)` for `(A, B, B_w)` at any operating point; returns central-finite-difference Jacobians. The result at the full-power point is saved to `../reachability/linearization_at_op.mat` by `test_linearize.m`. - Safe regions come from the FRET spec's guards (see `../fret-pipeline/specs/synthesis_config_v3.json`) — these define `X_entry`, `X_exit`, `X_safe` per the thesis approach. Numerical predicate thresholds still need to be pinned (see `../claude_memory/` notes for the discussion). - Do NOT try to verify the whole hybrid system at once. Pick one mode, compute its reach set, discharge the per-mode obligation. The compositionality argument in the thesis is what makes this tractable. - First reach set is in `../reachability/reach_operation.m` (operation mode under LQR); a Lyapunov-ellipsoid barrier-cert attempt is in `../reachability/barrier_lyapunov.m`. ## For control design - The LQR gain in `ctrl_operation_lqr.m` is cached in a persistent variable. If you change `Q_lqr` or `R_lqr` or mutate `plant`, restart MATLAB to clear the cache. - `ctrl_heatup.m` uses feedback linearization + saturated P. No integrator on purpose — adding PI would double the plant's intrinsic thermal-mass integration and make anti-windup a hybrid mode, both bad for reach. - Starting heatup from a truly zero-power IC produces a large lag + power-spike overshoot in the simulation. Physical resolution: the reactor should be taken critical in shutdown mode (at ~0.1% power) before DRC transitions to heatup. `main_mode_sweep.m` uses this IC. ## Robustness caveats (idealized in current artifacts) - **α_f, α_c are treated as known exactly.** In reality α_f drifts ~20% over burnup; α_c spans ~10x across soluble-boron dilution over a cycle; xenon adds 2-3 $ reactivity on its own timescale. The feedback-linearization in `ctrl_heatup.m` assumes the controller's α matches the plant's; if not, the clean `rho_total = Kp*e` property degrades to `Kp*e + delta*alpha*dT`, and the P term must absorb the residual. Stabilization still holds but reach analysis should eventually treat α as a bounded parametric uncertainty. - **Saturation is a hybrid sub-mode.** The `sat(u, u_min, u_max)` in `ctrl_heatup.m` is formally piecewise affine. Current reach treats it as dormant (true for operation/LQR, near-true for the demo heatup trajectory). A rigorous heatup reach has to model the saturation regions explicitly. - **Linear-model reach is not sound for the nonlinear plant.** The reach artifacts in `../reachability/` use the linearization; the result is a sound over-approximation of the LINEAR model's reach, not of the plant's. To upgrade: nonlinear reach directly, or linear reach + Taylor-remainder inflation. See `../reachability/README.md` § Soundness status.