refactor: scripts subdivision + TOML configs + results/ split + presentation outline
Architecture restructure from morning review: 1. code/scripts/ subdivided into sim/, reach/, barrier/, plot/. Easier nav; `barrier/` is the natural place for SOS scale-up scripts. 2. Heatup PJ reach variants consolidated behind TOML configs. reach_heatup_pj.jl now takes `--config path/to/config.toml`; configs/heatup/baseline.toml (wide entry, from predicates.json) and configs/heatup/tight.toml (narrow entry, reproduces all-6-halfspaces discharged result). Old reach_heatup_pj_tight.jl and reach_heatup_pj_tight_full.jl deleted (superseded). 3. Reach output .mat files moved from reachability/ to results/. reachability/ now = specs + docs; results/ = ephemeral outputs (gitignored *.mat). README added. 4. OVERNIGHT_NOTES.md archived to claude_memory/2026-04-20-21-overnight- session-summary.md (date range in the filename makes the history clearer). All include() / Pkg.activate() paths in scripts updated for the new depth. Smoke tests pass (reach_operation.jl generates its .mat in the new results/ location; sim_sanity.jl matches MATLAB). Presentation outline for the 20-min prelim talk landed in presentations/prelim-presentation/outline.md. 14-slide assertion- evidence format targeting OT-informed cybersecurity audience. Each slide: one declarative assertion + one figure. Outline includes which figures already exist and which need to be created, timing checkpoints, cybersecurity angle to emphasize, and Q&A prep. New config configs/heatup/with_steam_dump.toml + its companion scripts/reach/reach_heatup_pj_sd.jl (12-state RHS with Q_sg as an augmented bounded parameter x[10] and time as x[11]). Kicks off point 3 from morning review. Next up: scram X_entry expansion (morning point 2) — LOCA scenario + union of mode reach envelopes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c5816c359e
commit
2bbb1871cc
@ -324,7 +324,7 @@ no more hand-maintained traceability table.
|
|||||||
|
|
||||||
# ╔═╡ 37d6b212-9f00-4684-9f91-50c7e17cbd62
|
# ╔═╡ 37d6b212-9f00-4684-9f91-50c7e17cbd62
|
||||||
begin
|
begin
|
||||||
reach_mat_path = joinpath(@__DIR__, "..", "reachability", "reach_operation_result.mat")
|
reach_mat_path = joinpath(@__DIR__, "..", "results", "reach_operation_result.mat")
|
||||||
have_reach_mat = isfile(reach_mat_path)
|
have_reach_mat = isfile(reach_mat_path)
|
||||||
if have_reach_mat
|
if have_reach_mat
|
||||||
reach_mat = matread(reach_mat_path)
|
reach_mat = matread(reach_mat_path)
|
||||||
@ -463,7 +463,7 @@ extend past the ~10 s prompt-neutron stiffness wall.
|
|||||||
|
|
||||||
# ╔═╡ 45e8962a-873e-48d3-aac4-70ea0cf36c97
|
# ╔═╡ 45e8962a-873e-48d3-aac4-70ea0cf36c97
|
||||||
begin
|
begin
|
||||||
pj_mat_path = joinpath(@__DIR__, "..", "reachability", "reach_heatup_pj_result.mat")
|
pj_mat_path = joinpath(@__DIR__, "..", "results", "reach_heatup_pj_result.mat")
|
||||||
if isfile(pj_mat_path)
|
if isfile(pj_mat_path)
|
||||||
pj_mat = matread(pj_mat_path)
|
pj_mat = matread(pj_mat_path)
|
||||||
md"""**Loaded** `reach_heatup_pj_result.mat` — pending plot rendering."""
|
md"""**Loaded** `reach_heatup_pj_result.mat` — pending plot rendering."""
|
||||||
|
|||||||
22
code/configs/heatup/baseline.toml
Normal file
22
code/configs/heatup/baseline.toml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Baseline heatup-mode reach configuration.
|
||||||
|
# Wide X_entry as specified in predicates.json mode_boundaries.
|
||||||
|
# Matches the original reach_heatup_pj.jl defaults.
|
||||||
|
|
||||||
|
name = "baseline"
|
||||||
|
description = "Wide X_entry from mode_boundaries.q_heatup.X_entry_polytope"
|
||||||
|
|
||||||
|
# Load X_entry from predicates.json (true) or from this file (false).
|
||||||
|
use_predicates_entry = true
|
||||||
|
|
||||||
|
[tmjets]
|
||||||
|
orderT = 4
|
||||||
|
orderQ = 2
|
||||||
|
abstol = 1e-9
|
||||||
|
maxsteps = 100000
|
||||||
|
|
||||||
|
[probes]
|
||||||
|
horizons_seconds = [60.0, 300.0, 1800.0, 5400.0]
|
||||||
|
|
||||||
|
[output]
|
||||||
|
save_per_step = true
|
||||||
|
result_file = "reach_heatup_pj_baseline.mat"
|
||||||
27
code/configs/heatup/tight.toml
Normal file
27
code/configs/heatup/tight.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Tight X_entry heatup reach: T_c in [285, 291] (6 K vs 14 K baseline).
|
||||||
|
# Produces the clean "all 6 inv1_holds halfspaces discharged at T=300s"
|
||||||
|
# result from 2026-04-20 overnight session.
|
||||||
|
|
||||||
|
name = "tight"
|
||||||
|
description = "Narrow X_entry on T_c/T_f/T_cold; n in [1e-3, 2e-3]"
|
||||||
|
|
||||||
|
use_predicates_entry = false
|
||||||
|
|
||||||
|
[entry]
|
||||||
|
n_range = [1.0e-3, 2.0e-3]
|
||||||
|
T_f_range_C = [285.0, 291.0]
|
||||||
|
T_c_range_C = [285.0, 291.0]
|
||||||
|
T_cold_range_C = [278.0, 285.0]
|
||||||
|
|
||||||
|
[tmjets]
|
||||||
|
orderT = 4
|
||||||
|
orderQ = 2
|
||||||
|
abstol = 1e-9
|
||||||
|
maxsteps = 100000
|
||||||
|
|
||||||
|
[probes]
|
||||||
|
horizons_seconds = [60.0, 300.0]
|
||||||
|
|
||||||
|
[output]
|
||||||
|
save_per_step = true
|
||||||
|
result_file = "reach_heatup_pj_tight.mat"
|
||||||
40
code/configs/heatup/with_steam_dump.toml
Normal file
40
code/configs/heatup/with_steam_dump.toml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Heatup reach with a bounded secondary-side steam-dump Q_sg.
|
||||||
|
#
|
||||||
|
# Instead of Q_sg ≡ 0 (original assumption), treat Q_sg as a bounded
|
||||||
|
# disturbance in [0, 0.05·P_0]. Physical interpretation: operator
|
||||||
|
# opens/closes the secondary-side steam dump to manage primary
|
||||||
|
# temperature during the ramp; exact value not known, but bounded
|
||||||
|
# by atmospheric-dump capacity (~5% of P_0 rated).
|
||||||
|
#
|
||||||
|
# The reach script picks up Q_sg as an augmented state x[11] with
|
||||||
|
# dx[11] = 0, entry box covering [0, 0.05*P_0].
|
||||||
|
|
||||||
|
name = "with_steam_dump"
|
||||||
|
description = "Tight X_entry + bounded Q_sg ∈ [0, 0.05·P_0] as disturbance"
|
||||||
|
|
||||||
|
use_predicates_entry = false
|
||||||
|
steam_dump_enabled = true
|
||||||
|
|
||||||
|
[entry]
|
||||||
|
n_range = [1.0e-3, 2.0e-3]
|
||||||
|
T_f_range_C = [285.0, 291.0]
|
||||||
|
T_c_range_C = [285.0, 291.0]
|
||||||
|
T_cold_range_C = [278.0, 285.0]
|
||||||
|
|
||||||
|
[entry.steam_dump]
|
||||||
|
# Q_sg bounds in fractions of P_0.
|
||||||
|
Q_lo_fraction_P0 = 0.0
|
||||||
|
Q_hi_fraction_P0 = 0.05
|
||||||
|
|
||||||
|
[tmjets]
|
||||||
|
orderT = 4
|
||||||
|
orderQ = 2
|
||||||
|
abstol = 1e-9
|
||||||
|
maxsteps = 100000
|
||||||
|
|
||||||
|
[probes]
|
||||||
|
horizons_seconds = [60.0, 300.0]
|
||||||
|
|
||||||
|
[output]
|
||||||
|
save_per_step = true
|
||||||
|
result_file = "reach_heatup_pj_with_steam_dump.mat"
|
||||||
@ -8,17 +8,17 @@
|
|||||||
# anisotropy, not controller tuning.
|
# anisotropy, not controller tuning.
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using Printf
|
using Printf
|
||||||
using LinearAlgebra
|
using LinearAlgebra
|
||||||
using MatrixEquations
|
using MatrixEquations
|
||||||
using JSON
|
using JSON
|
||||||
|
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_linearize.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_linearize.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "load_predicates.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "load_predicates.jl"))
|
||||||
|
|
||||||
plant = pke_params()
|
plant = pke_params()
|
||||||
x_op = pke_initial_conditions(plant)
|
x_op = pke_initial_conditions(plant)
|
||||||
@ -10,17 +10,17 @@
|
|||||||
# This is the structural anisotropy limitation, not a code bug.
|
# This is the structural anisotropy limitation, not a code bug.
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using Printf
|
using Printf
|
||||||
using LinearAlgebra
|
using LinearAlgebra
|
||||||
using MatrixEquations
|
using MatrixEquations
|
||||||
using JSON
|
using JSON
|
||||||
|
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_linearize.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_linearize.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "load_predicates.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "load_predicates.jl"))
|
||||||
|
|
||||||
plant = pke_params()
|
plant = pke_params()
|
||||||
x_op = pke_initial_conditions(plant)
|
x_op = pke_initial_conditions(plant)
|
||||||
@ -21,7 +21,7 @@
|
|||||||
# Uses JuMP + HiGHS (open-source, ships with no license trouble).
|
# Uses JuMP + HiGHS (open-source, ships with no license trouble).
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using Printf
|
using Printf
|
||||||
using LinearAlgebra
|
using LinearAlgebra
|
||||||
@ -31,10 +31,10 @@ using JSON
|
|||||||
using JuMP
|
using JuMP
|
||||||
using HiGHS
|
using HiGHS
|
||||||
|
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_linearize.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_linearize.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "load_predicates.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "load_predicates.jl"))
|
||||||
|
|
||||||
plant = pke_params()
|
plant = pke_params()
|
||||||
x_op = pke_initial_conditions(plant)
|
x_op = pke_initial_conditions(plant)
|
||||||
@ -55,7 +55,7 @@ b_inv2 = inv2.b_poly # 6
|
|||||||
|
|
||||||
# --- Precursor bounds from reach-tube envelope ---
|
# --- Precursor bounds from reach-tube envelope ---
|
||||||
# Read reach_operation_result.mat; take min/max of X_lo, X_hi on precursors.
|
# Read reach_operation_result.mat; take min/max of X_lo, X_hi on precursors.
|
||||||
reach_mat_path = joinpath(@__DIR__, "..", "..", "reachability",
|
reach_mat_path = joinpath(@__DIR__, "..", "..", "..", "reachability",
|
||||||
"reach_operation_result.mat")
|
"reach_operation_result.mat")
|
||||||
reach = matread(reach_mat_path)
|
reach = matread(reach_mat_path)
|
||||||
X_lo = reach["X_lo"]; X_hi = reach["X_hi"]
|
X_lo = reach["X_lo"]; X_hi = reach["X_hi"]
|
||||||
@ -24,7 +24,7 @@
|
|||||||
# Using SOS multipliers σ_i(x), w-dependence via lossless-disturbance bound.
|
# Using SOS multipliers σ_i(x), w-dependence via lossless-disturbance bound.
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using LinearAlgebra
|
using LinearAlgebra
|
||||||
using MatrixEquations
|
using MatrixEquations
|
||||||
@ -32,9 +32,9 @@ using DynamicPolynomials
|
|||||||
using SumOfSquares
|
using SumOfSquares
|
||||||
using CSDP
|
using CSDP
|
||||||
|
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_linearize.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_linearize.jl"))
|
||||||
|
|
||||||
plant = pke_params()
|
plant = pke_params()
|
||||||
x_op = pke_initial_conditions(plant)
|
x_op = pke_initial_conditions(plant)
|
||||||
@ -22,17 +22,17 @@
|
|||||||
# julia --project=. scripts/plot_reach_tubes.jl heatup_pj
|
# julia --project=. scripts/plot_reach_tubes.jl heatup_pj
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using MAT
|
using MAT
|
||||||
using Plots
|
using Plots
|
||||||
gr()
|
gr()
|
||||||
|
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
|
||||||
const PLANT = pke_params()
|
const PLANT = pke_params()
|
||||||
|
|
||||||
function plot_tubes_operation()
|
function plot_tubes_operation()
|
||||||
mat_path = joinpath(@__DIR__, "..", "..", "reachability", "reach_operation_result.mat")
|
mat_path = joinpath(@__DIR__, "..", "..", "..", "results", "reach_operation_result.mat")
|
||||||
d = matread(mat_path)
|
d = matread(mat_path)
|
||||||
|
|
||||||
T_vec = vec(d["T"]) # time grid
|
T_vec = vec(d["T"]) # time grid
|
||||||
@ -75,7 +75,7 @@ function plot_tubes_operation()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function plot_tubes_heatup_pj()
|
function plot_tubes_heatup_pj()
|
||||||
mat_path = joinpath(@__DIR__, "..", "..", "reachability",
|
mat_path = joinpath(@__DIR__, "..", "..", "..", "reachability",
|
||||||
"reach_heatup_pj_tight_full.mat")
|
"reach_heatup_pj_tight_full.mat")
|
||||||
d = matread(mat_path)
|
d = matread(mat_path)
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ function _plot_common(t, Tc_lo, Tc_hi, Th_lo, Th_hi, Tco_lo, Tco_hi,
|
|||||||
|
|
||||||
fig = plot(p1, p2, p3, p4, layout=(2, 2), size=(1300, 800),
|
fig = plot(p1, p2, p3, p4, layout=(2, 2), size=(1300, 800),
|
||||||
plot_title=title_stem)
|
plot_title=title_stem)
|
||||||
figdir = joinpath(@__DIR__, "..", "..", "docs", "figures")
|
figdir = joinpath(@__DIR__, "..", "..", "..", "docs", "figures")
|
||||||
isdir(figdir) || mkpath(figdir)
|
isdir(figdir) || mkpath(figdir)
|
||||||
outpath = joinpath(figdir, outname)
|
outpath = joinpath(figdir, outname)
|
||||||
savefig(fig, outpath)
|
savefig(fig, outpath)
|
||||||
@ -157,7 +157,7 @@ if which_plot in ("operation", "both")
|
|||||||
plot_tubes_operation()
|
plot_tubes_operation()
|
||||||
end
|
end
|
||||||
if which_plot in ("heatup_pj", "both")
|
if which_plot in ("heatup_pj", "both")
|
||||||
mat_path = joinpath(@__DIR__, "..", "..", "reachability",
|
mat_path = joinpath(@__DIR__, "..", "..", "..", "reachability",
|
||||||
"reach_heatup_pj_tight_full.mat")
|
"reach_heatup_pj_tight_full.mat")
|
||||||
if isfile(mat_path)
|
if isfile(mat_path)
|
||||||
plot_tubes_heatup_pj()
|
plot_tubes_heatup_pj()
|
||||||
@ -26,7 +26,7 @@
|
|||||||
# Time carried as augmented state x[11].
|
# Time carried as augmented state x[11].
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using LinearAlgebra
|
using LinearAlgebra
|
||||||
using ReachabilityAnalysis, LazySets
|
using ReachabilityAnalysis, LazySets
|
||||||
@ -101,7 +101,7 @@ const KP_HEATUP = 1e-4
|
|||||||
end
|
end
|
||||||
|
|
||||||
# --- Build X_entry from predicates.json ---
|
# --- Build X_entry from predicates.json ---
|
||||||
pred_path = joinpath(@__DIR__, "..", "..", "reachability", "predicates.json")
|
pred_path = joinpath(@__DIR__, "..", "..", "..", "reachability", "predicates.json")
|
||||||
pred_raw = JSON.parsefile(pred_path)
|
pred_raw = JSON.parsefile(pred_path)
|
||||||
entry = pred_raw["mode_boundaries"]["q_heatup"]["X_entry_polytope"]
|
entry = pred_raw["mode_boundaries"]["q_heatup"]["X_entry_polytope"]
|
||||||
|
|
||||||
269
code/scripts/reach/reach_heatup_pj.jl
Normal file
269
code/scripts/reach/reach_heatup_pj.jl
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
#!/usr/bin/env julia
|
||||||
|
#
|
||||||
|
# reach_heatup_pj.jl — nonlinear reach on heatup, prompt-jump model.
|
||||||
|
#
|
||||||
|
# Reduced from 10-state to 9-state (n is algebraic). Removes the Λ⁻¹
|
||||||
|
# stiffness that capped the full-state reach at ~10 s.
|
||||||
|
#
|
||||||
|
# State (10D with augmented time):
|
||||||
|
# x[1..6] = C_1..C_6 (delayed-neutron precursors)
|
||||||
|
# x[7] = T_f
|
||||||
|
# x[8] = T_c
|
||||||
|
# x[9] = T_cold
|
||||||
|
# x[10] = t (augmented time, dt/dt = 1)
|
||||||
|
#
|
||||||
|
# n is algebraic: n = Λ·Σ λ_i C_i / (β - ρ), ρ = K_p·(T_ref - T_c).
|
||||||
|
#
|
||||||
|
# Configuration-driven: pass a TOML config path as the first CLI arg,
|
||||||
|
# or omit for the baseline config.
|
||||||
|
#
|
||||||
|
# julia --project=. scripts/reach/reach_heatup_pj.jl # baseline
|
||||||
|
# julia --project=. scripts/reach/reach_heatup_pj.jl configs/heatup/tight.toml
|
||||||
|
#
|
||||||
|
# Configs live in code/configs/heatup/*.toml. See baseline.toml for
|
||||||
|
# the full schema.
|
||||||
|
|
||||||
|
using Pkg
|
||||||
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
|
using LinearAlgebra
|
||||||
|
using ReachabilityAnalysis, LazySets
|
||||||
|
using JSON
|
||||||
|
using MAT
|
||||||
|
using TOML
|
||||||
|
|
||||||
|
# --- Plant constants (must match pke_params) ---
|
||||||
|
const LAMBDA = 1e-4
|
||||||
|
const BETA_1, BETA_2, BETA_3, BETA_4, BETA_5, BETA_6 =
|
||||||
|
0.000215, 0.001424, 0.001274, 0.002568, 0.000748, 0.000273
|
||||||
|
const BETA = BETA_1 + BETA_2 + BETA_3 + BETA_4 + BETA_5 + BETA_6
|
||||||
|
const LAM_1, LAM_2, LAM_3, LAM_4, LAM_5, LAM_6 =
|
||||||
|
0.0124, 0.0305, 0.111, 0.301, 1.14, 3.01
|
||||||
|
|
||||||
|
const P0 = 1e9
|
||||||
|
const M_F, C_F, M_C, C_C, HA, W_M, M_SG =
|
||||||
|
50000.0, 300.0, 20000.0, 5450.0, 5e7, 5000.0, 30000.0
|
||||||
|
|
||||||
|
const T_COLD0 = 290.0
|
||||||
|
const DT_CORE = P0 / (W_M * C_C)
|
||||||
|
const T_HOT0 = T_COLD0 + DT_CORE
|
||||||
|
const T_C0 = (T_HOT0 + T_COLD0) / 2
|
||||||
|
const T_F0 = T_C0 + P0 / HA
|
||||||
|
|
||||||
|
const T_STANDBY = T_C0 - 33.333333
|
||||||
|
const RAMP_RATE_CS = 28.0 / 3600
|
||||||
|
const KP_HEATUP = 1e-4
|
||||||
|
|
||||||
|
# --- Taylorized heatup PJ RHS ---
|
||||||
|
@taylorize function rhs_heatup_pj_taylor!(dx, x, p, t)
|
||||||
|
rho = KP_HEATUP * (T_STANDBY + RAMP_RATE_CS * x[10] - x[8])
|
||||||
|
sum_lam_C = LAM_1*x[1] + LAM_2*x[2] + LAM_3*x[3] +
|
||||||
|
LAM_4*x[4] + LAM_5*x[5] + LAM_6*x[6]
|
||||||
|
denom = BETA - rho
|
||||||
|
n = LAMBDA * sum_lam_C / denom
|
||||||
|
inv_factor = sum_lam_C / denom
|
||||||
|
|
||||||
|
dx[1] = BETA_1 * inv_factor - LAM_1 * x[1]
|
||||||
|
dx[2] = BETA_2 * inv_factor - LAM_2 * x[2]
|
||||||
|
dx[3] = BETA_3 * inv_factor - LAM_3 * x[3]
|
||||||
|
dx[4] = BETA_4 * inv_factor - LAM_4 * x[4]
|
||||||
|
dx[5] = BETA_5 * inv_factor - LAM_5 * x[5]
|
||||||
|
dx[6] = BETA_6 * inv_factor - LAM_6 * x[6]
|
||||||
|
|
||||||
|
dx[7] = (P0 * n - HA * (x[7] - x[8])) / (M_F * C_F)
|
||||||
|
dx[8] = (HA * (x[7] - x[8]) - 2 * W_M * C_C * (x[8] - x[9])) / (M_C * C_C)
|
||||||
|
dx[9] = (2 * W_M * C_C * (x[8] - x[9])) / (M_SG * C_C)
|
||||||
|
|
||||||
|
dx[10] = one(x[1])
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Config loader ---
|
||||||
|
function load_config(config_path)
|
||||||
|
if isfile(config_path)
|
||||||
|
return TOML.parsefile(config_path)
|
||||||
|
else
|
||||||
|
error("Config file not found: $config_path")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function build_entry_box(config)
|
||||||
|
if get(config, "use_predicates_entry", false)
|
||||||
|
pred_path = joinpath(@__DIR__, "..", "..", "..", "reachability", "predicates.json")
|
||||||
|
pred_raw = JSON.parsefile(pred_path)
|
||||||
|
entry = pred_raw["mode_boundaries"]["q_heatup"]["X_entry_polytope"]
|
||||||
|
n_lo, n_hi = entry["n_range"]
|
||||||
|
T_f_lo, T_f_hi = entry["T_f_range_C"]
|
||||||
|
T_c_lo, T_c_hi = entry["T_c_range_C"]
|
||||||
|
T_cold_lo, T_cold_hi = entry["T_cold_range_C"]
|
||||||
|
else
|
||||||
|
e = config["entry"]
|
||||||
|
n_lo, n_hi = e["n_range"]
|
||||||
|
T_f_lo, T_f_hi = e["T_f_range_C"]
|
||||||
|
T_c_lo, T_c_hi = e["T_c_range_C"]
|
||||||
|
T_cold_lo, T_cold_hi = e["T_cold_range_C"]
|
||||||
|
end
|
||||||
|
|
||||||
|
n_mid = 0.5 * (n_lo + n_hi)
|
||||||
|
C_mid = [BETA_1/(LAM_1*LAMBDA), BETA_2/(LAM_2*LAMBDA),
|
||||||
|
BETA_3/(LAM_3*LAMBDA), BETA_4/(LAM_4*LAMBDA),
|
||||||
|
BETA_5/(LAM_5*LAMBDA), BETA_6/(LAM_6*LAMBDA)] .* n_mid
|
||||||
|
|
||||||
|
x_lo = [C_mid[1]*(n_lo/n_mid), C_mid[2]*(n_lo/n_mid),
|
||||||
|
C_mid[3]*(n_lo/n_mid), C_mid[4]*(n_lo/n_mid),
|
||||||
|
C_mid[5]*(n_lo/n_mid), C_mid[6]*(n_lo/n_mid),
|
||||||
|
T_f_lo, T_c_lo, T_cold_lo, 0.0]
|
||||||
|
x_hi = [C_mid[1]*(n_hi/n_mid), C_mid[2]*(n_hi/n_mid),
|
||||||
|
C_mid[3]*(n_hi/n_mid), C_mid[4]*(n_hi/n_mid),
|
||||||
|
C_mid[5]*(n_hi/n_mid), C_mid[6]*(n_hi/n_mid),
|
||||||
|
T_f_hi, T_c_hi, T_cold_hi, 0.0]
|
||||||
|
|
||||||
|
return Hyperrectangle(low=x_lo, high=x_hi),
|
||||||
|
(n_lo=n_lo, n_hi=n_hi,
|
||||||
|
T_f_lo=T_f_lo, T_f_hi=T_f_hi,
|
||||||
|
T_c_lo=T_c_lo, T_c_hi=T_c_hi,
|
||||||
|
T_cold_lo=T_cold_lo, T_cold_hi=T_cold_hi)
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Per-step envelope extraction ---
|
||||||
|
function extract_envelopes(flow_hr)
|
||||||
|
n_steps = length(flow_hr)
|
||||||
|
t_arr = zeros(n_steps)
|
||||||
|
Tc_lo_ts = zeros(n_steps); Tc_hi_ts = zeros(n_steps)
|
||||||
|
Tf_lo_ts = zeros(n_steps); Tf_hi_ts = zeros(n_steps)
|
||||||
|
Tco_lo_ts = zeros(n_steps); Tco_hi_ts = zeros(n_steps)
|
||||||
|
n_lo_ts = zeros(n_steps); n_hi_ts = zeros(n_steps)
|
||||||
|
rho_lo_ts = zeros(n_steps); rho_hi_ts = zeros(n_steps)
|
||||||
|
for (k, R) in enumerate(flow_hr)
|
||||||
|
s = set(R)
|
||||||
|
t_arr[k] = high(s, 10)
|
||||||
|
Tc_lo_ts[k] = low(s, 8); Tc_hi_ts[k] = high(s, 8)
|
||||||
|
Tf_lo_ts[k] = low(s, 7); Tf_hi_ts[k] = high(s, 7)
|
||||||
|
Tco_lo_ts[k] = low(s, 9); Tco_hi_ts[k] = high(s, 9)
|
||||||
|
sumLC_lo = LAM_1*low(s,1) + LAM_2*low(s,2) + LAM_3*low(s,3) +
|
||||||
|
LAM_4*low(s,4) + LAM_5*low(s,5) + LAM_6*low(s,6)
|
||||||
|
sumLC_hi = LAM_1*high(s,1) + LAM_2*high(s,2) + LAM_3*high(s,3) +
|
||||||
|
LAM_4*high(s,4) + LAM_5*high(s,5) + LAM_6*high(s,6)
|
||||||
|
t_hi_here = high(s, 10)
|
||||||
|
t_lo_here = low(s, 10)
|
||||||
|
Tref_lo = T_STANDBY + RAMP_RATE_CS * t_lo_here
|
||||||
|
Tref_hi = T_STANDBY + RAMP_RATE_CS * t_hi_here
|
||||||
|
rho_lo_here = KP_HEATUP * (Tref_lo - high(s, 8))
|
||||||
|
rho_hi_here = KP_HEATUP * (Tref_hi - low(s, 8))
|
||||||
|
rho_lo_ts[k] = rho_lo_here
|
||||||
|
rho_hi_ts[k] = rho_hi_here
|
||||||
|
denom_lo = BETA - rho_hi_here
|
||||||
|
denom_hi = BETA - rho_lo_here
|
||||||
|
if denom_lo > 0
|
||||||
|
n_lo_ts[k] = LAMBDA * sumLC_lo / denom_hi
|
||||||
|
n_hi_ts[k] = LAMBDA * sumLC_hi / denom_lo
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return (; t_arr,
|
||||||
|
Tc_lo_ts, Tc_hi_ts, Tf_lo_ts, Tf_hi_ts,
|
||||||
|
Tco_lo_ts, Tco_hi_ts, n_lo_ts, n_hi_ts,
|
||||||
|
rho_lo_ts, rho_hi_ts)
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Main ---
|
||||||
|
function main()
|
||||||
|
default_config = joinpath(@__DIR__, "..", "..", "configs", "heatup", "baseline.toml")
|
||||||
|
config_path = length(ARGS) > 0 ? ARGS[1] : default_config
|
||||||
|
# Allow a config path relative to repo root or code/.
|
||||||
|
if !isfile(config_path)
|
||||||
|
alt = joinpath(@__DIR__, "..", "..", config_path)
|
||||||
|
isfile(alt) && (config_path = alt)
|
||||||
|
end
|
||||||
|
config = load_config(config_path)
|
||||||
|
|
||||||
|
println("\n=== Heatup PJ reach — config: $(config["name"]) ===")
|
||||||
|
println(" $(get(config, "description", ""))")
|
||||||
|
|
||||||
|
X0, entry_info = build_entry_box(config)
|
||||||
|
println(" X_entry: n ∈ [$(entry_info.n_lo), $(entry_info.n_hi)], " *
|
||||||
|
"T_c ∈ [$(entry_info.T_c_lo), $(entry_info.T_c_hi)] °C")
|
||||||
|
|
||||||
|
tmjets_cfg = config["tmjets"]
|
||||||
|
probes = config["probes"]["horizons_seconds"]
|
||||||
|
|
||||||
|
results = Dict{Float64, Any}()
|
||||||
|
|
||||||
|
for T_probe in probes
|
||||||
|
println("\n--- Probe T = $T_probe s ($(round(T_probe/60; digits=1)) min) ---")
|
||||||
|
sys = BlackBoxContinuousSystem(rhs_heatup_pj_taylor!, 10)
|
||||||
|
prob = InitialValueProblem(sys, X0)
|
||||||
|
try
|
||||||
|
alg = TMJets(
|
||||||
|
orderT = tmjets_cfg["orderT"],
|
||||||
|
orderQ = tmjets_cfg["orderQ"],
|
||||||
|
abstol = tmjets_cfg["abstol"],
|
||||||
|
maxsteps = tmjets_cfg["maxsteps"],
|
||||||
|
)
|
||||||
|
t_start = time()
|
||||||
|
sol = solve(prob; T=Float64(T_probe), alg=alg)
|
||||||
|
elapsed = time() - t_start
|
||||||
|
flow = flowpipe(sol)
|
||||||
|
n_sets = length(flow)
|
||||||
|
println(" TMJets: $n_sets reach-sets, wall $(round(elapsed; digits=1)) s")
|
||||||
|
|
||||||
|
flow_hr = overapproximate(flow, Hyperrectangle)
|
||||||
|
env = extract_envelopes(flow_hr)
|
||||||
|
|
||||||
|
println(" n envelope: [$(round(minimum(env.n_lo_ts); sigdigits=4)), $(round(maximum(env.n_hi_ts); sigdigits=4))]")
|
||||||
|
println(" T_c envelope: [$(round(minimum(env.Tc_lo_ts); digits=2)), $(round(maximum(env.Tc_hi_ts); digits=2))] °C")
|
||||||
|
println(" T_f envelope: [$(round(minimum(env.Tf_lo_ts); digits=2)), $(round(maximum(env.Tf_hi_ts); digits=2))] °C")
|
||||||
|
println(" T_cold env: [$(round(minimum(env.Tco_lo_ts); digits=2)), $(round(maximum(env.Tco_hi_ts); digits=2))] °C")
|
||||||
|
println(" rho env: [$(round(minimum(env.rho_lo_ts); sigdigits=4)), $(round(maximum(env.rho_hi_ts); sigdigits=4))]")
|
||||||
|
|
||||||
|
results[T_probe] = (status="OK", n_sets=n_sets, elapsed=elapsed, env=env)
|
||||||
|
catch err
|
||||||
|
msg = sprint(showerror, err)
|
||||||
|
println(" FAILED: ", first(msg, 300))
|
||||||
|
results[T_probe] = (status="FAILED", err=first(msg, 300))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
println("\n=== Summary ===")
|
||||||
|
for T_probe in probes
|
||||||
|
haskey(results, T_probe) || continue
|
||||||
|
r = results[T_probe]
|
||||||
|
if r.status == "OK"
|
||||||
|
println(" T = $(T_probe) s: OK, $(r.n_sets) sets, $(round(r.elapsed; digits=1))s wall")
|
||||||
|
else
|
||||||
|
println(" T = $(T_probe) s: FAILED")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Save ---
|
||||||
|
if get(config, "output", Dict()) |> (o -> get(o, "save_per_step", false))
|
||||||
|
result_file = config["output"]["result_file"]
|
||||||
|
mat_out = joinpath(@__DIR__, "..", "..", "..", "results", result_file)
|
||||||
|
saved = Dict{String, Any}(
|
||||||
|
"config_name" => config["name"],
|
||||||
|
"probe_horizons" => collect(probes),
|
||||||
|
"beta" => BETA,
|
||||||
|
"Kp" => KP_HEATUP,
|
||||||
|
"T_c0" => T_C0,
|
||||||
|
"T_cold0" => T_COLD0,
|
||||||
|
"T_standby" => T_STANDBY,
|
||||||
|
)
|
||||||
|
for T_probe in probes
|
||||||
|
haskey(results, T_probe) || continue
|
||||||
|
r = results[T_probe]
|
||||||
|
r.status == "OK" || continue
|
||||||
|
pre = "T_$(Int(T_probe))_"
|
||||||
|
env = r.env
|
||||||
|
saved[pre * "t_arr"] = env.t_arr
|
||||||
|
saved[pre * "Tc_lo_ts"] = env.Tc_lo_ts; saved[pre * "Tc_hi_ts"] = env.Tc_hi_ts
|
||||||
|
saved[pre * "Tf_lo_ts"] = env.Tf_lo_ts; saved[pre * "Tf_hi_ts"] = env.Tf_hi_ts
|
||||||
|
saved[pre * "Tco_lo_ts"] = env.Tco_lo_ts; saved[pre * "Tco_hi_ts"] = env.Tco_hi_ts
|
||||||
|
saved[pre * "n_lo_ts"] = env.n_lo_ts; saved[pre * "n_hi_ts"] = env.n_hi_ts
|
||||||
|
saved[pre * "rho_lo_ts"] = env.rho_lo_ts; saved[pre * "rho_hi_ts"] = env.rho_hi_ts
|
||||||
|
end
|
||||||
|
matwrite(mat_out, saved)
|
||||||
|
println("\nSaved per-step envelopes to $mat_out")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
main()
|
||||||
178
code/scripts/reach/reach_heatup_pj_sd.jl
Normal file
178
code/scripts/reach/reach_heatup_pj_sd.jl
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
#!/usr/bin/env julia
|
||||||
|
#
|
||||||
|
# reach_heatup_pj_sd.jl — heatup PJ reach WITH bounded Q_sg steam dump.
|
||||||
|
#
|
||||||
|
# Adds an augmented state x[11] = Q_sg as a bounded parameter (dx[11] = 0).
|
||||||
|
# The reach propagates the entry-box range of Q_sg forward; at each reach
|
||||||
|
# set, the Q_sg extent is the disturbance envelope the controller has to
|
||||||
|
# reject. Physical story: operator controls secondary steam dump; actual
|
||||||
|
# value unknown but bounded in [0, 0.05·P_0].
|
||||||
|
#
|
||||||
|
# Time is carried as x[12] = t (dt/dt = 1).
|
||||||
|
#
|
||||||
|
# Diagnostic script — uses the tight-entry + steam-dump config.
|
||||||
|
|
||||||
|
using Pkg
|
||||||
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
|
using LinearAlgebra
|
||||||
|
using ReachabilityAnalysis, LazySets
|
||||||
|
using JSON
|
||||||
|
using MAT
|
||||||
|
using TOML
|
||||||
|
|
||||||
|
# Plant constants.
|
||||||
|
const LAMBDA = 1e-4
|
||||||
|
const BETA_1, BETA_2, BETA_3, BETA_4, BETA_5, BETA_6 =
|
||||||
|
0.000215, 0.001424, 0.001274, 0.002568, 0.000748, 0.000273
|
||||||
|
const BETA = BETA_1 + BETA_2 + BETA_3 + BETA_4 + BETA_5 + BETA_6
|
||||||
|
const LAM_1, LAM_2, LAM_3, LAM_4, LAM_5, LAM_6 =
|
||||||
|
0.0124, 0.0305, 0.111, 0.301, 1.14, 3.01
|
||||||
|
|
||||||
|
const P0 = 1e9
|
||||||
|
const M_F, C_F, M_C, C_C, HA, W_M, M_SG =
|
||||||
|
50000.0, 300.0, 20000.0, 5450.0, 5e7, 5000.0, 30000.0
|
||||||
|
|
||||||
|
const T_COLD0 = 290.0
|
||||||
|
const DT_CORE = P0 / (W_M * C_C)
|
||||||
|
const T_HOT0 = T_COLD0 + DT_CORE
|
||||||
|
const T_C0 = (T_HOT0 + T_COLD0) / 2
|
||||||
|
const T_F0 = T_C0 + P0 / HA
|
||||||
|
|
||||||
|
const T_STANDBY = T_C0 - 33.333333
|
||||||
|
const RAMP_RATE_CS = 28.0 / 3600
|
||||||
|
const KP_HEATUP = 1e-4
|
||||||
|
|
||||||
|
# 12-state RHS: [C_1..C_6, T_f, T_c, T_cold, Q_sg, t]
|
||||||
|
# dx[10] = 0 (Q_sg is a bounded parameter)
|
||||||
|
# dx[11] = 1 (time)
|
||||||
|
# Hmm — indexing conflict with original version. Reorganize:
|
||||||
|
# x[1..6] = C_1..C_6
|
||||||
|
# x[7] = T_f
|
||||||
|
# x[8] = T_c
|
||||||
|
# x[9] = T_cold
|
||||||
|
# x[10] = Q_sg (constant-parameter, dx[10]=0)
|
||||||
|
# x[11] = t (dx[11]=1)
|
||||||
|
@taylorize function rhs_heatup_sd_taylor!(dx, x, p, t)
|
||||||
|
rho = KP_HEATUP * (T_STANDBY + RAMP_RATE_CS * x[11] - x[8])
|
||||||
|
sum_lam_C = LAM_1*x[1] + LAM_2*x[2] + LAM_3*x[3] +
|
||||||
|
LAM_4*x[4] + LAM_5*x[5] + LAM_6*x[6]
|
||||||
|
denom = BETA - rho
|
||||||
|
n = LAMBDA * sum_lam_C / denom
|
||||||
|
inv_factor = sum_lam_C / denom
|
||||||
|
|
||||||
|
dx[1] = BETA_1 * inv_factor - LAM_1 * x[1]
|
||||||
|
dx[2] = BETA_2 * inv_factor - LAM_2 * x[2]
|
||||||
|
dx[3] = BETA_3 * inv_factor - LAM_3 * x[3]
|
||||||
|
dx[4] = BETA_4 * inv_factor - LAM_4 * x[4]
|
||||||
|
dx[5] = BETA_5 * inv_factor - LAM_5 * x[5]
|
||||||
|
dx[6] = BETA_6 * inv_factor - LAM_6 * x[6]
|
||||||
|
|
||||||
|
dx[7] = (P0 * n - HA * (x[7] - x[8])) / (M_F * C_F)
|
||||||
|
dx[8] = (HA * (x[7] - x[8]) - 2 * W_M * C_C * (x[8] - x[9])) / (M_C * C_C)
|
||||||
|
dx[9] = (2 * W_M * C_C * (x[8] - x[9]) - x[10]) / (M_SG * C_C) # Q_sg now enters!
|
||||||
|
|
||||||
|
dx[10] = zero(x[1]) # Q_sg constant
|
||||||
|
dx[11] = one(x[1]) # time
|
||||||
|
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
function main()
|
||||||
|
config_path = length(ARGS) > 0 ? ARGS[1] :
|
||||||
|
joinpath(@__DIR__, "..", "..", "configs", "heatup", "with_steam_dump.toml")
|
||||||
|
if !isfile(config_path)
|
||||||
|
alt = joinpath(@__DIR__, "..", "..", config_path)
|
||||||
|
isfile(alt) && (config_path = alt)
|
||||||
|
end
|
||||||
|
config = TOML.parsefile(config_path)
|
||||||
|
|
||||||
|
println("\n=== Heatup PJ reach with steam dump — config: $(config["name"]) ===")
|
||||||
|
println(" $(get(config, "description", ""))")
|
||||||
|
|
||||||
|
e = config["entry"]
|
||||||
|
n_lo, n_hi = e["n_range"]
|
||||||
|
T_f_lo, T_f_hi = e["T_f_range_C"]
|
||||||
|
T_c_lo, T_c_hi = e["T_c_range_C"]
|
||||||
|
T_cold_lo, T_cold_hi = e["T_cold_range_C"]
|
||||||
|
sd = e["steam_dump"]
|
||||||
|
Q_lo = sd["Q_lo_fraction_P0"] * P0
|
||||||
|
Q_hi = sd["Q_hi_fraction_P0"] * P0
|
||||||
|
|
||||||
|
n_mid = 0.5 * (n_lo + n_hi)
|
||||||
|
C_mid = [BETA_1/(LAM_1*LAMBDA), BETA_2/(LAM_2*LAMBDA),
|
||||||
|
BETA_3/(LAM_3*LAMBDA), BETA_4/(LAM_4*LAMBDA),
|
||||||
|
BETA_5/(LAM_5*LAMBDA), BETA_6/(LAM_6*LAMBDA)] .* n_mid
|
||||||
|
|
||||||
|
x_lo = [C_mid[1]*(n_lo/n_mid), C_mid[2]*(n_lo/n_mid),
|
||||||
|
C_mid[3]*(n_lo/n_mid), C_mid[4]*(n_lo/n_mid),
|
||||||
|
C_mid[5]*(n_lo/n_mid), C_mid[6]*(n_lo/n_mid),
|
||||||
|
T_f_lo, T_c_lo, T_cold_lo, Q_lo, 0.0]
|
||||||
|
x_hi = [C_mid[1]*(n_hi/n_mid), C_mid[2]*(n_hi/n_mid),
|
||||||
|
C_mid[3]*(n_hi/n_mid), C_mid[4]*(n_hi/n_mid),
|
||||||
|
C_mid[5]*(n_hi/n_mid), C_mid[6]*(n_hi/n_mid),
|
||||||
|
T_f_hi, T_c_hi, T_cold_hi, Q_hi, 0.0]
|
||||||
|
|
||||||
|
X0 = Hyperrectangle(low=x_lo, high=x_hi)
|
||||||
|
println(" n ∈ [$n_lo, $n_hi], T_c ∈ [$T_c_lo, $T_c_hi] °C")
|
||||||
|
println(" Q_sg (steam dump) ∈ [$(Q_lo/1e6), $(Q_hi/1e6)] MW")
|
||||||
|
|
||||||
|
t_cfg = config["tmjets"]
|
||||||
|
probes = config["probes"]["horizons_seconds"]
|
||||||
|
|
||||||
|
results = Dict{Float64, Any}()
|
||||||
|
for T_probe in probes
|
||||||
|
println("\n--- Probe T = $T_probe s ---")
|
||||||
|
sys = BlackBoxContinuousSystem(rhs_heatup_sd_taylor!, 11)
|
||||||
|
prob = InitialValueProblem(sys, X0)
|
||||||
|
try
|
||||||
|
alg = TMJets(orderT=t_cfg["orderT"], orderQ=t_cfg["orderQ"],
|
||||||
|
abstol=t_cfg["abstol"], maxsteps=t_cfg["maxsteps"])
|
||||||
|
t0 = time()
|
||||||
|
sol = solve(prob; T=Float64(T_probe), alg=alg)
|
||||||
|
elapsed = time() - t0
|
||||||
|
flow_hr = overapproximate(flowpipe(sol), Hyperrectangle)
|
||||||
|
n_sets = length(flow_hr)
|
||||||
|
println(" TMJets: $n_sets reach-sets in $(round(elapsed; digits=1)) s")
|
||||||
|
|
||||||
|
Tc_lo_env = minimum(low(set(R), 8) for R in flow_hr)
|
||||||
|
Tc_hi_env = maximum(high(set(R), 8) for R in flow_hr)
|
||||||
|
Tco_lo_env = minimum(low(set(R), 9) for R in flow_hr)
|
||||||
|
Tco_hi_env = maximum(high(set(R), 9) for R in flow_hr)
|
||||||
|
Tf_lo_env = minimum(low(set(R), 7) for R in flow_hr)
|
||||||
|
Tf_hi_env = maximum(high(set(R), 7) for R in flow_hr)
|
||||||
|
println(" T_c envelope: [$(round(Tc_lo_env; digits=2)), $(round(Tc_hi_env; digits=2))] °C")
|
||||||
|
println(" T_f envelope: [$(round(Tf_lo_env; digits=2)), $(round(Tf_hi_env; digits=2))] °C")
|
||||||
|
println(" T_cold env: [$(round(Tco_lo_env; digits=2)), $(round(Tco_hi_env; digits=2))] °C")
|
||||||
|
|
||||||
|
# Compare low-T_avg trip (280 °C).
|
||||||
|
low_trip_ok = Tc_lo_env >= 280.0
|
||||||
|
println(" Low-T_avg trip (T_c ≥ 280): $(low_trip_ok ? "✅" : "× loose")")
|
||||||
|
|
||||||
|
results[T_probe] = (status="OK", Tc=(Tc_lo_env, Tc_hi_env),
|
||||||
|
Tf=(Tf_lo_env, Tf_hi_env),
|
||||||
|
Tcold=(Tco_lo_env, Tco_hi_env),
|
||||||
|
low_trip_ok=low_trip_ok)
|
||||||
|
catch err
|
||||||
|
println(" FAILED: ", first(sprint(showerror, err), 300))
|
||||||
|
results[T_probe] = (status="FAILED",)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
mat_out = joinpath(@__DIR__, "..", "..", "..", "results", config["output"]["result_file"])
|
||||||
|
saved = Dict{String, Any}("config_name" => config["name"],
|
||||||
|
"Q_lo" => Q_lo, "Q_hi" => Q_hi)
|
||||||
|
for (T_probe, r) in results
|
||||||
|
if r.status == "OK"
|
||||||
|
pre = "T_$(Int(T_probe))_"
|
||||||
|
saved[pre * "Tc_lo"] = r.Tc[1]; saved[pre * "Tc_hi"] = r.Tc[2]
|
||||||
|
saved[pre * "Tf_lo"] = r.Tf[1]; saved[pre * "Tf_hi"] = r.Tf[2]
|
||||||
|
saved[pre * "Tcold_lo"] = r.Tcold[1]; saved[pre * "Tcold_hi"] = r.Tcold[2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
matwrite(mat_out, saved)
|
||||||
|
println("\nSaved to $mat_out")
|
||||||
|
end
|
||||||
|
|
||||||
|
main()
|
||||||
@ -11,7 +11,7 @@
|
|||||||
# Linear-reach results here are an approximate-but-useful gut check.
|
# Linear-reach results here are an approximate-but-useful gut check.
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using Printf
|
using Printf
|
||||||
using LinearAlgebra
|
using LinearAlgebra
|
||||||
@ -20,11 +20,11 @@ using Plots
|
|||||||
using JSON
|
using JSON
|
||||||
using MAT
|
using MAT
|
||||||
|
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_linearize.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_linearize.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "load_predicates.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "load_predicates.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "reach_linear.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "reach_linear.jl"))
|
||||||
|
|
||||||
plant = pke_params()
|
plant = pke_params()
|
||||||
x_op = pke_initial_conditions(plant)
|
x_op = pke_initial_conditions(plant)
|
||||||
@ -101,7 +101,7 @@ end
|
|||||||
# --- Plots: T_c reach tube, two views ---
|
# --- Plots: T_c reach tube, two views ---
|
||||||
CtoF(T) = T * 9/5 + 32
|
CtoF(T) = T * 9/5 + 32
|
||||||
delta_safe_Tc = pred.constants.t_avg_in_range_halfwidth_C
|
delta_safe_Tc = pred.constants.t_avg_in_range_halfwidth_C
|
||||||
figdir = joinpath(@__DIR__, "..", "..", "docs", "figures")
|
figdir = joinpath(@__DIR__, "..", "..", "..", "docs", "figures")
|
||||||
isdir(figdir) || mkpath(figdir)
|
isdir(figdir) || mkpath(figdir)
|
||||||
|
|
||||||
p_safety = plot(T, CtoF.(X_nom[9, :]), lw=1.2, color=:red,
|
p_safety = plot(T, CtoF.(X_nom[9, :]), lw=1.2, color=:red,
|
||||||
@ -140,7 +140,7 @@ plot!(fig_n, T, X_hi[1, :], fillrange=X_lo[1, :], fillalpha=0.3,
|
|||||||
savefig(fig_n, joinpath(figdir, "reach_operation_n.png"))
|
savefig(fig_n, joinpath(figdir, "reach_operation_n.png"))
|
||||||
|
|
||||||
# --- Save result ---
|
# --- Save result ---
|
||||||
matfile = joinpath(@__DIR__, "..", "..", "reachability", "reach_operation_result.mat")
|
matfile = joinpath(@__DIR__, "..", "..", "..", "results", "reach_operation_result.mat")
|
||||||
matwrite(matfile, Dict("T" => collect(T), "R_lo" => R_lo, "R_hi" => R_hi,
|
matwrite(matfile, Dict("T" => collect(T), "R_lo" => R_lo, "R_hi" => R_hi,
|
||||||
"center" => Cnom, "X_lo" => X_lo, "X_hi" => X_hi,
|
"center" => Cnom, "X_lo" => X_lo, "X_hi" => X_hi,
|
||||||
"X_nom" => X_nom, "K" => Matrix(K), "A_cl" => A_cl,
|
"X_nom" => X_nom, "K" => Matrix(K), "A_cl" => A_cl,
|
||||||
@ -9,7 +9,7 @@
|
|||||||
# 9-state PJ model (10D with augmented time).
|
# 9-state PJ model (10D with augmented time).
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using LinearAlgebra
|
using LinearAlgebra
|
||||||
using ReachabilityAnalysis, LazySets
|
using ReachabilityAnalysis, LazySets
|
||||||
@ -140,7 +140,7 @@ for T_probe in (10.0, 30.0, 60.0)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
mat_out = joinpath(@__DIR__, "..", "..", "reachability", "reach_scram_pj_result.mat")
|
mat_out = joinpath(@__DIR__, "..", "..", "..", "results", "reach_scram_pj_result.mat")
|
||||||
saved = Dict{String, Any}("probe_horizons" => collect((10.0, 30.0, 60.0)))
|
saved = Dict{String, Any}("probe_horizons" => collect((10.0, 30.0, 60.0)))
|
||||||
for T_probe in (10.0, 30.0, 60.0)
|
for T_probe in (10.0, 30.0, 60.0)
|
||||||
haskey(results, T_probe) || continue
|
haskey(results, T_probe) || continue
|
||||||
@ -1,243 +0,0 @@
|
|||||||
#!/usr/bin/env julia
|
|
||||||
#
|
|
||||||
# reach_heatup_pj.jl — nonlinear reach on heatup, prompt-jump model.
|
|
||||||
#
|
|
||||||
# Reduced from 10-state to 9-state (n is algebraic). Removes the
|
|
||||||
# Λ⁻¹ stiffness that capped the full-state reach at ~10 s. We push
|
|
||||||
# horizons up: 60 s, 300 s, 1800 s, 5400 s, full T_max = 18000 s (5 hr).
|
|
||||||
#
|
|
||||||
# State (10D with augmented time):
|
|
||||||
# x[1..6] = C_1..C_6 (delayed-neutron precursors)
|
|
||||||
# x[7] = T_f
|
|
||||||
# x[8] = T_c
|
|
||||||
# x[9] = T_cold
|
|
||||||
# x[10] = t (augmented time, dt/dt = 1)
|
|
||||||
#
|
|
||||||
# n is algebraic: n = Λ · Σ λ_i C_i / (β - ρ), ρ = K_p · (T_ref - T_c).
|
|
||||||
|
|
||||||
using Pkg
|
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
|
||||||
|
|
||||||
using LinearAlgebra
|
|
||||||
using ReachabilityAnalysis, LazySets
|
|
||||||
using JSON
|
|
||||||
using MAT
|
|
||||||
|
|
||||||
# --- Inlined plant constants (must match pke_params) ---
|
|
||||||
const LAMBDA = 1e-4
|
|
||||||
const BETA_1, BETA_2, BETA_3, BETA_4, BETA_5, BETA_6 =
|
|
||||||
0.000215, 0.001424, 0.001274, 0.002568, 0.000748, 0.000273
|
|
||||||
const BETA = BETA_1 + BETA_2 + BETA_3 + BETA_4 + BETA_5 + BETA_6
|
|
||||||
const LAM_1, LAM_2, LAM_3, LAM_4, LAM_5, LAM_6 =
|
|
||||||
0.0124, 0.0305, 0.111, 0.301, 1.14, 3.01
|
|
||||||
|
|
||||||
const P0 = 1e9
|
|
||||||
const M_F, C_F, M_C, C_C, HA, W_M, M_SG =
|
|
||||||
50000.0, 300.0, 20000.0, 5450.0, 5e7, 5000.0, 30000.0
|
|
||||||
|
|
||||||
# Note: feedback-linearization in ctrl_heatup_unsat cancels the alpha
|
|
||||||
# terms exactly, so under closed-loop the effective rho is just Kp·e.
|
|
||||||
# We don't need ALPHA_F, ALPHA_C in the reach RHS as a result.
|
|
||||||
|
|
||||||
const T_COLD0 = 290.0
|
|
||||||
const DT_CORE = P0 / (W_M * C_C)
|
|
||||||
const T_HOT0 = T_COLD0 + DT_CORE
|
|
||||||
const T_C0 = (T_HOT0 + T_COLD0) / 2
|
|
||||||
const T_F0 = T_C0 + P0 / HA
|
|
||||||
|
|
||||||
const T_STANDBY = T_C0 - 33.333333
|
|
||||||
const RAMP_RATE_CS = 28.0 / 3600
|
|
||||||
const KP_HEATUP = 1e-4
|
|
||||||
|
|
||||||
# --- Taylorized PJ heatup RHS, 10D with augmented time ---
|
|
||||||
@taylorize function rhs_heatup_pj_taylor!(dx, x, p, t)
|
|
||||||
# rho_total under closed-loop feedback linearization = Kp · (T_ref - T_c).
|
|
||||||
rho = KP_HEATUP * (T_STANDBY + RAMP_RATE_CS * x[10] - x[8])
|
|
||||||
|
|
||||||
# Algebraic prompt-jump n.
|
|
||||||
sum_lam_C = LAM_1*x[1] + LAM_2*x[2] + LAM_3*x[3] +
|
|
||||||
LAM_4*x[4] + LAM_5*x[5] + LAM_6*x[6]
|
|
||||||
denom = BETA - rho
|
|
||||||
n = LAMBDA * sum_lam_C / denom
|
|
||||||
inv_factor = sum_lam_C / denom
|
|
||||||
|
|
||||||
# Precursor balance under PJ.
|
|
||||||
dx[1] = BETA_1 * inv_factor - LAM_1 * x[1]
|
|
||||||
dx[2] = BETA_2 * inv_factor - LAM_2 * x[2]
|
|
||||||
dx[3] = BETA_3 * inv_factor - LAM_3 * x[3]
|
|
||||||
dx[4] = BETA_4 * inv_factor - LAM_4 * x[4]
|
|
||||||
dx[5] = BETA_5 * inv_factor - LAM_5 * x[5]
|
|
||||||
dx[6] = BETA_6 * inv_factor - LAM_6 * x[6]
|
|
||||||
|
|
||||||
# Thermal — n appears algebraically in fuel eq.
|
|
||||||
dx[7] = (P0 * n - HA * (x[7] - x[8])) / (M_F * C_F)
|
|
||||||
dx[8] = (HA * (x[7] - x[8]) - 2 * W_M * C_C * (x[8] - x[9])) / (M_C * C_C)
|
|
||||||
dx[9] = (2 * W_M * C_C * (x[8] - x[9])) / (M_SG * C_C)
|
|
||||||
|
|
||||||
# Augmented time.
|
|
||||||
dx[10] = one(x[1])
|
|
||||||
|
|
||||||
return nothing
|
|
||||||
end
|
|
||||||
|
|
||||||
# --- Build X_entry (PJ form: no n) from predicates.json ---
|
|
||||||
pred_path = joinpath(@__DIR__, "..", "..", "reachability", "predicates.json")
|
|
||||||
pred_raw = JSON.parsefile(pred_path)
|
|
||||||
entry = pred_raw["mode_boundaries"]["q_heatup"]["X_entry_polytope"]
|
|
||||||
|
|
||||||
n_lo, n_hi = entry["n_range"]
|
|
||||||
T_f_lo, T_f_hi = entry["T_f_range_C"]
|
|
||||||
T_c_lo, T_c_hi = entry["T_c_range_C"]
|
|
||||||
T_cold_lo, T_cold_hi = entry["T_cold_range_C"]
|
|
||||||
|
|
||||||
n_mid = 0.5 * (n_lo + n_hi)
|
|
||||||
C_mid_1 = (BETA_1 / (LAM_1 * LAMBDA)) * n_mid
|
|
||||||
C_mid_2 = (BETA_2 / (LAM_2 * LAMBDA)) * n_mid
|
|
||||||
C_mid_3 = (BETA_3 / (LAM_3 * LAMBDA)) * n_mid
|
|
||||||
C_mid_4 = (BETA_4 / (LAM_4 * LAMBDA)) * n_mid
|
|
||||||
C_mid_5 = (BETA_5 / (LAM_5 * LAMBDA)) * n_mid
|
|
||||||
C_mid_6 = (BETA_6 / (LAM_6 * LAMBDA)) * n_mid
|
|
||||||
|
|
||||||
# C_i scale linearly with n; sweep across the n_lo..n_hi band.
|
|
||||||
x_lo = [C_mid_1 * (n_lo / n_mid); C_mid_2 * (n_lo / n_mid);
|
|
||||||
C_mid_3 * (n_lo / n_mid); C_mid_4 * (n_lo / n_mid);
|
|
||||||
C_mid_5 * (n_lo / n_mid); C_mid_6 * (n_lo / n_mid);
|
|
||||||
T_f_lo; T_c_lo; T_cold_lo;
|
|
||||||
0.0]
|
|
||||||
x_hi = [C_mid_1 * (n_hi / n_mid); C_mid_2 * (n_hi / n_mid);
|
|
||||||
C_mid_3 * (n_hi / n_mid); C_mid_4 * (n_hi / n_mid);
|
|
||||||
C_mid_5 * (n_hi / n_mid); C_mid_6 * (n_hi / n_mid);
|
|
||||||
T_f_hi; T_c_hi; T_cold_hi;
|
|
||||||
0.0]
|
|
||||||
|
|
||||||
X0 = Hyperrectangle(low=x_lo, high=x_hi)
|
|
||||||
|
|
||||||
println("\n=== Nonlinear heatup reach, prompt-jump model ===")
|
|
||||||
println(" X_entry (n-implied range): n ∈ [$(n_lo), $(n_hi)]")
|
|
||||||
println(" T_c ∈ [$(T_c_lo), $(T_c_hi)] °C")
|
|
||||||
|
|
||||||
T_RAMP_END = (T_C0 - T_STANDBY) / RAMP_RATE_CS
|
|
||||||
println(" T_ramp_end = $(round(T_RAMP_END; digits=0)) s ($(round(T_RAMP_END/60; digits=1)) min)")
|
|
||||||
println(" Probing horizons up to T_max(heatup) = 18000 s (5 hr).")
|
|
||||||
|
|
||||||
# Probe at increasing horizons. Stop early if any probe fails.
|
|
||||||
probe_horizons = (60.0, 300.0, 1800.0, 5400.0)
|
|
||||||
|
|
||||||
results = Dict{Float64, Any}()
|
|
||||||
for T_probe in probe_horizons
|
|
||||||
println("\n--- Probe T = $T_probe s ($(round(T_probe/60; digits=1)) min) ---")
|
|
||||||
sys = BlackBoxContinuousSystem(rhs_heatup_pj_taylor!, 10)
|
|
||||||
prob = InitialValueProblem(sys, X0)
|
|
||||||
try
|
|
||||||
alg = TMJets(orderT=4, orderQ=2, abstol=1e-9, maxsteps=100000)
|
|
||||||
t_start = time()
|
|
||||||
sol = solve(prob; T=T_probe, alg=alg)
|
|
||||||
elapsed = time() - t_start
|
|
||||||
flow = flowpipe(sol)
|
|
||||||
n_sets = length(flow)
|
|
||||||
println(" TMJets: $n_sets reach-sets, wall-time $(round(elapsed; digits=1)) s")
|
|
||||||
|
|
||||||
flow_hr = overapproximate(flow, Hyperrectangle)
|
|
||||||
n_steps = length(flow_hr)
|
|
||||||
# Per-timestep envelopes for plotting (saved below).
|
|
||||||
t_arr = zeros(n_steps)
|
|
||||||
Tc_lo_ts = zeros(n_steps); Tc_hi_ts = zeros(n_steps)
|
|
||||||
Tf_lo_ts = zeros(n_steps); Tf_hi_ts = zeros(n_steps)
|
|
||||||
Tco_lo_ts = zeros(n_steps); Tco_hi_ts = zeros(n_steps)
|
|
||||||
n_lo_ts = zeros(n_steps); n_hi_ts = zeros(n_steps)
|
|
||||||
rho_lo_ts = zeros(n_steps); rho_hi_ts = zeros(n_steps)
|
|
||||||
for (k, R) in enumerate(flow_hr)
|
|
||||||
s = set(R)
|
|
||||||
t_arr[k] = high(s, 10) # time-state upper bound ≈ current time
|
|
||||||
Tc_lo_ts[k] = low(s, 8); Tc_hi_ts[k] = high(s, 8)
|
|
||||||
Tf_lo_ts[k] = low(s, 7); Tf_hi_ts[k] = high(s, 7)
|
|
||||||
Tco_lo_ts[k] = low(s, 9); Tco_hi_ts[k] = high(s, 9)
|
|
||||||
# Algebraic n reconstruction at this reach-set.
|
|
||||||
sumLC_lo = LAM_1*low(s,1) + LAM_2*low(s,2) + LAM_3*low(s,3) +
|
|
||||||
LAM_4*low(s,4) + LAM_5*low(s,5) + LAM_6*low(s,6)
|
|
||||||
sumLC_hi = LAM_1*high(s,1) + LAM_2*high(s,2) + LAM_3*high(s,3) +
|
|
||||||
LAM_4*high(s,4) + LAM_5*high(s,5) + LAM_6*high(s,6)
|
|
||||||
rho_lo_here = KP_HEATUP * (T_STANDBY - high(s, 8))
|
|
||||||
rho_hi_here = KP_HEATUP * (T_C0 - low(s, 8))
|
|
||||||
rho_lo_ts[k] = rho_lo_here
|
|
||||||
rho_hi_ts[k] = rho_hi_here
|
|
||||||
denom_lo = BETA - rho_hi_here
|
|
||||||
denom_hi = BETA - rho_lo_here
|
|
||||||
if denom_lo > 0
|
|
||||||
n_lo_ts[k] = LAMBDA * sumLC_lo / denom_hi
|
|
||||||
n_hi_ts[k] = LAMBDA * sumLC_hi / denom_lo
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# Also global-envelope values (for backward compat).
|
|
||||||
Tc_lo_env = minimum(Tc_lo_ts); Tc_hi_env = maximum(Tc_hi_ts)
|
|
||||||
Tf_lo_env = minimum(Tf_lo_ts); Tf_hi_env = maximum(Tf_hi_ts)
|
|
||||||
Tcold_lo = minimum(Tco_lo_ts); Tcold_hi = maximum(Tco_hi_ts)
|
|
||||||
n_env_lo = minimum(n_lo_ts); n_env_hi = maximum(n_hi_ts)
|
|
||||||
|
|
||||||
println(" n envelope (reconstructed): [$(round(n_env_lo; sigdigits=4)), $(round(n_env_hi; sigdigits=4))]")
|
|
||||||
println(" T_f envelope: [$(round(Tf_lo_env; digits=2)), $(round(Tf_hi_env; digits=2))] °C")
|
|
||||||
println(" T_c envelope: [$(round(Tc_lo_env; digits=2)), $(round(Tc_hi_env; digits=2))] °C")
|
|
||||||
println(" T_cold envelope: [$(round(Tcold_lo; digits=2)), $(round(Tcold_hi; digits=2))] °C")
|
|
||||||
results[T_probe] = (status="OK", n_sets=n_sets, elapsed=elapsed,
|
|
||||||
Tc=(Tc_lo_env, Tc_hi_env),
|
|
||||||
Tf=(Tf_lo_env, Tf_hi_env),
|
|
||||||
Tcold=(Tcold_lo, Tcold_hi),
|
|
||||||
n=(n_env_lo, n_env_hi),
|
|
||||||
t_arr=t_arr,
|
|
||||||
Tc_lo_ts=Tc_lo_ts, Tc_hi_ts=Tc_hi_ts,
|
|
||||||
Tf_lo_ts=Tf_lo_ts, Tf_hi_ts=Tf_hi_ts,
|
|
||||||
Tco_lo_ts=Tco_lo_ts, Tco_hi_ts=Tco_hi_ts,
|
|
||||||
n_lo_ts=n_lo_ts, n_hi_ts=n_hi_ts,
|
|
||||||
rho_lo_ts=rho_lo_ts, rho_hi_ts=rho_hi_ts)
|
|
||||||
catch err
|
|
||||||
msg = sprint(showerror, err)
|
|
||||||
println(" FAILED: ", first(msg, 300))
|
|
||||||
results[T_probe] = (status="FAILED", err=first(msg, 300))
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
println("\n=== Summary ===")
|
|
||||||
for T_probe in probe_horizons
|
|
||||||
haskey(results, T_probe) || continue
|
|
||||||
r = results[T_probe]
|
|
||||||
if r.status == "OK"
|
|
||||||
println(" T = $(T_probe) s: OK, $(r.n_sets) reach-sets, $(round(r.elapsed; digits=1))s wall")
|
|
||||||
else
|
|
||||||
println(" T = $(T_probe) s: FAILED")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Save the longest-successful probe's envelope arrays for the app.
|
|
||||||
mat_out = joinpath(@__DIR__, "..", "..", "reachability", "reach_heatup_pj_result.mat")
|
|
||||||
saved = Dict{String, Any}()
|
|
||||||
saved["probe_horizons"] = collect(probe_horizons)
|
|
||||||
for T_probe in probe_horizons
|
|
||||||
haskey(results, T_probe) || continue
|
|
||||||
r = results[T_probe]
|
|
||||||
if r.status == "OK"
|
|
||||||
pre = "T_$(Int(T_probe))_"
|
|
||||||
saved[pre * "Tc_lo"] = r.Tc[1]
|
|
||||||
saved[pre * "Tc_hi"] = r.Tc[2]
|
|
||||||
saved[pre * "Tf_lo"] = r.Tf[1]
|
|
||||||
saved[pre * "Tf_hi"] = r.Tf[2]
|
|
||||||
saved[pre * "Tcold_lo"] = r.Tcold[1]
|
|
||||||
saved[pre * "Tcold_hi"] = r.Tcold[2]
|
|
||||||
saved[pre * "n_lo"] = r.n[1]
|
|
||||||
saved[pre * "n_hi"] = r.n[2]
|
|
||||||
# Per-timestep envelopes
|
|
||||||
saved[pre * "t_arr"] = r.t_arr
|
|
||||||
saved[pre * "Tc_lo_ts"] = r.Tc_lo_ts
|
|
||||||
saved[pre * "Tc_hi_ts"] = r.Tc_hi_ts
|
|
||||||
saved[pre * "Tf_lo_ts"] = r.Tf_lo_ts
|
|
||||||
saved[pre * "Tf_hi_ts"] = r.Tf_hi_ts
|
|
||||||
saved[pre * "Tco_lo_ts"] = r.Tco_lo_ts
|
|
||||||
saved[pre * "Tco_hi_ts"] = r.Tco_hi_ts
|
|
||||||
saved[pre * "n_lo_ts"] = r.n_lo_ts
|
|
||||||
saved[pre * "n_hi_ts"] = r.n_hi_ts
|
|
||||||
saved[pre * "rho_lo_ts"] = r.rho_lo_ts
|
|
||||||
saved[pre * "rho_hi_ts"] = r.rho_hi_ts
|
|
||||||
end
|
|
||||||
end
|
|
||||||
matwrite(mat_out, saved)
|
|
||||||
println("\nSaved envelope summaries to $mat_out")
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
#!/usr/bin/env julia
|
|
||||||
#
|
|
||||||
# reach_heatup_pj_tight.jl — heatup PJ reach with a tighter X_entry.
|
|
||||||
#
|
|
||||||
# The default X_entry (from predicates.json) has T_c ∈ [281, 295] — 14 K
|
|
||||||
# wide. The baseline PJ reach at T=300s produced T_c envelope
|
|
||||||
# [272.4, 295.0], violating the low-T_avg trip at 280.
|
|
||||||
#
|
|
||||||
# Hypothesis: entry-box width is contributing to tube growth. Try
|
|
||||||
# T_c ∈ [285, 291] (6 K) and T_f matched, see if the lower envelope
|
|
||||||
# rises above 280.
|
|
||||||
|
|
||||||
using Pkg
|
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
|
||||||
|
|
||||||
using LinearAlgebra
|
|
||||||
using ReachabilityAnalysis, LazySets
|
|
||||||
using MAT
|
|
||||||
|
|
||||||
# Same constants as reach_heatup_pj.jl.
|
|
||||||
const LAMBDA = 1e-4
|
|
||||||
const BETA_1, BETA_2, BETA_3, BETA_4, BETA_5, BETA_6 =
|
|
||||||
0.000215, 0.001424, 0.001274, 0.002568, 0.000748, 0.000273
|
|
||||||
const BETA = BETA_1 + BETA_2 + BETA_3 + BETA_4 + BETA_5 + BETA_6
|
|
||||||
const LAM_1, LAM_2, LAM_3, LAM_4, LAM_5, LAM_6 =
|
|
||||||
0.0124, 0.0305, 0.111, 0.301, 1.14, 3.01
|
|
||||||
const P0 = 1e9
|
|
||||||
const M_F, C_F, M_C, C_C, HA, W_M, M_SG =
|
|
||||||
50000.0, 300.0, 20000.0, 5450.0, 5e7, 5000.0, 30000.0
|
|
||||||
const T_COLD0 = 290.0
|
|
||||||
const DT_CORE = P0 / (W_M * C_C)
|
|
||||||
const T_HOT0 = T_COLD0 + DT_CORE
|
|
||||||
const T_C0 = (T_HOT0 + T_COLD0) / 2
|
|
||||||
const T_F0 = T_C0 + P0 / HA
|
|
||||||
|
|
||||||
const T_STANDBY = T_C0 - 33.333333
|
|
||||||
const RAMP_RATE_CS = 28.0 / 3600
|
|
||||||
const KP_HEATUP = 1e-4
|
|
||||||
|
|
||||||
@taylorize function rhs_heatup_pj_tight!(dx, x, p, t)
|
|
||||||
rho = KP_HEATUP * (T_STANDBY + RAMP_RATE_CS * x[10] - x[8])
|
|
||||||
sum_lam_C = LAM_1*x[1] + LAM_2*x[2] + LAM_3*x[3] +
|
|
||||||
LAM_4*x[4] + LAM_5*x[5] + LAM_6*x[6]
|
|
||||||
denom = BETA - rho
|
|
||||||
n = LAMBDA * sum_lam_C / denom
|
|
||||||
inv_factor = sum_lam_C / denom
|
|
||||||
dx[1] = BETA_1 * inv_factor - LAM_1 * x[1]
|
|
||||||
dx[2] = BETA_2 * inv_factor - LAM_2 * x[2]
|
|
||||||
dx[3] = BETA_3 * inv_factor - LAM_3 * x[3]
|
|
||||||
dx[4] = BETA_4 * inv_factor - LAM_4 * x[4]
|
|
||||||
dx[5] = BETA_5 * inv_factor - LAM_5 * x[5]
|
|
||||||
dx[6] = BETA_6 * inv_factor - LAM_6 * x[6]
|
|
||||||
dx[7] = (P0 * n - HA * (x[7] - x[8])) / (M_F * C_F)
|
|
||||||
dx[8] = (HA * (x[7] - x[8]) - 2 * W_M * C_C * (x[8] - x[9])) / (M_C * C_C)
|
|
||||||
dx[9] = (2 * W_M * C_C * (x[8] - x[9])) / (M_SG * C_C)
|
|
||||||
dx[10] = one(x[1])
|
|
||||||
return nothing
|
|
||||||
end
|
|
||||||
|
|
||||||
# Tighter X_entry on T_c and T_f specifically.
|
|
||||||
n_lo, n_hi = 1.0e-3, 2.0e-3 # narrower n
|
|
||||||
T_f_lo, T_f_hi = 285.0, 291.0 # was 275–295
|
|
||||||
T_c_lo, T_c_hi = 285.0, 291.0 # was 281–295
|
|
||||||
T_cold_lo, T_cold_hi = 278.0, 285.0 # was 270–281 (shifted up)
|
|
||||||
|
|
||||||
n_mid = 0.5 * (n_lo + n_hi)
|
|
||||||
C_mid = [BETA_1/(LAM_1*LAMBDA), BETA_2/(LAM_2*LAMBDA),
|
|
||||||
BETA_3/(LAM_3*LAMBDA), BETA_4/(LAM_4*LAMBDA),
|
|
||||||
BETA_5/(LAM_5*LAMBDA), BETA_6/(LAM_6*LAMBDA)] .* n_mid
|
|
||||||
|
|
||||||
x_lo = [C_mid[1]*(n_lo/n_mid), C_mid[2]*(n_lo/n_mid),
|
|
||||||
C_mid[3]*(n_lo/n_mid), C_mid[4]*(n_lo/n_mid),
|
|
||||||
C_mid[5]*(n_lo/n_mid), C_mid[6]*(n_lo/n_mid),
|
|
||||||
T_f_lo, T_c_lo, T_cold_lo, 0.0]
|
|
||||||
x_hi = [C_mid[1]*(n_hi/n_mid), C_mid[2]*(n_hi/n_mid),
|
|
||||||
C_mid[3]*(n_hi/n_mid), C_mid[4]*(n_hi/n_mid),
|
|
||||||
C_mid[5]*(n_hi/n_mid), C_mid[6]*(n_hi/n_mid),
|
|
||||||
T_f_hi, T_c_hi, T_cold_hi, 0.0]
|
|
||||||
|
|
||||||
X0 = Hyperrectangle(low=x_lo, high=x_hi)
|
|
||||||
|
|
||||||
println("\n=== Heatup PJ reach with TIGHTENED X_entry ===")
|
|
||||||
println(" T_c ∈ [$(T_c_lo), $(T_c_hi)] (width 6 K, was 14 K)")
|
|
||||||
println(" T_f ∈ [$(T_f_lo), $(T_f_hi)]")
|
|
||||||
println(" n-implied ∈ [$(n_lo), $(n_hi)]")
|
|
||||||
|
|
||||||
for T_probe in (60.0, 300.0)
|
|
||||||
println("\n--- Probe T = $T_probe s ---")
|
|
||||||
sys = BlackBoxContinuousSystem(rhs_heatup_pj_tight!, 10)
|
|
||||||
prob = InitialValueProblem(sys, X0)
|
|
||||||
try
|
|
||||||
alg = TMJets(orderT=4, orderQ=2, abstol=1e-9, maxsteps=100000)
|
|
||||||
t_start = time()
|
|
||||||
sol = solve(prob; T=T_probe, alg=alg)
|
|
||||||
elapsed = time() - t_start
|
|
||||||
flow = flowpipe(sol)
|
|
||||||
flow_hr = overapproximate(flow, Hyperrectangle)
|
|
||||||
Tc_lo_env = minimum(low(set(R), 8) for R in flow_hr)
|
|
||||||
Tc_hi_env = maximum(high(set(R), 8) for R in flow_hr)
|
|
||||||
Tf_lo_env = minimum(low(set(R), 7) for R in flow_hr)
|
|
||||||
Tf_hi_env = maximum(high(set(R), 7) for R in flow_hr)
|
|
||||||
println(" $(length(flow_hr)) sets in $(round(elapsed; digits=1))s")
|
|
||||||
println(" T_c envelope: [$(round(Tc_lo_env; digits=2)), $(round(Tc_hi_env; digits=2))] °C")
|
|
||||||
println(" T_f envelope: [$(round(Tf_lo_env; digits=2)), $(round(Tf_hi_env; digits=2))] °C")
|
|
||||||
println(" Low-T_avg trip (T_c ≥ 280): $(Tc_lo_env >= 280 ? "✅ DISCHARGED" : "× still loose")")
|
|
||||||
catch err
|
|
||||||
println(" FAILED: ", first(sprint(showerror, err), 200))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
#!/usr/bin/env julia
|
|
||||||
#
|
|
||||||
# reach_heatup_pj_tight_full.jl — tight-entry heatup PJ reach,
|
|
||||||
# per-timestep envelopes saved for plotting (T_c, T_h, T_cold, rho, n).
|
|
||||||
#
|
|
||||||
# Identical dynamics to reach_heatup_pj_tight.jl but keeps the full
|
|
||||||
# tube data so we can overlay T_c and T_h (and infer power) on one plot.
|
|
||||||
|
|
||||||
using Pkg
|
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
|
||||||
|
|
||||||
using LinearAlgebra
|
|
||||||
using ReachabilityAnalysis, LazySets
|
|
||||||
using MAT
|
|
||||||
|
|
||||||
const LAMBDA = 1e-4
|
|
||||||
const BETA_1, BETA_2, BETA_3, BETA_4, BETA_5, BETA_6 =
|
|
||||||
0.000215, 0.001424, 0.001274, 0.002568, 0.000748, 0.000273
|
|
||||||
const BETA = BETA_1 + BETA_2 + BETA_3 + BETA_4 + BETA_5 + BETA_6
|
|
||||||
const LAM_1, LAM_2, LAM_3, LAM_4, LAM_5, LAM_6 =
|
|
||||||
0.0124, 0.0305, 0.111, 0.301, 1.14, 3.01
|
|
||||||
const P0 = 1e9
|
|
||||||
const M_F, C_F, M_C, C_C, HA, W_M, M_SG =
|
|
||||||
50000.0, 300.0, 20000.0, 5450.0, 5e7, 5000.0, 30000.0
|
|
||||||
const T_COLD0 = 290.0
|
|
||||||
const DT_CORE = P0 / (W_M * C_C)
|
|
||||||
const T_HOT0 = T_COLD0 + DT_CORE
|
|
||||||
const T_C0 = (T_HOT0 + T_COLD0) / 2
|
|
||||||
const T_F0 = T_C0 + P0 / HA
|
|
||||||
|
|
||||||
const T_STANDBY = T_C0 - 33.333333
|
|
||||||
const RAMP_RATE_CS = 28.0 / 3600
|
|
||||||
const KP_HEATUP = 1e-4
|
|
||||||
|
|
||||||
@taylorize function rhs_heatup_pj_tf!(dx, x, p, t)
|
|
||||||
rho = KP_HEATUP * (T_STANDBY + RAMP_RATE_CS * x[10] - x[8])
|
|
||||||
sum_lam_C = LAM_1*x[1] + LAM_2*x[2] + LAM_3*x[3] +
|
|
||||||
LAM_4*x[4] + LAM_5*x[5] + LAM_6*x[6]
|
|
||||||
denom = BETA - rho
|
|
||||||
n = LAMBDA * sum_lam_C / denom
|
|
||||||
inv_factor = sum_lam_C / denom
|
|
||||||
dx[1] = BETA_1 * inv_factor - LAM_1 * x[1]
|
|
||||||
dx[2] = BETA_2 * inv_factor - LAM_2 * x[2]
|
|
||||||
dx[3] = BETA_3 * inv_factor - LAM_3 * x[3]
|
|
||||||
dx[4] = BETA_4 * inv_factor - LAM_4 * x[4]
|
|
||||||
dx[5] = BETA_5 * inv_factor - LAM_5 * x[5]
|
|
||||||
dx[6] = BETA_6 * inv_factor - LAM_6 * x[6]
|
|
||||||
dx[7] = (P0 * n - HA * (x[7] - x[8])) / (M_F * C_F)
|
|
||||||
dx[8] = (HA * (x[7] - x[8]) - 2 * W_M * C_C * (x[8] - x[9])) / (M_C * C_C)
|
|
||||||
dx[9] = (2 * W_M * C_C * (x[8] - x[9])) / (M_SG * C_C)
|
|
||||||
dx[10] = one(x[1])
|
|
||||||
return nothing
|
|
||||||
end
|
|
||||||
|
|
||||||
# Tight X_entry.
|
|
||||||
n_lo, n_hi = 1.0e-3, 2.0e-3
|
|
||||||
T_f_lo, T_f_hi = 285.0, 291.0
|
|
||||||
T_c_lo, T_c_hi = 285.0, 291.0
|
|
||||||
T_cold_lo, T_cold_hi = 278.0, 285.0
|
|
||||||
|
|
||||||
n_mid = 0.5 * (n_lo + n_hi)
|
|
||||||
C_mid = [BETA_1/(LAM_1*LAMBDA), BETA_2/(LAM_2*LAMBDA),
|
|
||||||
BETA_3/(LAM_3*LAMBDA), BETA_4/(LAM_4*LAMBDA),
|
|
||||||
BETA_5/(LAM_5*LAMBDA), BETA_6/(LAM_6*LAMBDA)] .* n_mid
|
|
||||||
|
|
||||||
x_lo = [C_mid[1]*(n_lo/n_mid), C_mid[2]*(n_lo/n_mid),
|
|
||||||
C_mid[3]*(n_lo/n_mid), C_mid[4]*(n_lo/n_mid),
|
|
||||||
C_mid[5]*(n_lo/n_mid), C_mid[6]*(n_lo/n_mid),
|
|
||||||
T_f_lo, T_c_lo, T_cold_lo, 0.0]
|
|
||||||
x_hi = [C_mid[1]*(n_hi/n_mid), C_mid[2]*(n_hi/n_mid),
|
|
||||||
C_mid[3]*(n_hi/n_mid), C_mid[4]*(n_hi/n_mid),
|
|
||||||
C_mid[5]*(n_hi/n_mid), C_mid[6]*(n_hi/n_mid),
|
|
||||||
T_f_hi, T_c_hi, T_cold_hi, 0.0]
|
|
||||||
X0 = Hyperrectangle(low=x_lo, high=x_hi)
|
|
||||||
|
|
||||||
println("\n=== Tight-entry heatup PJ reach — saving full per-step envelopes ===")
|
|
||||||
|
|
||||||
T_probe = 300.0
|
|
||||||
sys = BlackBoxContinuousSystem(rhs_heatup_pj_tf!, 10)
|
|
||||||
prob = InitialValueProblem(sys, X0)
|
|
||||||
alg = TMJets(orderT=4, orderQ=2, abstol=1e-9, maxsteps=100000)
|
|
||||||
t_start = time()
|
|
||||||
sol = solve(prob; T=T_probe, alg=alg)
|
|
||||||
println(" Wall time: $(round(time() - t_start; digits=1)) s")
|
|
||||||
flow_hr = overapproximate(flowpipe(sol), Hyperrectangle)
|
|
||||||
n_steps = length(flow_hr)
|
|
||||||
println(" $n_steps reach-sets")
|
|
||||||
|
|
||||||
t_arr = zeros(n_steps)
|
|
||||||
Tc_lo_ts = zeros(n_steps); Tc_hi_ts = zeros(n_steps)
|
|
||||||
Tf_lo_ts = zeros(n_steps); Tf_hi_ts = zeros(n_steps)
|
|
||||||
Tco_lo_ts = zeros(n_steps); Tco_hi_ts = zeros(n_steps)
|
|
||||||
n_lo_ts = zeros(n_steps); n_hi_ts = zeros(n_steps)
|
|
||||||
rho_lo_ts = zeros(n_steps); rho_hi_ts = zeros(n_steps)
|
|
||||||
|
|
||||||
for (k, R) in enumerate(flow_hr)
|
|
||||||
s = set(R)
|
|
||||||
t_arr[k] = high(s, 10)
|
|
||||||
Tc_lo_ts[k] = low(s, 8); Tc_hi_ts[k] = high(s, 8)
|
|
||||||
Tf_lo_ts[k] = low(s, 7); Tf_hi_ts[k] = high(s, 7)
|
|
||||||
Tco_lo_ts[k] = low(s, 9); Tco_hi_ts[k] = high(s, 9)
|
|
||||||
sumLC_lo = LAM_1*low(s,1) + LAM_2*low(s,2) + LAM_3*low(s,3) +
|
|
||||||
LAM_4*low(s,4) + LAM_5*low(s,5) + LAM_6*low(s,6)
|
|
||||||
sumLC_hi = LAM_1*high(s,1) + LAM_2*high(s,2) + LAM_3*high(s,3) +
|
|
||||||
LAM_4*high(s,4) + LAM_5*high(s,5) + LAM_6*high(s,6)
|
|
||||||
# Ramp-ref bounds at this reach-set's t.
|
|
||||||
t_hi_here = high(s, 10)
|
|
||||||
t_lo_here = low(s, 10)
|
|
||||||
# T_ref at these times (monotone increasing):
|
|
||||||
Tref_lo = T_STANDBY + RAMP_RATE_CS * t_lo_here
|
|
||||||
Tref_hi = T_STANDBY + RAMP_RATE_CS * t_hi_here
|
|
||||||
rho_lo_here = KP_HEATUP * (Tref_lo - high(s, 8))
|
|
||||||
rho_hi_here = KP_HEATUP * (Tref_hi - low(s, 8))
|
|
||||||
rho_lo_ts[k] = rho_lo_here
|
|
||||||
rho_hi_ts[k] = rho_hi_here
|
|
||||||
denom_lo = BETA - rho_hi_here
|
|
||||||
denom_hi = BETA - rho_lo_here
|
|
||||||
if denom_lo > 0
|
|
||||||
n_lo_ts[k] = LAMBDA * sumLC_lo / denom_hi
|
|
||||||
n_hi_ts[k] = LAMBDA * sumLC_hi / denom_lo
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
println(" T_c envelope: [$(round(minimum(Tc_lo_ts); digits=2)), $(round(maximum(Tc_hi_ts); digits=2))] °C")
|
|
||||||
println(" T_hot envelope: [$(round(minimum(2 .* Tc_lo_ts .- Tco_hi_ts); digits=2)), $(round(maximum(2 .* Tc_hi_ts .- Tco_lo_ts); digits=2))] °C")
|
|
||||||
println(" T_cold env: [$(round(minimum(Tco_lo_ts); digits=2)), $(round(maximum(Tco_hi_ts); digits=2))] °C")
|
|
||||||
println(" rho envelope: [$(round(minimum(rho_lo_ts); sigdigits=4)), $(round(maximum(rho_hi_ts); sigdigits=4))]")
|
|
||||||
println(" rho / beta: [$(round(minimum(rho_lo_ts)/BETA; digits=3)), $(round(maximum(rho_hi_ts)/BETA; digits=3))]")
|
|
||||||
|
|
||||||
mat_out = joinpath(@__DIR__, "..", "..", "reachability", "reach_heatup_pj_tight_full.mat")
|
|
||||||
matwrite(mat_out, Dict(
|
|
||||||
"t_arr" => t_arr,
|
|
||||||
"Tc_lo_ts" => Tc_lo_ts, "Tc_hi_ts" => Tc_hi_ts,
|
|
||||||
"Tf_lo_ts" => Tf_lo_ts, "Tf_hi_ts" => Tf_hi_ts,
|
|
||||||
"Tco_lo_ts" => Tco_lo_ts, "Tco_hi_ts" => Tco_hi_ts,
|
|
||||||
"n_lo_ts" => n_lo_ts, "n_hi_ts" => n_hi_ts,
|
|
||||||
"rho_lo_ts" => rho_lo_ts, "rho_hi_ts" => rho_hi_ts,
|
|
||||||
"T_probe" => T_probe,
|
|
||||||
"beta" => BETA,
|
|
||||||
"Kp" => KP_HEATUP,
|
|
||||||
"T_c0" => T_C0,
|
|
||||||
"T_cold0" => T_COLD0,
|
|
||||||
"T_standby" => T_STANDBY,
|
|
||||||
))
|
|
||||||
println("\nSaved full per-step envelopes to $mat_out")
|
|
||||||
@ -7,7 +7,7 @@
|
|||||||
# names (overwrites MATLAB outputs — the Julia versions take over).
|
# names (overwrites MATLAB outputs — the Julia versions take over).
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using Printf
|
using Printf
|
||||||
using LinearAlgebra
|
using LinearAlgebra
|
||||||
@ -16,18 +16,18 @@ using Plots
|
|||||||
using MatrixEquations
|
using MatrixEquations
|
||||||
using JSON
|
using JSON
|
||||||
|
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_linearize.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_linearize.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_solver.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_solver.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "plot_pke_results.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "plot_pke_results.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "controllers", "controllers.jl"))
|
include(joinpath(@__DIR__, "..", "..", "controllers", "controllers.jl"))
|
||||||
|
|
||||||
# --- Plant + predicates ---
|
# --- Plant + predicates ---
|
||||||
plant = pke_params()
|
plant = pke_params()
|
||||||
|
|
||||||
# Load T_standby from predicates.json for the hot-standby IC + heatup ref.
|
# Load T_standby from predicates.json for the hot-standby IC + heatup ref.
|
||||||
pred_path = joinpath(@__DIR__, "..", "..", "reachability", "predicates.json")
|
pred_path = joinpath(@__DIR__, "..", "..", "..", "reachability", "predicates.json")
|
||||||
pred_raw = JSON.parsefile(pred_path)
|
pred_raw = JSON.parsefile(pred_path)
|
||||||
T_standby = plant.T_c0 + pred_raw["derived"]["T_standby_offset_C"]
|
T_standby = plant.T_c0 + pred_raw["derived"]["T_standby_offset_C"]
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ ctrl_op_lqr, K_lqr, _ = ctrl_operation_lqr_factory(
|
|||||||
plant, A, B; Q_lqr=Matrix(Q_lqr), R_lqr=R_lqr)
|
plant, A, B; Q_lqr=Matrix(Q_lqr), R_lqr=R_lqr)
|
||||||
|
|
||||||
# --- Figure output directory ---
|
# --- Figure output directory ---
|
||||||
figdir = joinpath(@__DIR__, "..", "..", "docs", "figures")
|
figdir = joinpath(@__DIR__, "..", "..", "..", "docs", "figures")
|
||||||
isdir(figdir) || mkpath(figdir)
|
isdir(figdir) || mkpath(figdir)
|
||||||
|
|
||||||
# --- Run each mode ---
|
# --- Run each mode ---
|
||||||
@ -9,7 +9,7 @@
|
|||||||
# trajectory that reach tubes can be checked against.
|
# trajectory that reach tubes can be checked against.
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using OrdinaryDiffEq
|
using OrdinaryDiffEq
|
||||||
|
|
||||||
@ -6,13 +6,13 @@
|
|||||||
# and prints the final state, which should agree with MATLAB to ~1e-3.
|
# and prints the final state, which should agree with MATLAB to ~1e-3.
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using OrdinaryDiffEq
|
using OrdinaryDiffEq
|
||||||
|
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "controllers", "controllers.jl"))
|
include(joinpath(@__DIR__, "..", "..", "controllers", "controllers.jl"))
|
||||||
|
|
||||||
plant = pke_params()
|
plant = pke_params()
|
||||||
x0 = pke_initial_conditions(plant)
|
x0 = pke_initial_conditions(plant)
|
||||||
@ -11,17 +11,17 @@
|
|||||||
# reconstructed n vs the dynamic n. Quantify the error explicitly.
|
# reconstructed n vs the dynamic n. Quantify the error explicitly.
|
||||||
|
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
Pkg.activate(joinpath(@__DIR__, "..", ".."))
|
||||||
|
|
||||||
using Printf
|
using Printf
|
||||||
using LinearAlgebra
|
using LinearAlgebra
|
||||||
using OrdinaryDiffEq
|
using OrdinaryDiffEq
|
||||||
using Plots
|
using Plots
|
||||||
|
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_params.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "src", "pke_th_rhs_pj.jl"))
|
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs_pj.jl"))
|
||||||
include(joinpath(@__DIR__, "..", "controllers", "controllers.jl"))
|
include(joinpath(@__DIR__, "..", "..", "controllers", "controllers.jl"))
|
||||||
|
|
||||||
plant = pke_params()
|
plant = pke_params()
|
||||||
T_standby = plant.T_c0 - 33.333333
|
T_standby = plant.T_c0 - 33.333333
|
||||||
@ -88,7 +88,7 @@ for t_check in (1.0, 5.0, 10.0, 60.0, 300.0, 1200.0, 3000.0)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# --- Plot trajectory overlay ---
|
# --- Plot trajectory overlay ---
|
||||||
figdir = joinpath(@__DIR__, "..", "..", "docs", "figures")
|
figdir = joinpath(@__DIR__, "..", "..", "..", "docs", "figures")
|
||||||
isdir(figdir) || mkpath(figdir)
|
isdir(figdir) || mkpath(figdir)
|
||||||
|
|
||||||
CtoF(T) = T*9/5 + 32
|
CtoF(T) = T*9/5 + 32
|
||||||
395
presentations/prelim-presentation/outline.md
Normal file
395
presentations/prelim-presentation/outline.md
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
# Preliminary Example Presentation — Assertion-Evidence Outline
|
||||||
|
|
||||||
|
**Format:** Assertion-evidence (Alley). Each slide: one declarative sentence
|
||||||
|
at the top, one piece of visual evidence below. No bullet soup.
|
||||||
|
|
||||||
|
**Duration:** 20 minutes. ~12 slides at ~1.5 min each (60 s typical, up to
|
||||||
|
2 min for the heavier ones).
|
||||||
|
|
||||||
|
**Audience:** OT-informed cybersecurity experts. Mostly CS, some control-theory
|
||||||
|
familiarity, very little reactor-physics background. Can assume fluency with:
|
||||||
|
LTL, automata, model-checking, reachability (as a concept), SMT/SAT. Must
|
||||||
|
explain: PKE, reactivity, precursors, hybrid systems.
|
||||||
|
|
||||||
|
**Running thread:** FRET natural-language requirement → LTL → synthesized
|
||||||
|
discrete controller → continuous plant → per-mode reach-avoid proof
|
||||||
|
→ hybrid correctness by composition.
|
||||||
|
|
||||||
|
**Design principles:**
|
||||||
|
- **Plots over bullets.** Every result slide anchors on one figure.
|
||||||
|
- **Physical intuition before math.** Reactor basics before PKE.
|
||||||
|
- **Honest limitations boxed on each result slide.** Audience are cyber
|
||||||
|
folks — they respect limits more than triumphs.
|
||||||
|
- **CS vocabulary by default, engineering terms defined inline.**
|
||||||
|
- **End with the seam**, not with a victory lap. The thesis question is
|
||||||
|
"how do discrete proofs and continuous proofs compose?" not "we verified
|
||||||
|
everything."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 1 — Title + hook
|
||||||
|
|
||||||
|
**Assertion:** Verified hybrid control for nuclear startup is a real problem
|
||||||
|
with no deployed solution.
|
||||||
|
|
||||||
|
**Evidence:** Title block + a single image showing a control-room board of
|
||||||
|
old-school analog gauges adjacent to a digital SCADA screen. Caption: "The
|
||||||
|
gap we're filling."
|
||||||
|
|
||||||
|
**Speak:** Introduce self + advisor + NRC fellowship context. Name the problem:
|
||||||
|
nuclear reactor operations are 70-80% human-error-driven at severe-accident
|
||||||
|
level (Chernobyl, TMI, Fukushima all primarily human factors). Plants run on
|
||||||
|
paper procedures that have no formal verification. The most advanced work to
|
||||||
|
date (HARDENS, NRC-funded) verified the discrete trip system in isolation and
|
||||||
|
stopped there. This talk: a preliminary example of bridging discrete
|
||||||
|
requirements all the way to continuous-state safety proofs, with the seam
|
||||||
|
between them as the research contribution.
|
||||||
|
|
||||||
|
**Reference:** thesis §2, ¶1-4. Cites Kemeny1979, Hogberg_2013, Kiniry2024.
|
||||||
|
|
||||||
|
**Figures to make:** find or compose the control-room image.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 2 — The hybrid-systems gap
|
||||||
|
|
||||||
|
**Assertion:** Existing tools verify either discrete *or* continuous systems,
|
||||||
|
never the seam between them.
|
||||||
|
|
||||||
|
**Evidence:** Two-column diagram. Left column: discrete (FRET, Spot,
|
||||||
|
SAL, HARDENS, TLA+). Right column: continuous (MATLAB, Modelica, CORA,
|
||||||
|
Flow*, HyTech). A dashed line between them labeled "the bridge problem."
|
||||||
|
|
||||||
|
**Speak:** HARDENS got to TRL 2-3 on the discrete side alone. Continuous-side
|
||||||
|
reach tools exist but assume the discrete controller is given as input. What
|
||||||
|
nobody has done: produce an *end-to-end* proof where the discrete controller's
|
||||||
|
transitions actually fire in physical time on the modeled plant.
|
||||||
|
|
||||||
|
**Reference:** thesis §2.2 "Formal Methods", §2.2.1 HARDENS, §2.2.2 Temporal
|
||||||
|
Logic. Thesis §2.3 (if exists — continuous methods).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 3 — Running example: the PWR_HYBRID_3 controller
|
||||||
|
|
||||||
|
**Assertion:** Our target is a 4-mode hybrid controller that takes a PWR
|
||||||
|
from hot standby to full power and back.
|
||||||
|
|
||||||
|
**Evidence:** The DRC state diagram (from `fret-pipeline/diagrams/PWR_HYBRID_3_DRC_states.png`).
|
||||||
|
Four nodes — shutdown, heatup, operation, scram — with labeled transitions.
|
||||||
|
|
||||||
|
**Speak:** One sentence of nuclear physics: a PWR has neutron population
|
||||||
|
(power), coolant temperatures at three locations, and precursor concentrations
|
||||||
|
that determine delayed-neutron generation. Startup is shutdown → heatup →
|
||||||
|
operation; any fault triggers scram. Mode transitions gated by boolean
|
||||||
|
conditions like `t_avg_in_range ∧ p_above_crit ∧ inv1_holds`.
|
||||||
|
|
||||||
|
Four modes means four *physical* behaviors the plant has to exhibit for
|
||||||
|
the discrete logic to be sound. This example stresses every layer of the
|
||||||
|
bridge we're building.
|
||||||
|
|
||||||
|
**Evidence path:** `fret-pipeline/diagrams/PWR_HYBRID_3_DRC_states.png`,
|
||||||
|
`fret-pipeline/pwr_hybrid_3.json` for one example requirement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 4 — Layer 1: FRET → LTL → synthesized DRC
|
||||||
|
|
||||||
|
**Assertion:** Natural-language requirements become a verified discrete
|
||||||
|
controller automatically.
|
||||||
|
|
||||||
|
**Evidence:** One-panel figure: left half = snippet of FRET natural language
|
||||||
|
("Upon `control_mode = q_heatup ∧ t_avg_in_range ∧ p_above_crit ∧ inv1_holds`
|
||||||
|
DRC shall at the next timepoint satisfy `control_mode = q_operation`"), right
|
||||||
|
half = the corresponding LTL, bottom = arrow labeled `ltlsynt` pointing to
|
||||||
|
the synthesized state-diagram node.
|
||||||
|
|
||||||
|
**Speak:** FRET compiles natural-language requirements into LTL (Spot
|
||||||
|
ecosystem). `ltlsynt` turns LTL into an AIGER circuit representing the
|
||||||
|
minimal correct discrete controller. This is well-trodden ground in CS-land;
|
||||||
|
our contribution is *using it* on reactor operating procedures — so far
|
||||||
|
a formal-methods-free domain.
|
||||||
|
|
||||||
|
**Evidence path:** `fret-pipeline/README.md`, `pwr_hybrid_3.json` sample
|
||||||
|
requirement, `fret-pipeline/circuits/PWR_HYBRID_3_DRC.aag` AIGER circuit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 5 — Layer 2: the plant model
|
||||||
|
|
||||||
|
**Assertion:** A 10-state PKE + thermal-hydraulics model is faithful enough
|
||||||
|
for reach analysis and tractable enough to actually compute with.
|
||||||
|
|
||||||
|
**Evidence:** The state-vector diagram: `x = [n, C_1, ..., C_6, T_f, T_c, T_cold]`
|
||||||
|
with arrows showing the coupling — neutron balance → fuel heating → coolant
|
||||||
|
transport → feedback to reactivity.
|
||||||
|
|
||||||
|
**Speak:** Point-kinetic equations + three thermal nodes. 10 states, 1
|
||||||
|
control input (rod worth u), 1 disturbance (steam-generator heat removal Q_sg).
|
||||||
|
Stiff system: prompt-neutron timescale is 10⁻⁴ s, thermal timescales are
|
||||||
|
seconds to minutes. That stiffness becomes critical later — it's what forces
|
||||||
|
the singular-perturbation move.
|
||||||
|
|
||||||
|
Five controllers: one per DRC mode (shutdown, heatup, operation under P, under
|
||||||
|
LQR, scram). All Julia, all in `code/src/` + `code/controllers/`.
|
||||||
|
|
||||||
|
**Evidence path:** `code/src/pke_th_rhs.jl`, `journal/journal.pdf` entry
|
||||||
|
2026-04-17 for derivation.
|
||||||
|
|
||||||
|
**Figures to make:** state-vector coupling diagram (can be ASCII → inkscape).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 6 — Layer 3: the reach-avoid obligation per mode
|
||||||
|
|
||||||
|
**Assertion:** Discrete correctness transfers to continuous correctness
|
||||||
|
via one reach-avoid obligation per mode.
|
||||||
|
|
||||||
|
**Evidence:** Taxonomy table from `reachability/WALKTHROUGH.md`:
|
||||||
|
|
||||||
|
| Mode | Kind | Obligation |
|
||||||
|
|---|---|---|
|
||||||
|
| shutdown | equilibrium | stay in X_safe forever |
|
||||||
|
| heatup | transition | from X_entry, reach X_exit in [T_min, T_max] while maintaining inv1 |
|
||||||
|
| operation | equilibrium | stay in X_safe forever under bounded Q_sg |
|
||||||
|
| scram | transition | from any mode, drive n safely subcritical in T_max |
|
||||||
|
|
||||||
|
**Speak:** This is the structural insight. Equilibrium modes → forever-invariance
|
||||||
|
proofs. Transition modes → reach-avoid proofs with time budgets. The DRC
|
||||||
|
transitions fire iff the reach set enters the right exit predicate. If
|
||||||
|
every mode's obligation is discharged, the hybrid system is correct by
|
||||||
|
composition. The methodological contribution of the chapter.
|
||||||
|
|
||||||
|
**Reference:** `reachability/WALKTHROUGH.md` §1, `reachability/predicates.json`
|
||||||
|
→ `mode_boundaries`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 7 — Operation mode: discharge all 6 safety halfspaces (the win)
|
||||||
|
|
||||||
|
**Assertion:** Under LQR feedback and ±15% Q_sg variation, the operation-mode
|
||||||
|
reach tube discharges every safety halfspace with orders of magnitude of margin.
|
||||||
|
|
||||||
|
**Evidence:** The two-panel figure `docs/figures/reach_operation_tubes.png`.
|
||||||
|
Left: temperature tubes (T_c, T_hot, T_cold) overlaid; the near-zero width
|
||||||
|
of T_c visually demonstrates LQR contraction. Right: ΔT_core tube with a
|
||||||
|
right-axis in MW showing the power is tight around nominal.
|
||||||
|
|
||||||
|
Per-halfspace margin table (inset or second slide if space allows):
|
||||||
|
|
||||||
|
| Halfspace | Limit | Reach max | Margin |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| fuel_centerline | 1200 °C | 328.9 | **+871 K** |
|
||||||
|
| t_avg_high_trip | 320 °C | 308.4 | **+11.6 K** |
|
||||||
|
| n_high_trip | 1.15 | 1.012 | **+0.14** |
|
||||||
|
|
||||||
|
**Speak:** LQR contracts the regulated direction (T_c) from 0.1 K to 0.03 K
|
||||||
|
halfwidth. Uncontrolled directions (precursors) expand but don't appear in
|
||||||
|
any safety predicate. Tightest margin is the high-flux trip at 12% headroom.
|
||||||
|
|
||||||
|
**Limitations box:** linear-reach of a nonlinear plant — *approximate, not
|
||||||
|
sound* yet for the physical plant. That's what the next slides address.
|
||||||
|
|
||||||
|
**Evidence path:** `code/scripts/reach/reach_operation.jl`,
|
||||||
|
`docs/figures/reach_operation_tubes.png`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 8 — Quadratic Lyapunov barrier fails structurally
|
||||||
|
|
||||||
|
**Assertion:** The canonical quadratic Lyapunov barrier cannot certify this
|
||||||
|
plant — *even with LQR inside the barrier* — because of plant anisotropy.
|
||||||
|
|
||||||
|
**Evidence:** Bar chart comparing open-loop vs LQR-closed-loop Lyapunov bound
|
||||||
|
on `n_high_trip` direction: OL is 27 million × nominal, CL is 1,242 × nominal.
|
||||||
|
Title: "LQR helps 20,000× but both bounds are physically meaningless."
|
||||||
|
|
||||||
|
**Speak:** Plant has prompt timescale 10⁻⁴ s vs thermal timescale ~10 s.
|
||||||
|
Lyapunov matrix P inherits that 10⁴ spread. Resulting ellipsoid stretches
|
||||||
|
obscenely along the fast directions when projected to physical coordinates.
|
||||||
|
No amount of controller tuning fixes this — it's set by the plant's own
|
||||||
|
physics. The ellipsoid geometry cannot match the slab-shaped safe region.
|
||||||
|
**This is the motivation for polynomial (SOS) or polytopic barriers.**
|
||||||
|
|
||||||
|
**Reference:** `code/scripts/barrier/barrier_compare_OL_CL.jl`, journal entry
|
||||||
|
2026-04-20 (evening).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 9 — Prompt-jump reduction makes nonlinear reach tractable
|
||||||
|
|
||||||
|
**Assertion:** Singular-perturbation reduction of the fast neutronics gives
|
||||||
|
us 30× more reach horizon and a rigorous O(Λ) error bound via Tikhonov.
|
||||||
|
|
||||||
|
**Evidence:** Side-by-side: left panel = validate_pj_heatup.png (empirical:
|
||||||
|
PJ and full-state trajectories overlay perfectly over 50 minutes). Right panel
|
||||||
|
= the Tikhonov bound written out mathematically:
|
||||||
|
$$|x(t) - x_{\mathrm{PJ}}(t)| \leq C \cdot \Lambda = \mathcal{O}(10^{-4})$$
|
||||||
|
|
||||||
|
**Speak:** Setting dn/dt = 0 and solving algebraically for n gives us
|
||||||
|
`n = Λ·Σλᵢ·Cᵢ / (β - ρ)`. State drops from 10 to 9, removes the Λ⁻¹
|
||||||
|
stiffness. Reach tool (TMJets) can take steps on thermal timescale instead
|
||||||
|
of prompt timescale.
|
||||||
|
|
||||||
|
The magic: we *prove* the PJ approximation's validity condition (`β - ρ > 0`
|
||||||
|
with margin) **as part of the same reach obligation** via the
|
||||||
|
`prompt_critical_margin_heatup` halfspace in `inv1_holds`. No hand-waving —
|
||||||
|
the reach machinery proves both the safety claim and the approximation's
|
||||||
|
soundness together.
|
||||||
|
|
||||||
|
**Evidence path:** `code/src/pke_th_rhs_pj.jl`, `code/scripts/sim/validate_pj.jl`,
|
||||||
|
`journal/` entry 2026-04-21 "Tikhonov bound".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 10 — First sound nonlinear heatup reach (the second win)
|
||||||
|
|
||||||
|
**Assertion:** With PJ + a tightened entry box, nonlinear Taylor-model reach
|
||||||
|
discharges all 6 `inv1_holds` safety halfspaces over the first 300 s of heatup.
|
||||||
|
|
||||||
|
**Evidence:** The four-panel `reach_heatup_pj_tubes.png`: temperature tubes
|
||||||
|
overlaid, ΔT_core, ρ in dollars (stays between -0.25 $ and -0.05 $ —
|
||||||
|
sub-prompt-critical), n decaying.
|
||||||
|
|
||||||
|
**Speak:** TMJets Taylor-model integration. 12,932 reach-sets, 200 s wall time.
|
||||||
|
Tube **stable** — T_c envelope `[281.05, 291.0]` identical at 60 s and 300 s,
|
||||||
|
meaning the controller holds the state inside the tube indefinitely. This
|
||||||
|
is the first sound (modulo O(Λ) PJ error) nonlinear reach-avoid artifact
|
||||||
|
for this plant.
|
||||||
|
|
||||||
|
**Limitations box:** 300 s of a 5-hour `T_max`. Step budget caps horizon; entry
|
||||||
|
refinement (Blanchini-style) is the path to hours.
|
||||||
|
|
||||||
|
**Evidence path:** `code/scripts/reach/reach_heatup_pj.jl configs/heatup/tight.toml`,
|
||||||
|
`docs/figures/reach_heatup_pj_tubes.png`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 11 — Degree-4 SOS polynomial barrier, a working proof of concept
|
||||||
|
|
||||||
|
**Assertion:** A degree-4 polynomial barrier certificate — the structure that
|
||||||
|
quadratic Lyapunov cannot provide — works on the operation-mode projection.
|
||||||
|
|
||||||
|
**Evidence:** Equation block showing the symbolic polynomial
|
||||||
|
(abbreviated — the actual 13-term polynomial from `code/scripts/barrier/barrier_sos_2d.jl`).
|
||||||
|
Side caption: "CSDP returned OPTIMAL. Solve: ~3 seconds."
|
||||||
|
|
||||||
|
**Speak:** Polynomials of degree 4 can localize to slab-shaped safety regions
|
||||||
|
in a way degree-2 (quadratic) cannot. The Prajna-Jadbabaie formulation: find
|
||||||
|
`B(x)` such that `B ≤ 0` on entry, `B ≥ 0` on unsafe, and `∇B·f ≤ 0` on
|
||||||
|
`{B=0}`. Sum-of-squares programming reduces this to an SDP. 2D projection
|
||||||
|
proof of concept; scaling to full 10-state requires bigger solver (Mosek or SCS).
|
||||||
|
|
||||||
|
**Reference:** `code/scripts/barrier/barrier_sos_2d.jl`, journal entry
|
||||||
|
2026-04-21.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 12 — The hybrid correctness story, assembled
|
||||||
|
|
||||||
|
**Assertion:** All four pieces — FRET/synthesis, plant model, reach tubes,
|
||||||
|
SOS/polytopic barriers — compose into a hybrid system correctness argument.
|
||||||
|
|
||||||
|
**Evidence:** The composition picture. DRC state diagram at center; each
|
||||||
|
DRC node has an arrow to a "per-mode obligation box" labeled with its proof
|
||||||
|
artifact (tube or barrier or certificate). Arrows between nodes = transitions,
|
||||||
|
labeled with the predicate polytope. Outer dashed box: "hybrid system
|
||||||
|
closed-loop safety."
|
||||||
|
|
||||||
|
**Speak:** What's been built, what's been proven, what's next. Transition
|
||||||
|
correctness between modes is the next thesis-blocking piece — each mode's
|
||||||
|
`X_exit` has to be inside the next mode's `X_entry` (with margin). That's an
|
||||||
|
inclusion check, not a reach — but it's the final structural piece.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 13 — Limitations and next steps
|
||||||
|
|
||||||
|
**Assertion:** This is a preliminary example, not a deployable system.
|
||||||
|
|
||||||
|
**Evidence:** A two-column list:
|
||||||
|
|
||||||
|
| What's proven | What's next |
|
||||||
|
|---|---|
|
||||||
|
| Operation mode safe under ±15% load (approximate, via linear reach) | Nonlinear operation reach (tight SOS barrier, LOCA disturbance) |
|
||||||
|
| Heatup PJ reach discharges all 6 safety halfspaces for 300 s | Extend to full 5-hr `T_max`; model steam-dump disturbance |
|
||||||
|
| Scram PJ n-decay monotone over 60 s | Expand `X_entry(scram)` to union of all mode tubes + LOCA |
|
||||||
|
| Degree-4 SOS barrier on 2D projection | Full 10-state SOS barrier |
|
||||||
|
| PJ error rigorously bounded by Tikhonov | Parametric α uncertainty; DNBR correlation |
|
||||||
|
|
||||||
|
**Speak:** The gap from prelim example to deployable system is well-defined:
|
||||||
|
more states, tighter bounds, real tech-spec numbers, hardware-in-the-loop.
|
||||||
|
None of these is a research novelty unto itself — they're engineering.
|
||||||
|
The research contribution is the composition framework, demonstrated end-to-end
|
||||||
|
on a nontrivial running example. Phase 2 of the thesis is filling in the
|
||||||
|
gaps and expanding to multiple plants.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 14 — Questions / acknowledgements
|
||||||
|
|
||||||
|
**Assertion:** (none — backup slide)
|
||||||
|
|
||||||
|
**Evidence:** Thanks to advisor, committee, NRC fellowship. Links to:
|
||||||
|
repo (gitea), journal PDF, thesis in progress.
|
||||||
|
|
||||||
|
**Speak:** Open for questions. Expect questions on:
|
||||||
|
- "Why not Stateflow/Simulink?" → (have answer prepared; HARDENS used
|
||||||
|
Cryptol for a reason — formal tool integration matters)
|
||||||
|
- "What's the relationship to LTLMoP / DragonFly?" → (survey answer)
|
||||||
|
- "How would this interact with ML components?" → (out of scope for now,
|
||||||
|
the whole pitch is *no ML in safety-critical loop*)
|
||||||
|
- "What's your threat model for cybersecurity?" → (tie back to OT audience —
|
||||||
|
formal methods guarantee the controller's logic; they do not protect the
|
||||||
|
comms layer or implementation-level vulns. Mention HARDENS' focus on
|
||||||
|
"correctness of implementation" vs our focus on "correctness of
|
||||||
|
specification")
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Presentation construction notes
|
||||||
|
|
||||||
|
### What to build in Beamer later
|
||||||
|
- Assertion-evidence template (one sentence at top, centered figure below).
|
||||||
|
- Consistent color coding: green for "discharged" / "proved", red for
|
||||||
|
"limitation" / "gap", blue for "discrete-layer", amber for "continuous-layer".
|
||||||
|
- The composition diagram on slide 12 — this will take the most Tikz work.
|
||||||
|
|
||||||
|
### Figures that need to be created or dressed up for the talk
|
||||||
|
- Slide 1: control-room visual.
|
||||||
|
- Slide 2: discrete/continuous tools comparison.
|
||||||
|
- Slide 3: use `fret-pipeline/diagrams/PWR_HYBRID_3_DRC_states.png`.
|
||||||
|
- Slide 4: FRET → LTL → AIGER panel.
|
||||||
|
- Slide 5: 10-state coupling diagram.
|
||||||
|
- Slide 7, 10: reuse `docs/figures/reach_*_tubes.png`.
|
||||||
|
- Slide 8: bar chart (new, easy matplotlib).
|
||||||
|
- Slide 9: reuse `validate_pj_heatup.png`.
|
||||||
|
- Slide 11: latex-typeset polynomial + CSDP log snippet.
|
||||||
|
- Slide 12: composition diagram (Tikz, will take time).
|
||||||
|
|
||||||
|
### Cybersecurity angle to emphasize
|
||||||
|
- The point the OT audience will care about: this kind of verification
|
||||||
|
**constrains what the controller can physically do**. Even if an attacker
|
||||||
|
gets past authentication and can inject arbitrary commands, the DRC-plus-
|
||||||
|
reach-certified envelope limits how bad things can get *within the
|
||||||
|
physical plant*. That's a different assurance axis than the usual
|
||||||
|
comms-security one and complements it.
|
||||||
|
- Formal methods as defense-in-depth: they catch bugs *before* deployment,
|
||||||
|
which reduces attack surface more than any runtime defense.
|
||||||
|
- The PJ reduction + Tikhonov approach might be of interest for other
|
||||||
|
safety-critical stiff systems (power grid, aerospace).
|
||||||
|
|
||||||
|
### Things to NOT do
|
||||||
|
- Don't get lost in reactor-physics detail. One sentence per physical
|
||||||
|
concept; get them to the CS content fast.
|
||||||
|
- Don't show code unless it's a slide about *why* the code structure
|
||||||
|
matters. Code screenshots are terrible evidence.
|
||||||
|
- Don't oversell. Honest limitations at every stage builds trust with
|
||||||
|
a skeptical audience.
|
||||||
|
- Don't use more than 2 bullet points on any slide. Alley rules.
|
||||||
|
|
||||||
|
### Timing checkpoints
|
||||||
|
- Slide 6 (mode-obligation taxonomy) by minute 8.
|
||||||
|
- Slide 10 (first nonlinear reach result) by minute 13.
|
||||||
|
- Slide 12 (composition story) by minute 17.
|
||||||
|
- Slide 13 (limitations) by minute 19.
|
||||||
1
results/.gitignore
vendored
Normal file
1
results/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.mat
|
||||||
32
results/README.md
Normal file
32
results/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# results/
|
||||||
|
|
||||||
|
Output artifacts from reach / barrier / validation runs. All files
|
||||||
|
in this directory are **gitignored** — they're regenerated by running
|
||||||
|
the corresponding script.
|
||||||
|
|
||||||
|
## What lives here
|
||||||
|
|
||||||
|
Named `<analysis>_<variant>.mat` (MATLAB v7 format, read/written via
|
||||||
|
Julia's MAT.jl for historical convenience — switching to JLD2 is a
|
||||||
|
deferred item, see `journal/` for context).
|
||||||
|
|
||||||
|
Generated by (as of 2026-04-21):
|
||||||
|
|
||||||
|
| File | Generated by |
|
||||||
|
|---|---|
|
||||||
|
| `reach_operation_result.mat` | `code/scripts/reach/reach_operation.jl` |
|
||||||
|
| `reach_heatup_pj_tight_full.mat` | `code/scripts/reach/reach_heatup_pj_tight_full.jl` |
|
||||||
|
| `reach_scram_pj_result.mat` | `code/scripts/reach/reach_scram_pj.jl` |
|
||||||
|
|
||||||
|
Consumed by:
|
||||||
|
|
||||||
|
- `code/scripts/plot/plot_reach_tubes.jl` — tube overlay figures.
|
||||||
|
- `app/predicate_explorer.jl` — live per-halfspace margin rendering.
|
||||||
|
|
||||||
|
## Why this directory exists
|
||||||
|
|
||||||
|
`reachability/` used to hold both source-of-truth specs
|
||||||
|
(`predicates.json`, README, WALKTHROUGH.md) and ephemeral reach results.
|
||||||
|
Mixing stable vs.\ regenerated content made it hard to tell which was
|
||||||
|
which at a glance. Split: `reachability/` = specs + docs,
|
||||||
|
`results/` = outputs.
|
||||||
Loading…
x
Reference in New Issue
Block a user