#!/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")
