predicates: add heatup rate invariant as a linear halfspace
Earlier placeholder claimed ramp-rate limits weren't expressible as
state halfspaces without augmentation. That was wrong: dT_c/dt is
linear in (T_f, T_c, T_cold) directly from pke_th_rhs (no neutronics
coupling), so |dT_c/dt| <= r_max is two clean halfspaces over x.
Coefficients from pke_params:
a_f = hA / (M_c*c_c) = +0.4587 /s
a_c = -(hA + 2*W*c_c)/(M_c*c_c) = -0.9587 /s
a_cold = 2*W*c_c / (M_c*c_c) = +0.5000 /s
Sum = 0 exact (equilibrium when all T's equal).
Limit chosen: +/- 50 C/hr (tech-spec 28 C/hr + transient overshoot
budget). Verified on actual heatup sim: max dT_c/dt = 48.5 C/hr, min
= 0 C/hr. Passes our placeholder but tight — a strict 28 C/hr tech-
spec invariant would be violated by current ctrl_heatup tuning
(overshoot factor ~1.7x during mid-ramp).
Generalized load_predicates.m to accept multi-coefficient halfspace
rows via "row": [[state_idx, coeff], ...] format, in addition to the
existing single-coefficient {state_index, coeff} form. Backward
compatible.
inv1_holds now conjoins fuel_centerline, cold_leg_subcooled, and the
two rate halfspaces. DNBR still not modeled (would need an
augmented predicate with a correlation-based safety margin).
Hacker-Split: Dane asked about heatup rate invariant; realizing
my earlier 'needs state augmentation' claim was wrong and the rate
constraint is already linear. Fix it, verify against actual sim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dc4cfed61a
commit
72143bcff5
20
plant-model/test_heatup_rate.m
Normal file
20
plant-model/test_heatup_rate.m
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
%% test_heatup_rate.m — measure the actual heatup-rate trajectory from ctrl_heatup
|
||||||
|
clear; addpath('controllers'); addpath('../reachability');
|
||||||
|
plant = pke_params();
|
||||||
|
pred = load_predicates(plant);
|
||||||
|
x0 = [1e-3; (plant.beta_i ./ (plant.lambda_i * plant.Lambda)) * 1e-3; ...
|
||||||
|
pred.constants.T_standby; pred.constants.T_standby; pred.constants.T_standby];
|
||||||
|
ref = struct('T_start', pred.constants.T_standby, ...
|
||||||
|
'T_target', plant.T_c0, ...
|
||||||
|
'ramp_rate', 28/3600);
|
||||||
|
[t, X, U] = pke_solver(plant, @(t) 0, @ctrl_heatup, ref, [0 5400], x0);
|
||||||
|
|
||||||
|
a_f = 0.4587; a_c = -0.9587; a_cold = 0.5000;
|
||||||
|
dTcdt = a_f*X(:,8) + a_c*X(:,9) + a_cold*X(:,10); % C/s
|
||||||
|
|
||||||
|
fprintf('\n=== Heatup rate statistics ===\n');
|
||||||
|
fprintf(' max dT_c/dt: %.4f C/s = %6.1f C/hr\n', max(dTcdt), max(dTcdt)*3600);
|
||||||
|
fprintf(' min dT_c/dt: %.4f C/s = %6.1f C/hr\n', min(dTcdt), min(dTcdt)*3600);
|
||||||
|
fprintf(' rate limit: +/- 0.01389 C/s = +/- 50.0 C/hr\n');
|
||||||
|
fprintf(' violates upper? %s\n', string(any(dTcdt > 0.01389)));
|
||||||
|
fprintf(' violates lower? %s\n', string(any(dTcdt < -0.01389)));
|
||||||
@ -93,7 +93,18 @@ function group_out = parse_group(group_in, T_c0, T_f0, T_cold0, T_standby)
|
|||||||
b_poly = zeros(n_hs, 1);
|
b_poly = zeros(n_hs, 1);
|
||||||
for i = 1:n_hs
|
for i = 1:n_hs
|
||||||
hs = get_hs(i);
|
hs = get_hs(i);
|
||||||
|
if isfield(hs, 'row')
|
||||||
|
% Multi-coefficient halfspace: hs.row is a k x 2 matrix
|
||||||
|
% where each row is [state_index, coeff] (jsondecode from
|
||||||
|
% JSON array-of-arrays).
|
||||||
|
coeffs = hs.row;
|
||||||
|
for r = 1:size(coeffs, 1)
|
||||||
|
A_poly(i, coeffs(r, 1)) = coeffs(r, 2);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
% Single-coefficient halfspace: hs.state_index + hs.coeff.
|
||||||
A_poly(i, hs.state_index) = hs.coeff;
|
A_poly(i, hs.state_index) = hs.coeff;
|
||||||
|
end
|
||||||
b_poly(i) = evalin_context(hs.rhs_expr, T_c0, T_f0, T_cold0, T_standby);
|
b_poly(i) = evalin_context(hs.rhs_expr, T_c0, T_f0, T_cold0, T_standby);
|
||||||
end
|
end
|
||||||
group_out.(name).A_poly = A_poly;
|
group_out.(name).A_poly = A_poly;
|
||||||
|
|||||||
@ -110,6 +110,20 @@
|
|||||||
"halfspaces": [
|
"halfspaces": [
|
||||||
{ "state_index": 10, "coeff": 1.0, "rhs_expr": "T_cold0 + 15.0" }
|
{ "state_index": 10, "coeff": 1.0, "rhs_expr": "T_cold0 + 15.0" }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"heatup_rate_upper": {
|
||||||
|
"meaning": "Coolant heatup rate does not exceed tech-spec limit + overshoot margin.",
|
||||||
|
"concretization": "dT_c/dt = a_f*T_f + a_c*T_c + a_cold*T_cold <= 0.01389 C/s (50 C/hr; tech-spec 28 C/hr + transient overshoot budget)",
|
||||||
|
"_derivation": "dT_c/dt is linear in (T_f, T_c, T_cold) from pke_th_rhs.m: a_f=hA/(M_c*c_c)=+0.4587/s, a_c=-(hA+2*W*c_c)/(M_c*c_c)=-0.9587/s, a_cold=2*W*c_c/(M_c*c_c)=+0.5000/s. Coefficients sum to zero by construction (equilibrium when all T's equal).",
|
||||||
|
"halfspaces": [
|
||||||
|
{ "row": [[8, 0.4587], [9, -0.9587], [10, 0.5000]], "rhs_expr": "0.01389" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"heatup_rate_lower": {
|
||||||
|
"meaning": "Coolant cooldown rate during heatup doesn't exceed -50 C/hr (flag runaway cooldown).",
|
||||||
|
"halfspaces": [
|
||||||
|
{ "row": [[8, -0.4587], [9, 0.9587], [10, -0.5000]], "rhs_expr": "0.01389" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -120,8 +134,13 @@
|
|||||||
],
|
],
|
||||||
"inv1_holds": {
|
"inv1_holds": {
|
||||||
"meaning": "Heatup mode safety envelope.",
|
"meaning": "Heatup mode safety envelope.",
|
||||||
"conjunction_of": ["fuel_centerline", "cold_leg_subcooled"],
|
"conjunction_of": [
|
||||||
"_placeholder_note": "Ramp-rate limit and DNBR not expressible as state halfspaces without augmentation."
|
"fuel_centerline",
|
||||||
|
"cold_leg_subcooled",
|
||||||
|
"heatup_rate_upper",
|
||||||
|
"heatup_rate_lower"
|
||||||
|
],
|
||||||
|
"_note": "dT_c/dt is linear in (T_f, T_c, T_cold), so ramp-rate halfspace has 3 nonzero coefficients per row. DNBR not modeled — would need an augmented correlation-based predicate."
|
||||||
},
|
},
|
||||||
"inv2_holds": {
|
"inv2_holds": {
|
||||||
"meaning": "Operation mode safety envelope.",
|
"meaning": "Operation mode safety envelope.",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user