Add mild thermal/measurement lag and expose margins on dashboard
This commit is contained in:
@@ -79,6 +79,10 @@ HX_FOULING_HEAL_RATE = 5e-6 # cleaning/settling when cool/low steam
|
|||||||
HX_FOULING_MAX_PENALTY = 0.25 # fractional UA loss cap
|
HX_FOULING_MAX_PENALTY = 0.25 # fractional UA loss cap
|
||||||
BORON_WORTH_PER_PPM = 8e-6 # delta rho per ppm relative to baseline boron
|
BORON_WORTH_PER_PPM = 8e-6 # delta rho per ppm relative to baseline boron
|
||||||
BORON_TRIM_RATE_PPM_PER_S = 0.02 # slow boron trim toward setpoint when near target
|
BORON_TRIM_RATE_PPM_PER_S = 0.02 # slow boron trim toward setpoint when near target
|
||||||
|
# Mild thermal/measurement lags
|
||||||
|
FUEL_TO_CLAD_TIME_CONSTANT = 0.3 # seconds (mild lag)
|
||||||
|
CLAD_TO_COOLANT_TIME_CONSTANT = 0.2 # seconds (mild lag)
|
||||||
|
POWER_MEASUREMENT_TIME_CONSTANT = 6.0 # seconds
|
||||||
# Threshold inventories (event counts) for flagging common poisons in diagnostics.
|
# Threshold inventories (event counts) for flagging common poisons in diagnostics.
|
||||||
KEY_POISON_THRESHOLDS = {
|
KEY_POISON_THRESHOLDS = {
|
||||||
"Xe": 1e20, # xenon
|
"Xe": 1e20, # xenon
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ class ControlSystem:
|
|||||||
if self._filtered_power_mw <= 0.0:
|
if self._filtered_power_mw <= 0.0:
|
||||||
self._filtered_power_mw = raw_power
|
self._filtered_power_mw = raw_power
|
||||||
if raw_power > 0.7 * self.setpoint_mw:
|
if raw_power > 0.7 * self.setpoint_mw:
|
||||||
alpha = clamp(dt / 6.0, 0.0, 1.0) # ~6s time constant
|
tau = constants.POWER_MEASUREMENT_TIME_CONSTANT
|
||||||
|
alpha = clamp(dt / max(1e-6, tau), 0.0, 1.0)
|
||||||
self._filtered_power_mw += alpha * (raw_power - self._filtered_power_mw)
|
self._filtered_power_mw += alpha * (raw_power - self._filtered_power_mw)
|
||||||
measured_power = self._filtered_power_mw
|
measured_power = self._filtered_power_mw
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -784,6 +784,15 @@ class ReactorDashboard:
|
|||||||
reliefs.append("Secondary")
|
reliefs.append("Secondary")
|
||||||
relief_attr = curses.color_pair(2) | curses.A_BOLD if reliefs else 0
|
relief_attr = curses.color_pair(2) | curses.A_BOLD if reliefs else 0
|
||||||
lines.append(("Relief valves", ", ".join(reliefs) if reliefs else "Closed", relief_attr))
|
lines.append(("Relief valves", ", ".join(reliefs) if reliefs else "Closed", relief_attr))
|
||||||
|
lines.append(("DNB margin", f"{state.core.dnb_margin:5.2f}" if state.core.dnb_margin is not None else "n/a"))
|
||||||
|
lines.append(("Subcooling", f"{state.core.subcooling_margin:5.1f} K" if state.core.subcooling_margin is not None else "n/a"))
|
||||||
|
lines.append(
|
||||||
|
(
|
||||||
|
"SG level",
|
||||||
|
f"{state.secondary_loop.level*100:5.1f}%",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
lines.append(("SG pressure", f"{state.secondary_loop.pressure:5.2f}/{constants.MAX_PRESSURE:4.1f} MPa"))
|
||||||
lines.append(
|
lines.append(
|
||||||
(
|
(
|
||||||
"SCRAM trips",
|
"SCRAM trips",
|
||||||
|
|||||||
@@ -114,8 +114,16 @@ class ThermalSolver:
|
|||||||
dt: float,
|
dt: float,
|
||||||
residual_power_mw: float | None = None,
|
residual_power_mw: float | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
def _lag(prev: float, new: float, tau: float) -> float:
|
||||||
|
if tau <= 0.0:
|
||||||
|
return new
|
||||||
|
alpha = min(1.0, max(0.0, dt / max(1e-6, tau)))
|
||||||
|
return prev + alpha * (new - prev)
|
||||||
|
|
||||||
if residual_power_mw is None:
|
if residual_power_mw is None:
|
||||||
residual_power_mw = power_mw
|
residual_power_mw = power_mw
|
||||||
|
prev_fuel = core.fuel_temperature
|
||||||
|
prev_clad = core.clad_temperature or primary.temperature_out
|
||||||
temp_rise = temperature_rise(power_mw, primary.mass_flow_rate)
|
temp_rise = temperature_rise(power_mw, primary.mass_flow_rate)
|
||||||
primary.temperature_out = primary.temperature_in + temp_rise
|
primary.temperature_out = primary.temperature_in + temp_rise
|
||||||
# Fuel heats from total fission power (even when most is convected) plus any residual left in the coolant.
|
# Fuel heats from total fission power (even when most is convected) plus any residual left in the coolant.
|
||||||
@@ -136,6 +144,9 @@ class ThermalSolver:
|
|||||||
# Keep temperatures bounded and never below coolant outlet.
|
# Keep temperatures bounded and never below coolant outlet.
|
||||||
core.fuel_temperature = min(core.fuel_temperature, constants.MAX_CORE_TEMPERATURE)
|
core.fuel_temperature = min(core.fuel_temperature, constants.MAX_CORE_TEMPERATURE)
|
||||||
core.clad_temperature = min(clad, constants.MAX_CORE_TEMPERATURE)
|
core.clad_temperature = min(clad, constants.MAX_CORE_TEMPERATURE)
|
||||||
|
# Apply mild lags so heat moves from fuel to clad to coolant over a short time constant.
|
||||||
|
core.fuel_temperature = _lag(prev_fuel, core.fuel_temperature, constants.FUEL_TO_CLAD_TIME_CONSTANT)
|
||||||
|
core.clad_temperature = _lag(prev_clad, core.clad_temperature or prev_clad, constants.CLAD_TO_COOLANT_TIME_CONSTANT)
|
||||||
core.subcooling_margin = max(0.0, saturation_temperature(primary.pressure) - primary.temperature_out)
|
core.subcooling_margin = max(0.0, saturation_temperature(primary.pressure) - primary.temperature_out)
|
||||||
chf = self._critical_heat_flux(primary)
|
chf = self._critical_heat_flux(primary)
|
||||||
heat_flux = (power_mw * constants.MEGAWATT) / max(1.0, self._core_surface_area())
|
heat_flux = (power_mw * constants.MEGAWATT) / max(1.0, self._core_surface_area())
|
||||||
|
|||||||
Reference in New Issue
Block a user