PWR-HYBRID-3/code/src/pke_linearize.jl
Dane Sabo fbbaebff9f julia migration: port MATLAB to Julia, delete MATLAB, rename julia-port -> code
Full toolchain port. Numerical equivalence verified against MATLAB:
- main_mode_sweep.jl: every mode's final state matches MATLAB to 3-4 dp
- reach_operation.jl: per-halfspace margins match MATLAB exactly
- barrier_lyapunov.jl: per-halfspace bounds match (best Qbar from sweep
  yields max|dT_c| = 33.228 K either side)
- barrier_compare_OL_CL.jl: OL gamma 1.038e13, CL gamma 1.848e4
  matching the MATLAB result; LQR helps by ~20,000x on every halfspace.

Phase summary:
  Phase 1: pke_solver.jl, plot_pke_results.jl (Plots.jl), main_mode_sweep.jl
  Phase 2: reach_linear.jl, reach_operation.jl, barrier_lyapunov.jl,
           barrier_compare_OL_CL.jl, load_predicates.jl
  Phase 3 (this commit): delete plant-model/ entirely, delete reach
           code from reachability/ keeping predicates.json + docs,
           git mv julia-port/ -> code/, update root README + CLAUDE,
           write code/CLAUDE.md and code/README.md, update reach
           README + WALKTHROUGH file paths, journal preamble note
           that pre-port entries reference MATLAB paths.

Why now: prompt-neutron stiffness in nonlinear reach made it clear we
need TMJets, which is Julia. Already had the Julia plant model
working and matching MATLAB. Two languages = two sources of truth =
two places to drift. One language, one truth.

Manifest.toml gitignored. .mat results gitignored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:44:59 -04:00

38 lines
1.2 KiB
Julia

"""
pke_linearize(plant; x_star=nothing, u_star=0.0, Q_star=nothing)
Numerical Jacobians via central finite differences. Returns
`(A, B, B_w, x_star, u_star, Q_star)` such that for small (dx, du, dw):
dx/dt ≈ A dx + B du + B_w dw, where w = Q_sg.
Defaults: `x_star` = operating-point steady state, `u_star = 0`,
`Q_star = P0`.
"""
function pke_linearize(plant; x_star=nothing, u_star=0.0, Q_star=nothing)
x_star === nothing && (x_star = pke_initial_conditions(plant))
Q_star === nothing && (Q_star = plant.P0)
n = length(x_star)
eps_rel = 1e-6
eps_abs = 1e-8
f = (x, u, w) -> pke_th_rhs(x, 0.0, plant, t -> w, u)
A = zeros(n, n)
for k in 1:n
h = max(eps_rel * abs(x_star[k]), eps_abs)
xp = copy(x_star); xp[k] += h
xm = copy(x_star); xm[k] -= h
A[:, k] = (f(xp, u_star, Q_star) - f(xm, u_star, Q_star)) ./ (2h)
end
h = max(eps_rel * abs(u_star), eps_abs)
B = (f(x_star, u_star + h, Q_star) - f(x_star, u_star - h, Q_star)) ./ (2h)
h = max(eps_rel * abs(Q_star), 1.0)
B_w = (f(x_star, u_star, Q_star + h) - f(x_star, u_star, Q_star - h)) ./ (2h)
return A, B, B_w, x_star, u_star, Q_star
end