Add mild thermal/measurement lag and expose margins on dashboard

This commit is contained in:
Codex Agent
2025-11-28 22:51:43 +01:00
parent a600b59809
commit 12696b107f
4 changed files with 26 additions and 1 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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",

View File

@@ -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())