Add turbine throttle mapping and back-pressure penalty
This commit is contained in:
@@ -5,6 +5,6 @@
|
||||
- **Coolant & hydraulics**: primary/secondary pumps with head/flow curves, power draw scaling, wear tracking; pressure floors tied to saturation; auxiliary power model with generator auto-start.
|
||||
- **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 when flow is low.
|
||||
- **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, load dispatch to consumer, steam quality gating for output, generator states with batteries/spool.
|
||||
- **Steam cycle**: three turbines with spool dynamics, throttle mapping, condenser back-pressure penalty, load dispatch to consumer, steam quality gating for output, generator states with batteries/spool.
|
||||
- **Protections & failures**: health monitor degrading components under stress, automatic SCRAM on core or heat-sink loss, relief valves per loop, 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.
|
||||
|
||||
@@ -24,6 +24,14 @@ PUMP_SPOOL_TIME = 5.0 # seconds to reach commanded flow
|
||||
PRIMARY_PUMP_SHUTOFF_HEAD_MPA = 8.0 # approximate shutoff head for primary pumps
|
||||
SECONDARY_PUMP_SHUTOFF_HEAD_MPA = 7.0
|
||||
TURBINE_SPOOL_TIME = 12.0 # seconds to reach steady output
|
||||
|
||||
# Turbine/condenser parameters
|
||||
TURBINE_THROTTLE_MIN = 0.1
|
||||
TURBINE_THROTTLE_MAX = 1.0
|
||||
TURBINE_THROTTLE_EFFICIENCY_DROP = 0.15 # efficiency loss when at minimum throttle
|
||||
CONDENSER_BASE_PRESSURE_MPA = 0.01
|
||||
CONDENSER_MAX_PRESSURE_MPA = 0.3
|
||||
CONDENSER_BACKPRESSURE_PENALTY = 0.35 # fractional power loss at max back-pressure
|
||||
GENERATOR_SPOOL_TIME = 10.0 # seconds to reach full output
|
||||
# Auxiliary power assumptions
|
||||
PUMP_POWER_MW = 12.0 # MW draw per pump unit
|
||||
|
||||
@@ -435,6 +435,7 @@ class ReactorDashboard:
|
||||
"Unit3 Elec",
|
||||
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"),
|
||||
("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}"),
|
||||
|
||||
@@ -461,6 +461,10 @@ class Reactor:
|
||||
break
|
||||
turbine_state = state.turbines[idx]
|
||||
if idx in active_indices:
|
||||
# Simple throttle map: reduce throttle when electrical demand is low, open as demand rises.
|
||||
demand = turbine_state.load_demand_mw
|
||||
throttle = 0.4 if demand <= 0 else min(1.0, 0.4 + demand / max(1e-6, turbine.rated_output_mw))
|
||||
turbine.throttle = throttle
|
||||
turbine.step(state.secondary_loop, turbine_state, steam_power_mw=power_per_unit, dt=dt)
|
||||
if power_per_unit <= 0.0 and turbine_state.electrical_output_mw < 0.1:
|
||||
turbine_state.status = "OFF"
|
||||
|
||||
@@ -27,6 +27,7 @@ class Turbine:
|
||||
mechanical_efficiency: float = constants.STEAM_TURBINE_EFFICIENCY
|
||||
rated_output_mw: float = 400.0 # cap per unit electrical output
|
||||
spool_time: float = constants.TURBINE_SPOOL_TIME
|
||||
throttle: float = 1.0 # 0-1 valve position
|
||||
|
||||
def step(
|
||||
self,
|
||||
@@ -46,11 +47,15 @@ class Turbine:
|
||||
state.condenser_temperature = max(305.0, loop.temperature_in - 20.0)
|
||||
return
|
||||
|
||||
throttle = min(constants.TURBINE_THROTTLE_MAX, max(constants.TURBINE_THROTTLE_MIN, self.throttle))
|
||||
throttle_eff = 1.0 - constants.TURBINE_THROTTLE_EFFICIENCY_DROP * (constants.TURBINE_THROTTLE_MAX - throttle)
|
||||
|
||||
enthalpy = 2_700.0 + loop.steam_quality * 600.0
|
||||
mass_flow = effective_mass_flow * 0.6
|
||||
mass_flow = effective_mass_flow * 0.6 * throttle
|
||||
computed_power = (enthalpy * mass_flow / 1_000.0) / 1_000.0
|
||||
available_power = steam_power_mw if steam_power_mw > 0 else computed_power
|
||||
shaft_power_mw = available_power * self.mechanical_efficiency
|
||||
backpressure_loss = 1.0 - _backpressure_penalty(loop)
|
||||
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
|
||||
@@ -71,5 +76,15 @@ class Turbine:
|
||||
def _ramp(current: float, target: float, dt: float, time_constant: float) -> float:
|
||||
if time_constant <= 0.0:
|
||||
return target
|
||||
alpha = min(1.0, max(0.0, dt / time_constant))
|
||||
alpha = min(1.0, max(0.0, dt / max(1e-6, time_constant)))
|
||||
return current + (target - current) * alpha
|
||||
|
||||
|
||||
def _backpressure_penalty(loop: CoolantLoopState) -> float:
|
||||
base = constants.CONDENSER_BASE_PRESSURE_MPA
|
||||
max_p = constants.CONDENSER_MAX_PRESSURE_MPA
|
||||
pressure = max(base, min(max_p, loop.pressure))
|
||||
if pressure <= base:
|
||||
return 0.0
|
||||
frac = (pressure - base) / max(1e-6, max_p - base)
|
||||
return min(constants.CONDENSER_BACKPRESSURE_PENALTY, frac * constants.CONDENSER_BACKPRESSURE_PENALTY)
|
||||
|
||||
@@ -6,10 +6,11 @@ from reactor_sim.turbine import Turbine
|
||||
|
||||
def test_turbine_spools_toward_target_output():
|
||||
turbine = Turbine()
|
||||
turbine.throttle = 1.0
|
||||
loop = CoolantLoopState(
|
||||
temperature_in=600.0,
|
||||
temperature_out=650.0,
|
||||
pressure=6.0,
|
||||
pressure=0.02,
|
||||
mass_flow_rate=20_000.0,
|
||||
steam_quality=0.9,
|
||||
)
|
||||
@@ -20,14 +21,15 @@ def test_turbine_spools_toward_target_output():
|
||||
condenser_temperature=300.0,
|
||||
)
|
||||
target_electric = min(
|
||||
turbine.rated_output_mw, 300.0 * turbine.mechanical_efficiency * turbine.generator_efficiency
|
||||
turbine.rated_output_mw,
|
||||
300.0 * turbine.mechanical_efficiency * turbine.generator_efficiency,
|
||||
)
|
||||
|
||||
dt = 5.0
|
||||
dt = 1.0
|
||||
turbine.step(loop, state, steam_power_mw=300.0, dt=dt)
|
||||
assert 0.0 < state.electrical_output_mw < target_electric
|
||||
|
||||
for _ in range(5):
|
||||
for _ in range(60):
|
||||
turbine.step(loop, state, steam_power_mw=300.0, dt=dt)
|
||||
|
||||
assert state.electrical_output_mw == pytest.approx(target_electric, rel=0.05)
|
||||
|
||||
Reference in New Issue
Block a user