Add condenser realism and clean dashboard metrics
This commit is contained in:
@@ -37,6 +37,10 @@ TURBINE_THROTTLE_EFFICIENCY_DROP = 0.15 # efficiency loss when at minimum throt
|
|||||||
CONDENSER_BASE_PRESSURE_MPA = 0.01
|
CONDENSER_BASE_PRESSURE_MPA = 0.01
|
||||||
CONDENSER_MAX_PRESSURE_MPA = 0.3
|
CONDENSER_MAX_PRESSURE_MPA = 0.3
|
||||||
CONDENSER_BACKPRESSURE_PENALTY = 0.35 # fractional power loss at max back-pressure
|
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
|
GENERATOR_SPOOL_TIME = 10.0 # seconds to reach full output
|
||||||
# Auxiliary power assumptions
|
# Auxiliary power assumptions
|
||||||
PUMP_POWER_MW = 12.0 # MW draw per pump unit
|
PUMP_POWER_MW = 12.0 # MW draw per pump unit
|
||||||
|
|||||||
@@ -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})"),
|
("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"),
|
("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"),
|
("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"),
|
("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})"),
|
("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"),
|
("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"),
|
("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"),
|
("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",
|
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"),
|
("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"),
|
("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"),
|
("Load", f"{self._total_load_supplied(state):7.1f}/{self._total_load_demand(state):7.1f} MW"),
|
||||||
("Consumer", f"{consumer_status}"),
|
("Consumer", f"{consumer_status}"),
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ class TurbineState:
|
|||||||
shaft_power_mw: float
|
shaft_power_mw: float
|
||||||
electrical_output_mw: float
|
electrical_output_mw: float
|
||||||
condenser_temperature: float
|
condenser_temperature: float
|
||||||
|
condenser_pressure: float = constants.CONDENSER_BASE_PRESSURE_MPA
|
||||||
|
fouling_penalty: float = 0.0
|
||||||
load_demand_mw: float = 0.0
|
load_demand_mw: float = 0.0
|
||||||
load_supplied_mw: float = 0.0
|
load_supplied_mw: float = 0.0
|
||||||
status: str = "OFF"
|
status: str = "OFF"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from . import constants
|
from . import constants
|
||||||
from .thermal import saturation_temperature
|
from .thermal import saturation_temperature, saturation_pressure
|
||||||
from .state import CoolantLoopState, TurbineState
|
from .state import CoolantLoopState, TurbineState
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
@@ -45,7 +45,9 @@ class Turbine:
|
|||||||
state.load_demand_mw = 0.0
|
state.load_demand_mw = 0.0
|
||||||
state.load_supplied_mw = 0.0
|
state.load_supplied_mw = 0.0
|
||||||
state.steam_enthalpy = 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
|
return
|
||||||
|
|
||||||
throttle = min(constants.TURBINE_THROTTLE_MAX, max(constants.TURBINE_THROTTLE_MIN, self.throttle))
|
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
|
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 = steam_power_mw if steam_power_mw > 0 else computed_power
|
||||||
available_power = min(available_power, 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
|
shaft_power_mw = available_power * self.mechanical_efficiency * throttle_eff * backpressure_loss
|
||||||
electrical = shaft_power_mw * self.generator_efficiency
|
electrical = shaft_power_mw * self.generator_efficiency
|
||||||
if electrical > self.rated_output_mw:
|
if electrical > self.rated_output_mw:
|
||||||
electrical = self.rated_output_mw
|
electrical = self.rated_output_mw
|
||||||
shaft_power_mw = electrical / max(1e-6, self.generator_efficiency)
|
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.steam_enthalpy = enthalpy
|
||||||
state.shaft_power_mw = _ramp(state.shaft_power_mw, shaft_power_mw, dt, self.spool_time)
|
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)
|
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
|
return current + (target - current) * alpha
|
||||||
|
|
||||||
|
|
||||||
def _backpressure_penalty(loop: CoolantLoopState) -> float:
|
def _backpressure_penalty(state: TurbineState) -> float:
|
||||||
base = constants.CONDENSER_BASE_PRESSURE_MPA
|
base = constants.CONDENSER_BASE_PRESSURE_MPA
|
||||||
max_p = constants.CONDENSER_MAX_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:
|
if pressure <= base:
|
||||||
return 0.0
|
return min(constants.CONDENSER_BACKPRESSURE_PENALTY, state.fouling_penalty)
|
||||||
frac = (pressure - base) / max(1e-6, max_p - base)
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user