From 79f83c56d24c74757b3ec187a095309874880338 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Wed, 26 Nov 2025 23:03:58 +0100 Subject: [PATCH] Add condenser realism and clean dashboard metrics --- src/reactor_sim/constants.py | 4 ++++ src/reactor_sim/dashboard.py | 15 +++++++++------ src/reactor_sim/state.py | 2 ++ src/reactor_sim/turbine.py | 30 ++++++++++++++++++++++-------- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/reactor_sim/constants.py b/src/reactor_sim/constants.py index a598442..6d4da5c 100644 --- a/src/reactor_sim/constants.py +++ b/src/reactor_sim/constants.py @@ -37,6 +37,10 @@ TURBINE_THROTTLE_EFFICIENCY_DROP = 0.15 # efficiency loss when at minimum throt CONDENSER_BASE_PRESSURE_MPA = 0.01 CONDENSER_MAX_PRESSURE_MPA = 0.3 CONDENSER_BACKPRESSURE_PENALTY = 0.35 # fractional power loss at max back-pressure +CONDENSER_VACUUM_PUMP_RATE = 0.05 # MPa per second drawdown toward base when below max load +CONDENSER_COOLING_WATER_TEMP_K = 295.0 # cooling sink temperature +CONDENSER_FOULING_RATE = 0.00002 # incremental penalty per second of hot operation +CONDENSER_FOULING_MAX_PENALTY = 0.2 # max additional backpressure penalty from fouling GENERATOR_SPOOL_TIME = 10.0 # seconds to reach full output # Auxiliary power assumptions PUMP_POWER_MW = 12.0 # MW draw per pump unit diff --git a/src/reactor_sim/dashboard.py b/src/reactor_sim/dashboard.py index 66d60eb..b63e988 100644 --- a/src/reactor_sim/dashboard.py +++ b/src/reactor_sim/dashboard.py @@ -450,7 +450,6 @@ class ReactorDashboard: ("Outlet Temp", f"{state.primary_loop.temperature_out:7.1f} K (Target {constants.PRIMARY_OUTLET_TARGET_K:4.0f})"), ("Pressure", f"{state.primary_loop.pressure:5.2f}/{constants.MAX_PRESSURE:4.1f} MPa"), ("Pressurizer", f"{self.reactor.pressurizer_level*100:6.1f}% @ {constants.PRIMARY_PRESSURIZER_SETPOINT_MPA:4.1f} MPa"), - ("Loop Energy", f"{state.primary_loop.energy_j/1e6:7.0f} MJ"), ("Relief", "OPEN" if self.reactor.primary_relief_open else "CLOSED"), ], ) @@ -470,11 +469,6 @@ class ReactorDashboard: ("Outlet Temp", f"{state.secondary_loop.temperature_out:7.1f} K (Target {constants.SECONDARY_OUTLET_TARGET_K:4.0f})"), ("Pressure", f"{state.secondary_loop.pressure:5.2f}/{constants.MAX_PRESSURE:4.1f} MPa"), ("Steam Quality", f"{state.secondary_loop.steam_quality:5.2f}/1.00"), - ("Drum Energy", f"{state.secondary_loop.energy_j/1e6:7.0f} MJ"), - ( - "Spec Enthalpy", - f"{(state.secondary_loop.energy_j / max(1e-6, state.secondary_loop.inventory_kg))/1e3:7.0f} kJ/kg", - ), ("Relief", "OPEN" if self.reactor.secondary_relief_open else "CLOSED"), ], ) @@ -503,6 +497,15 @@ class ReactorDashboard: f"{state.turbines[2].electrical_output_mw:7.1f} MW" if len(state.turbines) > 2 else "n/a", ), ("Throttle", f"{self.reactor.turbines[0].throttle:5.2f}" if self.reactor.turbines else "n/a"), + ( + "Condenser", + ( + f"P={state.turbines[0].condenser_pressure:4.2f} MPa T={state.turbines[0].condenser_temperature:6.1f}K " + f"Foul={state.turbines[0].fouling_penalty*100:4.1f}%" + ) + if state.turbines + else "n/a", + ), ("Electrical", f"{state.total_electrical_output():7.1f} MW"), ("Load", f"{self._total_load_supplied(state):7.1f}/{self._total_load_demand(state):7.1f} MW"), ("Consumer", f"{consumer_status}"), diff --git a/src/reactor_sim/state.py b/src/reactor_sim/state.py index 4d710e9..6e498b5 100644 --- a/src/reactor_sim/state.py +++ b/src/reactor_sim/state.py @@ -71,6 +71,8 @@ class TurbineState: shaft_power_mw: float electrical_output_mw: float condenser_temperature: float + condenser_pressure: float = constants.CONDENSER_BASE_PRESSURE_MPA + fouling_penalty: float = 0.0 load_demand_mw: float = 0.0 load_supplied_mw: float = 0.0 status: str = "OFF" diff --git a/src/reactor_sim/turbine.py b/src/reactor_sim/turbine.py index 494a542..ec8767b 100644 --- a/src/reactor_sim/turbine.py +++ b/src/reactor_sim/turbine.py @@ -6,7 +6,7 @@ from dataclasses import dataclass import logging from . import constants -from .thermal import saturation_temperature +from .thermal import saturation_temperature, saturation_pressure from .state import CoolantLoopState, TurbineState LOGGER = logging.getLogger(__name__) @@ -45,7 +45,9 @@ class Turbine: state.load_demand_mw = 0.0 state.load_supplied_mw = 0.0 state.steam_enthalpy = 0.0 - state.condenser_temperature = max(305.0, loop.temperature_in - 20.0) + state.condenser_temperature = max(constants.CONDENSER_COOLING_WATER_TEMP_K, loop.temperature_in - 20.0) + state.condenser_pressure = max(constants.CONDENSER_BASE_PRESSURE_MPA, state.condenser_pressure - 0.01 * dt) + state.fouling_penalty = max(0.0, state.fouling_penalty - 0.0001 * dt) return throttle = min(constants.TURBINE_THROTTLE_MAX, max(constants.TURBINE_THROTTLE_MIN, self.throttle)) @@ -58,13 +60,24 @@ class Turbine: computed_power = (enthalpy * mass_flow) / 1_000.0 # MW from enthalpy flow available_power = steam_power_mw if steam_power_mw > 0 else computed_power available_power = min(available_power, computed_power) - backpressure_loss = 1.0 - _backpressure_penalty(loop) + backpressure_loss = 1.0 - _backpressure_penalty(state) shaft_power_mw = available_power * self.mechanical_efficiency * throttle_eff * backpressure_loss electrical = shaft_power_mw * self.generator_efficiency if electrical > self.rated_output_mw: electrical = self.rated_output_mw shaft_power_mw = electrical / max(1e-6, self.generator_efficiency) - condenser_temp = max(305.0, loop.temperature_in - 20.0) + condenser_temp = max(constants.CONDENSER_COOLING_WATER_TEMP_K, loop.temperature_in - 20.0) + # Vacuum pump tends toward base pressure; fouling raises it slowly when hot. + target_pressure = constants.CONDENSER_BASE_PRESSURE_MPA + if condenser_temp > constants.CONDENSER_COOLING_WATER_TEMP_K + 20.0: + state.fouling_penalty = min( + constants.CONDENSER_FOULING_MAX_PENALTY, + state.fouling_penalty + constants.CONDENSER_FOULING_RATE * dt, + ) + state.condenser_pressure = max( + target_pressure, + min(constants.CONDENSER_MAX_PRESSURE_MPA, state.condenser_pressure - constants.CONDENSER_VACUUM_PUMP_RATE * dt), + ) state.steam_enthalpy = enthalpy state.shaft_power_mw = _ramp(state.shaft_power_mw, shaft_power_mw, dt, self.spool_time) state.electrical_output_mw = _ramp(state.electrical_output_mw, electrical, dt, self.spool_time) @@ -84,11 +97,12 @@ def _ramp(current: float, target: float, dt: float, time_constant: float) -> flo return current + (target - current) * alpha -def _backpressure_penalty(loop: CoolantLoopState) -> float: +def _backpressure_penalty(state: TurbineState) -> float: base = constants.CONDENSER_BASE_PRESSURE_MPA max_p = constants.CONDENSER_MAX_PRESSURE_MPA - pressure = max(base, min(max_p, loop.pressure)) + pressure = max(base, min(max_p, state.condenser_pressure)) if pressure <= base: - return 0.0 + return min(constants.CONDENSER_BACKPRESSURE_PENALTY, state.fouling_penalty) frac = (pressure - base) / max(1e-6, max_p - base) - return min(constants.CONDENSER_BACKPRESSURE_PENALTY, frac * constants.CONDENSER_BACKPRESSURE_PENALTY) + penalty = frac * constants.CONDENSER_BACKPRESSURE_PENALTY + return min(constants.CONDENSER_BACKPRESSURE_PENALTY + state.fouling_penalty, penalty + state.fouling_penalty)