Add chemistry-driven fouling and HX/condenser penalties

This commit is contained in:
Codex Agent
2025-11-28 20:04:33 +01:00
parent a2afbabe49
commit d4ed590b0d
9 changed files with 103 additions and 7 deletions

View File

@@ -397,7 +397,12 @@ class Reactor:
if not self.secondary_pump_active or state.secondary_loop.mass_flow_rate <= 1.0:
transferred = 0.0
else:
transferred = heat_transfer(state.primary_loop, state.secondary_loop, total_power)
transferred = heat_transfer(
state.primary_loop,
state.secondary_loop,
total_power,
fouling_factor=getattr(state, "hx_fouling", 0.0),
)
residual = max(0.0, total_power - transferred)
self.thermal.step_core(state.core, state.primary_loop, total_power, dt, residual_power_mw=residual)
self.thermal.step_secondary(state.secondary_loop, transferred, dt)
@@ -425,6 +430,7 @@ class Reactor:
self.control.safety_backoff(state.core.subcooling_margin, state.core.dnb_margin, dt)
self._apply_secondary_boiloff(state, dt)
self._update_secondary_level(state, dt)
self._update_chemistry(state, dt)
steam_draw = self._step_turbine_bank(state, transferred, dt)
if steam_draw > 0.0:
@@ -652,6 +658,36 @@ class Reactor:
loop.level = min(1.2, max(0.0, loop.inventory_kg / nominal_mass))
self._last_steam_out_kg_s = steam_out
def _update_chemistry(self, state: PlantState, dt: float) -> None:
"""Track dissolved species and fouling impacts on HX and condenser."""
env = constants.ENVIRONMENT_TEMPERATURE
steam_out = state.secondary_loop.mass_flow_rate * max(0.0, state.secondary_loop.steam_quality)
temp = state.secondary_loop.temperature_out
temp_factor = max(0.0, (temp - env) / 300.0)
impurity_load = max(0.0, state.dissolved_oxygen_ppm + 0.5 * state.sodium_ppm)
fouling_rate = constants.HX_FOULING_RATE * temp_factor * impurity_load
heal = constants.HX_FOULING_HEAL_RATE * (1.0 if steam_out < 200.0 or temp_factor < 0.2 else 0.0)
state.hx_fouling = max(
0.0,
min(constants.HX_FOULING_MAX_PENALTY, state.hx_fouling + (fouling_rate - heal) * dt),
)
# Degas oxygen with steam production; small impurity ingress over time (worse when venting).
degas = 0.0005 * steam_out * dt / max(1.0, constants.SECONDARY_LOOP_VOLUME_M3)
state.dissolved_oxygen_ppm = max(0.0, state.dissolved_oxygen_ppm - degas)
ingress = (0.01 if self.secondary_relief_open else 0.002) * dt
state.sodium_ppm = min(constants.CHEM_MAX_PPM, state.sodium_ppm + ingress)
state.boron_ppm = max(0.0, state.boron_ppm - 0.001 * dt)
chem_penalty = constants.CONDENSER_CHEM_FOULING_RATE * impurity_load / 1_000.0
for turb_state in state.turbines:
turb_state.fouling_penalty = min(
constants.CONDENSER_FOULING_MAX_PENALTY,
max(0.0, turb_state.fouling_penalty + chem_penalty * dt),
)
backpressure = constants.CONDENSER_CHEM_BACKPRESSURE_FACTOR * impurity_load * dt
turb_state.condenser_pressure = min(
constants.CONDENSER_MAX_PRESSURE_MPA, turb_state.condenser_pressure + backpressure
)
def _inventory_flow_scale(self, loop: CoolantLoopState) -> float:
if loop.level <= constants.LOW_LEVEL_FLOW_FLOOR:
return 0.0