Add SCRAM matrix (DNB/subcool/SG limits) and clad split

This commit is contained in:
Codex Agent
2025-11-28 18:44:16 +01:00
parent 7ffb2a8ce0
commit def8ded816
4 changed files with 30 additions and 5 deletions

View File

@@ -6,5 +6,5 @@
- **Heat transfer**: steam-generator UA·ΔT_lm model with a pinch cap to keep the primary outlet hotter than the secondary, coolant heating uses total fission power with fuel heating decoupled from exchanger draw, and the secondary thermal solver includes passive cool-down plus steam-drum mass/energy balance with latent heat. - **Heat transfer**: steam-generator UA·ΔT_lm model with a pinch cap to keep the primary outlet hotter than the secondary, coolant heating uses total fission power with fuel heating decoupled from exchanger draw, and the secondary thermal solver includes passive cool-down plus steam-drum mass/energy balance with latent heat.
- **Pressurizer & inventory**: primary pressurizer trims toward 7 MPa with level tracking, loop inventories/levels steer flow availability, secondary steam boil-off draws down level with auto makeup, and pumps reduce flow/status to `CAV` when NPSH is insufficient. - **Pressurizer & inventory**: primary pressurizer trims toward 7 MPa with level tracking, loop inventories/levels steer flow availability, secondary steam boil-off draws down level with auto makeup, and pumps reduce flow/status to `CAV` when NPSH is insufficient.
- **Steam cycle**: three turbines with spool dynamics, throttle mapping, condenser vacuum/back-pressure with fouling and cooling sink temperature, load dispatch to consumer, steam quality gating for output, generator states with batteries/spool, and steam enthalpy-driven availability readout on the dashboard. - **Steam cycle**: three turbines with spool dynamics, throttle mapping, condenser vacuum/back-pressure with fouling and cooling sink temperature, load dispatch to consumer, steam quality gating for output, generator states with batteries/spool, and steam enthalpy-driven availability readout on the dashboard.
- **Protections & failures**: health monitor degrading components under stress, automatic SCRAM on core or heat-sink loss, relief valves per loop with venting/mass loss and pump pressure caps, maintenance actions to restore integrity. - **Protections & failures**: health monitor degrading components under stress, automatic SCRAM on core or heat-sink loss plus DNB/subcool and secondary level/pressure trips, relief valves per loop with venting/mass loss and pump pressure caps, maintenance actions to restore integrity.
- **Persistence & ops**: snapshots auto-save/load to `artifacts/last_state.json`; dashboard with live metrics, protections/warnings, heat-exchanger telemetry, component health, and control shortcuts. - **Persistence & ops**: snapshots auto-save/load to `artifacts/last_state.json`; dashboard with live metrics, protections/warnings, heat-exchanger telemetry, component health, and control shortcuts.

View File

@@ -761,6 +761,12 @@ class ReactorDashboard:
if self.reactor.secondary_relief_open: if self.reactor.secondary_relief_open:
reliefs.append("Secondary") reliefs.append("Secondary")
lines.append(("Relief valves", ", ".join(reliefs) if reliefs else "Closed")) lines.append(("Relief valves", ", ".join(reliefs) if reliefs else "Closed"))
lines.append(
(
"SCRAM trips",
"DNB<0.5 | Subcool<2K | SG lvl<5/>98% | SG P>15.2MPa",
)
)
return lines return lines
def _steam_available_power(self, state: PlantState) -> float: def _steam_available_power(self, state: PlantState) -> float:

View File

@@ -435,12 +435,25 @@ class Reactor:
if (not self.secondary_pump_active or state.secondary_loop.mass_flow_rate <= 1.0) and total_power > 50.0: if (not self.secondary_pump_active or state.secondary_loop.mass_flow_rate <= 1.0) and total_power > 50.0:
self._handle_heat_sink_loss(state) self._handle_heat_sink_loss(state)
if state.core.dnb_margin is not None and state.core.dnb_margin < 0.3: # SCRAM matrix: DNB, subcooling, steam generator level/pressure
if state.core.dnb_margin is not None and state.core.dnb_margin < 0.5:
LOGGER.critical("DNB margin low: %.2f, initiating SCRAM", state.core.dnb_margin) LOGGER.critical("DNB margin low: %.2f, initiating SCRAM", state.core.dnb_margin)
self.shutdown = True self.shutdown = True
self.control.scram() self.control.scram()
if state.core.subcooling_margin is not None and state.core.subcooling_margin < 5.0: if state.core.subcooling_margin is not None and state.core.subcooling_margin < 2.0:
LOGGER.critical("Subcooling margin lost: %.1fK, initiating SCRAM", state.core.subcooling_margin)
self.shutdown = True
self.control.scram()
elif state.core.subcooling_margin is not None and state.core.subcooling_margin < 5.0:
LOGGER.warning("Subcooling margin low: %.1fK", state.core.subcooling_margin) LOGGER.warning("Subcooling margin low: %.1fK", state.core.subcooling_margin)
if state.secondary_loop.level < 0.05 or state.secondary_loop.level > 0.98:
LOGGER.critical("Secondary level out of bounds (%.1f%%), initiating SCRAM", state.secondary_loop.level * 100)
self.shutdown = True
self.control.scram()
if state.secondary_loop.pressure > 0.95 * constants.MAX_PRESSURE:
LOGGER.critical("Secondary pressure high (%.2f MPa), initiating SCRAM", state.secondary_loop.pressure)
self.shutdown = True
self.control.scram()
failures = self.health_monitor.evaluate( failures = self.health_monitor.evaluate(
state, state,

View File

@@ -119,9 +119,15 @@ class ThermalSolver:
heating = (0.002 * max(0.0, power_mw) + 0.01 * max(0.0, residual_power_mw)) * dt heating = (0.002 * max(0.0, power_mw) + 0.01 * max(0.0, residual_power_mw)) * dt
cooling = 0.025 * max(0.0, core.fuel_temperature - primary.temperature_out) * dt cooling = 0.025 * max(0.0, core.fuel_temperature - primary.temperature_out) * dt
core.fuel_temperature += heating - cooling core.fuel_temperature += heating - cooling
# Simple clad/fuel split: clad lags fuel and is cooled by coolant.
clad = core.clad_temperature or primary.temperature_out
conduction = 0.02 * max(0.0, core.fuel_temperature - clad) * dt
clad_cooling = 0.05 * max(0.0, clad - primary.temperature_out) * dt
clad = max(primary.temperature_out, clad + conduction - clad_cooling)
core.fuel_temperature = max(primary.temperature_out, core.fuel_temperature - conduction)
# Keep fuel temperature bounded and never below the coolant outlet temperature. # Keep fuel temperature bounded and never below the coolant outlet temperature.
core.fuel_temperature = min(max(primary.temperature_out, core.fuel_temperature), constants.MAX_CORE_TEMPERATURE) core.fuel_temperature = min(core.fuel_temperature, constants.MAX_CORE_TEMPERATURE)
core.clad_temperature = max(primary.temperature_out, core.clad_temperature or primary.temperature_out) core.clad_temperature = min(clad, constants.MAX_CORE_TEMPERATURE)
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())