Add SCRAM matrix (DNB/subcool/SG limits) and clad split
This commit is contained in:
@@ -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.
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
@@ -761,6 +761,12 @@ class ReactorDashboard:
|
||||
if self.reactor.secondary_relief_open:
|
||||
reliefs.append("Secondary")
|
||||
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
|
||||
|
||||
def _steam_available_power(self, state: PlantState) -> float:
|
||||
|
||||
@@ -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:
|
||||
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)
|
||||
self.shutdown = True
|
||||
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)
|
||||
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(
|
||||
state,
|
||||
|
||||
@@ -119,9 +119,15 @@ class ThermalSolver:
|
||||
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
|
||||
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.
|
||||
core.fuel_temperature = min(max(primary.temperature_out, core.fuel_temperature), constants.MAX_CORE_TEMPERATURE)
|
||||
core.clad_temperature = max(primary.temperature_out, core.clad_temperature or primary.temperature_out)
|
||||
core.fuel_temperature = min(core.fuel_temperature, constants.MAX_CORE_TEMPERATURE)
|
||||
core.clad_temperature = min(clad, constants.MAX_CORE_TEMPERATURE)
|
||||
core.subcooling_margin = max(0.0, saturation_temperature(primary.pressure) - primary.temperature_out)
|
||||
chf = self._critical_heat_flux(primary)
|
||||
heat_flux = (power_mw * constants.MEGAWATT) / max(1.0, self._core_surface_area())
|
||||
|
||||
Reference in New Issue
Block a user