PWR-HYBRID-3/plant-model/pke_solver.m
Dane Sabo cebf8c167a Initial umbrella repo: thesis + FRET pipeline + plant model with first controllers
Folds three previously-separate pieces into one preliminary-example repo
for the HAHACS thesis:

- thesis/ (submodule) → gitea Thesis.git — the PhD proposal
- fret-pipeline/ — FRET requirements to AIGER controller (was
  ~/Documents/fret_processing/; prior single-commit history abandoned
  per user decision)
- plant-model/ — 10-state PKE + lumped T/H PWR model (was
  ~/Documents/PKE_Playground/; never version-controlled before)
- presentations/2026DICE/ (submodule) → gitea 2026DICE.git
- reachability/, hardware/ — empty placeholders for Thrust 3 and HIL
- docs/architecture.md — how the discrete and continuous layers compose
- claude_memory/ — session notes and scratch knowledge pattern

Plant model refactored to thesis naming (x, plant, u, ref); pke_th_rhs
now takes u as an explicit arg instead of reading rho_ext from the
params struct. First two controllers built to the contract
u = ctrl_<mode>(t, x, plant, ref): ctrl_null (baseline) and
ctrl_operation (stabilizing, proportional on T_avg). Validated under a
100% -> 80% Q_sg step: ctrl_operation reduces steady-state T_avg drift
~47% vs. the unforced plant.

Root CLAUDE.md emphasizes that CLAUDE.md files are living documents and
that any knowledge not captured before a session ends is lost forever;
claude_memory/ holds the session-level notes that haven't stabilized
enough to graduate into a CLAUDE.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 16:24:11 -04:00

61 lines
2.5 KiB
Matlab

function [t, X, U] = pke_solver(plant, Q_sg, ctrl_fn, ref, tspan)
% PKE_SOLVER Solve the coupled PKE + T/H system in closed loop with a mode controller.
%
% Inputs:
% plant - parameter struct from pke_params()
% Q_sg - function handle Q_sg(t) returning SG heat removal [W]
% ctrl_fn - function handle u = ctrl_fn(t, x, plant, ref)
% ref - struct of per-mode setpoints (e.g. ref.T_avg); [] if unused
% tspan - [t_start, t_end] in seconds
%
% Outputs:
% t - time vector (M x 1) from the ODE solver
% X - state matrix (M x 10): columns are [n, C1..C6, T_f, T_c, T_cold]
% U - control input trajectory (M x 1): u evaluated at each (t(k), X(k,:))
CtoF = @(T) T * 9/5 + 32;
%% ===== Print Steady-State =====
fprintf('=== Steady-State Conditions ===\n');
fprintf(' beta = %.5f (%.1f pcm)\n', plant.beta, plant.beta*1e5);
fprintf(' Lambda = %.1e s\n', plant.Lambda);
fprintf(' P0 = %.0f MWth\n', plant.P0/1e6);
fprintf(' T_cold = %.1f F\n', CtoF(plant.T_cold0));
fprintf(' T_avg = %.1f F\n', CtoF(plant.T_c0));
fprintf(' T_hot = %.1f F\n', CtoF(plant.T_hot0));
fprintf(' T_fuel = %.1f F\n', CtoF(plant.T_f0));
fprintf(' Core dT = %.1f F\n', plant.dT_core * 9/5);
fprintf('================================\n\n');
%% ===== Solve closed-loop =====
x0 = pke_initial_conditions(plant);
options = odeset('RelTol', 1e-8, 'AbsTol', 1e-10);
rhs = @(t, x) pke_th_rhs(t, x, plant, Q_sg, ctrl_fn(t, x, plant, ref));
[t, X] = ode15s(rhs, tspan, x0, options);
%% ===== Reconstruct control trajectory =====
M = length(t);
U = zeros(M, 1);
for k = 1:M
U(k) = ctrl_fn(t(k), X(k,:).', plant, ref);
end
%% ===== Print Final State =====
n = X(end, 1); T_f = X(end, 8); T_c = X(end, 9); T_cold = X(end, 10);
T_hot = 2*T_c - T_cold;
rho_tot = U(end) ...
+ plant.alpha_f*(T_f - plant.T_f0) ...
+ plant.alpha_c*(T_c - plant.T_c0);
fprintf('=== Final State (t = %.0f s) ===\n', t(end));
fprintf(' Power = %.1f MWth (%.3f x nominal)\n', n*plant.P0/1e6, n);
fprintf(' T_fuel = %.1f F\n', CtoF(T_f));
fprintf(' T_hot = %.1f F\n', CtoF(T_hot));
fprintf(' T_avg = %.1f F\n', CtoF(T_c));
fprintf(' T_cold = %.1f F\n', CtoF(T_cold));
fprintf(' u = %.4f $ (external reactivity from controller)\n', U(end)/plant.beta);
fprintf(' rho = %.4f $ (total, incl. T-feedback)\n', rho_tot/plant.beta);
end