PWR-HYBRID-3/julia-port/scripts/reach_operation.jl
Dane Sabo 9fc4afb611 julia-port: parallel plant model; sanity sim matches MATLAB, reach is stub
Port pke_params, pke_th_rhs, pke_linearize, and all five controllers
to Julia. sim_sanity.jl reproduces the MATLAB main.m operation-mode
scenario (100%->80% Q_sg step) and matches final state to 3 decimals
across n, T_f, T_avg, T_cold, u.

reach_operation.jl is a stub: ReachabilityAnalysis.jl (LGG09, GLGM06,
BFFPSV18) numerically explodes on the raw stiff system — envelopes of
1e14 K to 1e37 K instead of the known-tight 0.03 K. Almost certainly
a state-scaling issue: precursors C_i ~ 1e5, temperatures ~ 300,
eigvals span 5000x. Diagonal scaling + retry is planned; left for the
next pass since the hand-rolled MATLAB reach already discharges the
operation-mode obligation.

Project.toml pins OrdinaryDiffEq >= 6.111 (the one that precompiled
cleanly on first instantiate). Manifest gitignored.

Hacker-Split: Julia path open, reach side needs a scaling pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 12:52:57 -04:00

91 lines
3.5 KiB
Julia
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env julia
#
# reach_operation.jl — operation-mode linear reach in Julia. **STUB**.
#
# Attempt to reproduce reachability/reach_operation.m using
# ReachabilityAnalysis.jl.
#
# STATUS: does NOT yet produce a useful result. The closed-loop system
# is strongly stiff (eigvals spanning 0.012 to 65 /s) and has states
# with magnitudes differing by ~10 orders of magnitude (precursors C_i
# ~ 1e5 due to 1/Lambda, temperatures ~ 300). LGG09 / GLGM06 / BFFPSV18
# all blow up numerically over the 600s horizon, returning envelopes
# ranging from 2757 K to 1e37 K instead of the known-tight 0.03 K.
#
# Likely fix: diagonal state scaling S such that A_cl_scaled = S A_cl S^{-1}
# has O(1) entries, run reach in scaled coordinates, then invert S.
# Also worth trying: ASB07 (adaptive step) or the Taylor-model schemes.
# Left as future work — the hand-rolled MATLAB zonotope in
# reachability/reach_linear.m gives a valid result today, so the Julia
# port is priority-B.
using Pkg
Pkg.activate(joinpath(@__DIR__, ".."))
using LinearAlgebra
using MatrixEquations
using ReachabilityAnalysis, LazySets
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs.jl"))
include(joinpath(@__DIR__, "..", "src", "pke_linearize.jl"))
plant = pke_params()
x_op = pke_initial_conditions(plant)
# --- Linearize around the operating point ---
A, B, B_w, _, _, _ = pke_linearize(plant)
# --- LQR gain (same Q, R as the MATLAB side) ---
Q_lqr = Diagonal([1.0, 1e-3, 1e-3, 1e-3, 1e-3, 1e-3, 1e-3, 1e-2, 1e2, 1.0])
R_lqr = 1e6 * ones(1, 1)
X_ric, _, _ = arec(A, reshape(B, :, 1), R_lqr, Matrix(Q_lqr))
K = (R_lqr \ reshape(B, 1, :) * X_ric) # 1 x 10
A_cl = A - reshape(B, :, 1) * K
println("\n=== Julia closed-loop spectrum ===")
ev = eigvals(A_cl)
println(" max Re(eig) = ", round(maximum(real.(ev)); sigdigits=4))
println(" min Re(eig) = ", round(minimum(real.(ev)); sigdigits=4))
# --- Reach-set problem: dx/dt = A_cl dx + B_w w, dx(0) ∈ X0, w ∈ W ---
delta_entry = [0.01 * x_op[1];
0.001 .* abs.(x_op[2:7]);
0.1; 0.1; 0.1]
X0 = Hyperrectangle(zeros(10), delta_entry)
Q_nom = plant.P0
w_lo, w_hi = -0.15 * plant.P0, 0.0
W = Interval(w_lo, w_hi)
B_w_col = reshape(B_w, :, 1)
# Encode the bounded disturbance as a bounded input:
# dx/dt = A_cl x + B_w u, u ∈ W.
# Direct constructor since the @system macro's dialect is package-version sensitive.
sys = ConstrainedLinearControlContinuousSystem(A_cl, B_w_col, Universe(10), W)
prob = InitialValueProblem(sys, X0)
sol = solve(prob; T=600.0, alg=GLGM06(; δ=0.5, max_order=20))
# Extract T_c envelope via support function queries.
flow = flowpipe(sol)
e9 = zeros(10); e9[9] = 1.0
T_c_hi = [ρ(e9, set(R)) for R in flow]
T_c_lo = [-ρ(-e9, set(R)) for R in flow]
println("\n=== Operation-mode reach envelope on T_c (deviation from T_c0) ===")
println(" min dT_c = ", round(minimum(T_c_lo); digits=4), " K")
println(" max dT_c = ", round(maximum(T_c_hi); digits=4), " K")
println(" safety band |dT_c| <= 5.0 K")
if maximum(abs.([T_c_lo; T_c_hi])) <= 5.0
println(" OK: Julia reach set inside safety band.")
else
println(" *** VIOLATION ***")
end
# Save the envelope for later comparison
using MAT
matwrite(joinpath(@__DIR__, "..", "..", "reachability", "julia_reach_operation.mat"),
Dict("T_c_lo" => T_c_lo, "T_c_hi" => T_c_hi,
"A_cl" => A_cl, "K" => Matrix(K), "delta_entry" => delta_entry))
println("\nSaved envelope to reachability/julia_reach_operation.mat")