Compare commits

...

3 Commits

Author SHA1 Message Date
Codex Agent
4162ecf712 Add phased plan for realistic steam/enthalpy modeling 2025-11-24 23:23:01 +01:00
Codex Agent
bf744a07a5 Use higher secondary pump demand to avoid meltdown 2025-11-24 22:48:07 +01:00
Codex Agent
afa8997614 Reduce secondary pump head and ease secondary heating 2025-11-24 22:41:36 +01:00
4 changed files with 21 additions and 4 deletions

View File

@@ -7,3 +7,8 @@
- [ ] Introduce CHF/DNB margin, clad/fuel split temps, and SCRAM matrix for subcooling loss or SG level/pressure trips. - [ ] Introduce CHF/DNB margin, clad/fuel split temps, and SCRAM matrix for subcooling loss or SG level/pressure trips.
- [ ] Flesh out condenser behavior: vacuum pump limits, cooling water temperature coupling, and dynamic back-pressure with fouling. - [ ] Flesh out condenser behavior: vacuum pump limits, cooling water temperature coupling, and dynamic back-pressure with fouling.
- [ ] Dashboard polish: compact turbine/generator rows, color critical warnings (SCRAM/heat-sink), and reduce repeated log noise. - [ ] Dashboard polish: compact turbine/generator rows, color critical warnings (SCRAM/heat-sink), and reduce repeated log noise.
- [ ] Incremental realism plan:
- Add stored enthalpy for primary/secondary loops and a steam-drum mass/energy balance (sensible + latent) while keeping existing pump logic and tests passing.
- Adjust HX/pressure handling to use stored energy (saturation clamp and pressure rise) and validate steam formation with both pumps at ~3 GW.
- Update turbine power mapping to consume steam enthalpy/quality and align protection trips with real steam presence.
- Add integration test: cold start → gens/pumps 2/2 → ramp to ~3 GW → confirm steam quality threshold → enable all turbines and require electrical output.

View File

@@ -24,7 +24,7 @@ MEV_TO_J = 1.602_176_634e-13
ELECTRON_FISSION_CROSS_SECTION = 5e-16 # cm^2, tuned for simulation scale ELECTRON_FISSION_CROSS_SECTION = 5e-16 # cm^2, tuned for simulation scale
PUMP_SPOOL_TIME = 5.0 # seconds to reach commanded flow PUMP_SPOOL_TIME = 5.0 # seconds to reach commanded flow
PRIMARY_PUMP_SHUTOFF_HEAD_MPA = 8.0 # approximate shutoff head for primary pumps PRIMARY_PUMP_SHUTOFF_HEAD_MPA = 8.0 # approximate shutoff head for primary pumps
SECONDARY_PUMP_SHUTOFF_HEAD_MPA = 7.0 SECONDARY_PUMP_SHUTOFF_HEAD_MPA = 3.0
TURBINE_SPOOL_TIME = 12.0 # seconds to reach steady output TURBINE_SPOOL_TIME = 12.0 # seconds to reach steady output
# Turbine/condenser parameters # Turbine/condenser parameters

View File

@@ -324,7 +324,8 @@ class Reactor:
pump_state.status = "STOPPING" if pump_state.flow_rate > 0.1 else "OFF" pump_state.status = "STOPPING" if pump_state.flow_rate > 0.1 else "OFF"
if self.secondary_pump_active: if self.secondary_pump_active:
total_flow = 0.0 total_flow = 0.0
base_flow, base_head = self.secondary_pump.performance(0.75) demand = 0.75
base_flow, base_head = self.secondary_pump.performance(demand)
target_pressure = max(0.5, base_head * power_ratio) target_pressure = max(0.5, base_head * power_ratio)
loop_pressure = max(0.1, saturation_pressure(state.secondary_loop.temperature_out)) loop_pressure = max(0.1, saturation_pressure(state.secondary_loop.temperature_out))
target_flow = base_flow * power_ratio target_flow = base_flow * power_ratio
@@ -428,8 +429,10 @@ class Reactor:
) )
state.secondary_loop.temperature_in = state.secondary_loop.temperature_out state.secondary_loop.temperature_in = state.secondary_loop.temperature_out
else: else:
secondary_cooling = max(0.0, state.secondary_loop.temperature_out - env - 40.0) # Allow the secondary to retain more heat so it can approach saturation and form steam.
state.secondary_loop.temperature_in = max(env, state.secondary_loop.temperature_out - max(20.0, secondary_cooling)) excess = max(0.0, state.secondary_loop.temperature_out - env)
cooling_drop = min(40.0, max(10.0, 0.2 * excess))
state.secondary_loop.temperature_in = max(env, state.secondary_loop.temperature_out - cooling_drop)
state.primary_to_secondary_delta_t = max(0.0, state.primary_loop.temperature_out - state.secondary_loop.temperature_in) state.primary_to_secondary_delta_t = max(0.0, state.primary_loop.temperature_out - state.secondary_loop.temperature_in)
state.heat_exchanger_efficiency = 0.0 if total_power <= 0 else min(1.0, max(0.0, transferred / max(1e-6, total_power))) state.heat_exchanger_efficiency = 0.0 if total_power <= 0 else min(1.0, max(0.0, transferred / max(1e-6, total_power)))

View File

@@ -18,12 +18,17 @@ class CoreState:
reactivity_margin: float # delta rho reactivity_margin: float # delta rho
power_output_mw: float # MW thermal power_output_mw: float # MW thermal
burnup: float # fraction of fuel consumed burnup: float # fraction of fuel consumed
clad_temperature: float | None = None # Kelvin
xenon_inventory: float = 0.0 xenon_inventory: float = 0.0
iodine_inventory: float = 0.0 iodine_inventory: float = 0.0
delayed_precursors: list[float] = field(default_factory=list) delayed_precursors: list[float] = field(default_factory=list)
fission_product_inventory: dict[str, float] = field(default_factory=dict) fission_product_inventory: dict[str, float] = field(default_factory=dict)
emitted_particles: dict[str, float] = field(default_factory=dict) emitted_particles: dict[str, float] = field(default_factory=dict)
def __post_init__(self) -> None:
if self.clad_temperature is None:
self.clad_temperature = self.fuel_temperature
def update_burnup(self, dt: float) -> None: def update_burnup(self, dt: float) -> None:
produced_energy_mwh = self.power_output_mw * (dt / 3600.0) produced_energy_mwh = self.power_output_mw * (dt / 3600.0)
self.burnup = clamp(self.burnup + produced_energy_mwh * 1e-5, 0.0, 0.99) self.burnup = clamp(self.burnup + produced_energy_mwh * 1e-5, 0.0, 0.99)
@@ -111,6 +116,10 @@ class PlantState:
core_blob = dict(data["core"]) core_blob = dict(data["core"])
inventory = core_blob.pop("fission_product_inventory", {}) inventory = core_blob.pop("fission_product_inventory", {})
particles = core_blob.pop("emitted_particles", {}) particles = core_blob.pop("emitted_particles", {})
# Backwards/forwards compatibility for optional core fields.
core_blob.pop("dnb_margin", None)
core_blob.pop("subcooling_margin", None)
core_blob.setdefault("clad_temperature", core_blob.get("fuel_temperature", 295.0))
turbines_blob = data.get("turbines") turbines_blob = data.get("turbines")
if turbines_blob is None: if turbines_blob is None:
# Compatibility with previous single-turbine snapshots. # Compatibility with previous single-turbine snapshots.