predicates.json is the single source of truth for concretizing the
FRET-spec predicates (t_avg_above_min, t_avg_in_range, p_above_crit,
inv1_holds, inv2_holds) as polytopes {x : A x <= b}. Until now these
were abstract booleans in the synthesis spec; reach analysis
re-invented ad-hoc thresholds that weren't tied to the spec. Closes
the Thrust-1-meets-Thrust-3 seam.
T_standby now defined as T_c0 - 60 F = 275 C (from user review).
Replaces the earlier simplification where shutdown IC held all temps
at T_cold0. 275 C is inside the model's +/-50 C trust region around
operating point and above coolant saturation at reduced pressure.
load_predicates.m in MATLAB reads the JSON and resolves rhs_expr
strings (which reference plant-derived constants like T_c0, T_cold0,
T_standby) into numeric bounds. Returns per-predicate (A_poly, b_poly)
plus a constants struct.
main_mode_sweep.m now pulls T_standby from predicates and uses it
for shutdown + heatup ICs. Heatup horizon extended to 90 min to
cover the wider 60 F -> operating range at 28 C/hr tech-spec limit.
reach_operation.m reads delta_safe_Tc from the t_avg_in_range
halfspace instead of hardcoding +/-5 K. Current concretization is
+/-2.78 C (~5 F); LQR reach still shows 28x margin.
inv1_holds and inv2_holds are marked PLACEHOLDER in the JSON —
engineering best guesses, not derived from a specific plant's tech
specs or a DNBR correlation. Revisit before thesis defense.
Hacker-Split: single-source concretization for FRET predicates,
end seam with reach.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
139 lines
6.6 KiB
Matlab
139 lines
6.6 KiB
Matlab
%% main_mode_sweep.m — demo each DRC continuous-mode controller
|
|
%
|
|
% One scenario per mode, from an initial condition plausible for that
|
|
% mode. Figures saved to ../docs/figures/mode_sweep_*.png so the runs
|
|
% are reviewable without the MATLAB IDE.
|
|
%
|
|
% Modes, in the order DRC visits them starting from shutdown:
|
|
% 1. shutdown — deep subcritical, cold IC, Q_sg = 0
|
|
% 2. heatup — same cold IC, ramped T_avg reference
|
|
% 3. operation — operating IC, 100% -> 80% Q_sg step
|
|
% 4. scram — operating IC, rods slammed in, Q_sg decays to 0
|
|
%
|
|
% Each run produces plot_pke_results 4-panel plus saves a lightweight
|
|
% summary figure.
|
|
|
|
clear; clc; close all;
|
|
addpath('controllers');
|
|
addpath(fullfile('..', 'reachability')); % for load_predicates
|
|
|
|
plant = pke_params();
|
|
pred = load_predicates(plant); % T_standby and FRET-predicate concretizations
|
|
figdir = fullfile('..', 'docs', 'figures');
|
|
if ~exist(figdir, 'dir'), mkdir(figdir); end
|
|
|
|
CtoF = @(T) T*9/5 + 32;
|
|
|
|
%% ===== IC helpers =====
|
|
% Operating IC: full-power steady state.
|
|
x0_op = pke_initial_conditions(plant);
|
|
|
|
% Hot-standby shutdown IC: coolant flat at T_standby = T_c0 - 60 F ~ 275 C,
|
|
% reactor deep subcritical with only a trace neutron population. T_standby
|
|
% comes from reachability/predicates.json (single source of truth). Well
|
|
% inside the model's +/-50 C trust region around the operating point, and
|
|
% above coolant saturation at reduced operating pressure.
|
|
T_standby = pred.constants.T_standby;
|
|
n_shut = 1e-6;
|
|
C_shut = (plant.beta_i ./ (plant.lambda_i * plant.Lambda)) * n_shut;
|
|
x0_shut = [n_shut; C_shut; T_standby; T_standby; T_standby];
|
|
|
|
% Heatup IC: reactor already taken critical at 0.1% power at hot-standby
|
|
% temperature. Mirrors real plant practice: achieve criticality, then
|
|
% heat up. Same T_standby as the shutdown IC — heatup begins from where
|
|
% shutdown left off.
|
|
n_heat = 1e-3;
|
|
C_heat = (plant.beta_i ./ (plant.lambda_i * plant.Lambda)) * n_heat;
|
|
x0_heat = [n_heat; C_heat; T_standby; T_standby; T_standby];
|
|
|
|
%% ===== Mode 1: SHUTDOWN =====
|
|
fprintf('\n===== Mode 1: ctrl_shutdown =====\n');
|
|
Q_sg_shut = @(t) 0; % no SG demand
|
|
tspan_shut = [0, 600];
|
|
[t1, X1, U1] = pke_solver(plant, Q_sg_shut, @ctrl_shutdown, [], tspan_shut, x0_shut);
|
|
|
|
%% ===== Mode 2: HEATUP =====
|
|
fprintf('\n===== Mode 2: ctrl_heatup =====\n');
|
|
ref_heatup = struct();
|
|
ref_heatup.T_start = T_standby; % ~275 C (hot standby, = T_c0 - 60 F)
|
|
ref_heatup.T_target = plant.T_c0; % 308.35 C (operating setpoint)
|
|
ref_heatup.ramp_rate = 28 / 3600; % 28 C/hr, tech-spec limit
|
|
Q_sg_heat = @(t) 0; % no SG demand during heatup
|
|
tspan_heat = [0, 5400]; % ~90 min (33 C span at 28 C/hr = 71 min + settling)
|
|
[t2, X2, U2] = pke_solver(plant, Q_sg_heat, @ctrl_heatup, ref_heatup, tspan_heat, x0_heat);
|
|
|
|
%% ===== Mode 3a: OPERATION (plain P) =====
|
|
fprintf('\n===== Mode 3a: ctrl_operation (P) =====\n');
|
|
Q_sg_op = @(t) plant.P0 * (1.0 - 0.2 * (t >= 30)); % 100% -> 80% step
|
|
ref_op = struct('T_avg', plant.T_c0);
|
|
tspan_op = [0, 600];
|
|
[t3, X3, U3] = pke_solver(plant, Q_sg_op, @ctrl_operation, ref_op, tspan_op, x0_op);
|
|
|
|
%% ===== Mode 3b: OPERATION (LQR) =====
|
|
fprintf('\n===== Mode 3b: ctrl_operation_lqr =====\n');
|
|
[t3b, X3b, U3b] = pke_solver(plant, Q_sg_op, @ctrl_operation_lqr, [], tspan_op, x0_op);
|
|
|
|
%% ===== Mode 4: SCRAM =====
|
|
fprintf('\n===== Mode 4: ctrl_scram =====\n');
|
|
% After a scram signal, the turbine trips and SG isolation occurs fast.
|
|
% Q_sg drops from P0 to a decay-heat-level sink (3% of P0) in ~10 s,
|
|
% then holds. Not a true decay-heat model — good enough to show the
|
|
% post-scram thermal trajectory without coolant going unphysically cold.
|
|
Q_sg_scr = @(t) plant.P0 * max(0.03, 1 - max(0, t - 10) / 10);
|
|
tspan_scr = [0, 600];
|
|
[t4, X4, U4] = pke_solver(plant, Q_sg_scr, @ctrl_scram, [], tspan_scr, x0_op);
|
|
|
|
%% ===== Per-mode 4-panel plots =====
|
|
plot_pke_results(t1, X1, U1, plant, Q_sg_shut, 'ctrl\_shutdown (cold IC)');
|
|
exportgraphics(gcf, fullfile(figdir, 'mode_sweep_1_shutdown.png'), 'Resolution', 150);
|
|
|
|
plot_pke_results(t2, X2, U2, plant, Q_sg_heat, 'ctrl\_heatup (ramp T\_avg)');
|
|
exportgraphics(gcf, fullfile(figdir, 'mode_sweep_2_heatup.png'), 'Resolution', 150);
|
|
|
|
plot_pke_results(t3, X3, U3, plant, Q_sg_op, 'ctrl\_operation (P on T\_avg)');
|
|
exportgraphics(gcf, fullfile(figdir, 'mode_sweep_3_operation.png'), 'Resolution', 150);
|
|
|
|
plot_pke_results(t3b, X3b, U3b, plant, Q_sg_op, 'ctrl\_operation\_lqr');
|
|
exportgraphics(gcf, fullfile(figdir, 'mode_sweep_3b_operation_lqr.png'), 'Resolution', 150);
|
|
|
|
plot_pke_results(t4, X4, U4, plant, Q_sg_scr, 'ctrl\_scram');
|
|
exportgraphics(gcf, fullfile(figdir, 'mode_sweep_4_scram.png'), 'Resolution', 150);
|
|
|
|
%% ===== Heatup ramp-tracking figure =====
|
|
% Overlay the T_ref signal on T_avg so the lag is visible.
|
|
T_ref_trace = min(ref_heatup.T_start + ref_heatup.ramp_rate .* t2, ref_heatup.T_target);
|
|
figure('Position', [100 80 900 400], 'Name', 'Heatup ramp tracking');
|
|
plot(t2/60, CtoF(T_ref_trace), 'k--', 'LineWidth', 1.2); hold on;
|
|
plot(t2/60, CtoF(X2(:,9)), 'r-', 'LineWidth', 1.5);
|
|
xlabel('Time [min]'); ylabel('T_{avg} [F]');
|
|
legend('T_{ref}(t) (28 C/hr ramp)', 'T_{avg} (plant)', 'Location', 'southeast');
|
|
title('ctrl\_heatup: reference tracking');
|
|
grid on;
|
|
exportgraphics(gcf, fullfile(figdir, 'mode_sweep_heatup_tracking.png'), 'Resolution', 150);
|
|
|
|
%% ===== P vs LQR head-to-head on T_avg =====
|
|
figure('Position', [100 80 900 400], 'Name', 'Operation: P vs LQR');
|
|
plot(t3, CtoF(X3(:,9)), 'b-', 'LineWidth', 1.5); hold on;
|
|
plot(t3b, CtoF(X3b(:,9)), 'r-', 'LineWidth', 1.5);
|
|
yline(CtoF(plant.T_c0), 'k--');
|
|
xlabel('Time [s]'); ylabel('T_{avg} [F]');
|
|
legend('ctrl\_operation (P)', 'ctrl\_operation\_lqr', 'setpoint', 'Location', 'best');
|
|
title('Operation mode: P vs LQR under 100% \rightarrow 80% Q_{sg} step');
|
|
grid on;
|
|
exportgraphics(gcf, fullfile(figdir, 'mode_sweep_op_P_vs_LQR.png'), 'Resolution', 150);
|
|
|
|
%% ===== Summary figure: normalized power across all modes =====
|
|
figure('Position', [100 80 1000 450], 'Name', 'Normalized power, all modes');
|
|
subplot(2,2,1); semilogy(t1, max(X1(:,1), 1e-20)); grid on;
|
|
title('Shutdown'); xlabel('t [s]'); ylabel('n (log)');
|
|
subplot(2,2,2); plot(t2/60, X2(:,1)); grid on;
|
|
title('Heatup'); xlabel('t [min]'); ylabel('n');
|
|
subplot(2,2,3); plot(t3, X3(:,1)); grid on;
|
|
title('Operation'); xlabel('t [s]'); ylabel('n');
|
|
subplot(2,2,4); semilogy(t4, max(X4(:,1), 1e-20)); grid on;
|
|
title('Scram'); xlabel('t [s]'); ylabel('n (log)');
|
|
sgtitle('Normalized reactor power n(t) across DRC modes');
|
|
exportgraphics(gcf, fullfile(figdir, 'mode_sweep_power_overview.png'), 'Resolution', 150);
|
|
|
|
fprintf('\n=== Figures written to %s ===\n', figdir);
|