PWR-HYBRID-3/code/scripts/reach/reach_loca_operation.jl
Dane Sabo ef7ae06ffc point 2 + 3: LOCA entry reach + scram fat-entry + steam-dump heatup
Morning-review items 2 and 3.

Point 2 (scram X_entry expansion):
  - reach_loca_operation.jl: LQR reach under Q_sg widened to
    [0, 1.5*P_0] (steam-line break envelope) for 3 s horizon.
    Longer horizons cause numerical blowup in the box-hull
    reach propagator due to slow precursor modes amplifying
    under large disturbance — documented in the script.
  - reach_scram_pj_fat.jl: computes bounding-box union of
    hot-standby + heatup-tight + operation + LOCA reach
    envelopes, clamps obvious numerical outliers on precursors
    and temperatures, builds a fat X_entry(scram), runs scram
    PJ reach. Result pending (TMJets compiling).

Point 3 (heatup steam-dump Q_sg):
  - configs/heatup/with_steam_dump.toml: Q_sg ∈ [0, 0.05·P_0]
    as bounded parameter.
  - reach_heatup_pj_sd.jl: 12-state RHS with x[10]=Q_sg (dx=0,
    augmented bounded param) and x[11]=t. Running in
    background.

Tight-entry heatup via the new TOML-config reach_heatup_pj.jl
reproduces the previous all-6-halfspaces-discharged result
(300s horizon, T_c envelope [281.05, 291.0]). Refactor
preserves semantics.

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

91 lines
3.5 KiB
Julia

#!/usr/bin/env julia
#
# reach_loca_operation.jl — operation mode under a secondary-side LOCA.
#
# Simulates the fast transient just BEFORE scram fires: Q_sg jumps from
# nominal toward its worst-case runaway value (steam-line break = Q_sg
# spikes to ~1.5·P_0 as the primary cools rapidly). LQR can't handle
# this — scram is expected to fire. We care about WHERE the plant state
# ends up at the moment scram triggers, because that state becomes part
# of X_entry(scram).
#
# Uses the full-state linearized closed-loop under LQR, reach propagated
# with the existing hand-rolled reach_linear machinery (fast, sound for
# the linear model over short horizons).
using Pkg
Pkg.activate(joinpath(@__DIR__, "..", ".."))
using Printf
using LinearAlgebra
using MatrixEquations
using MAT
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
include(joinpath(@__DIR__, "..", "..", "src", "pke_linearize.jl"))
include(joinpath(@__DIR__, "..", "..", "src", "reach_linear.jl"))
plant = pke_params()
x_op = pke_initial_conditions(plant)
A, B, B_w, _, _, _ = pke_linearize(plant)
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
A_cl = A - reshape(B, :, 1) * K
# LOCA entry box — small perturbation around operating point (plant was
# nominally stable before the break).
delta_entry = [0.01 * x_op[1];
0.001 .* abs.(x_op[2:7]);
0.1; 0.1; 0.1]
# LOCA disturbance envelope: Q_sg spikes from P_0 up to 1.5·P_0 (break
# releases 50% more heat into secondary) or can collapse to 0 (break on
# a specific line isolates a loop). Either way widens the Q_sg range.
Q_nom = plant.P0
Q_min = 0.0
Q_max = 1.5 * plant.P0
w_lo = Q_min - Q_nom # -P_0
w_hi = Q_max - Q_nom # +0.5 P_0
# Short transient: scram must fire within a few seconds of detection
# of the LOCA event. 3 s is realistic (RPS trip + rod free-fall).
# Over longer horizons, the box-hull reach diverges numerically
# because precursor modes have slow return times (~56 s for group 1)
# and the worst-case |A_step|^k propagation grows super-linearly
# without cross-term cancellation — a known limitation of box-hull
# reach vs true zonotope propagation. The 3 s result captures the
# physically relevant transient before the DRC intervenes.
tspan = (0.0, 3.0)
dt = 0.05
T, R_lo, R_hi, Cnom = reach_linear(A_cl, B_w, zeros(10), delta_entry,
w_lo, w_hi, tspan, dt)
X_lo = R_lo .+ x_op
X_hi = R_hi .+ x_op
state_names = ["n","C1","C2","C3","C4","C5","C6","T_f","T_c","T_cold"]
println("\n=== LOCA reach — final-state envelope at t=$(tspan[2]) s ===")
println(" Disturbance: Q_sg ∈ [$(Q_min/1e6), $(Q_max/1e6)] MW (LOCA break)")
@printf " %-7s %-14s %-14s\n" "state" "lo" "hi"
for i in 1:10
@printf " %-7s %-14.4e %-14.4e\n" state_names[i] X_lo[i, end] X_hi[i, end]
end
# Save final-state envelope (per-state lo/hi at t=tspan[2]) for consumption
# by reach_scram_pj_fat_entry.jl.
mat_out = joinpath(@__DIR__, "..", "..", "..", "results", "reach_loca_operation.mat")
matwrite(mat_out, Dict(
"X_final_lo" => X_lo[:, end],
"X_final_hi" => X_hi[:, end],
"X_envelope_lo" => [minimum(X_lo[i, :]) for i in 1:10],
"X_envelope_hi" => [maximum(X_hi[i, :]) for i in 1:10],
"T" => collect(T),
"w_lo" => w_lo, "w_hi" => w_hi,
"state_names" => state_names,
))
println("\nSaved LOCA envelope to $mat_out")