Compare commits
7 Commits
fb1276f39f
...
14adf86e7f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14adf86e7f | ||
|
|
92857a27a0 | ||
|
|
5d62ea29b6 | ||
|
|
60c5e68ac4 | ||
|
|
d6655a7984 | ||
|
|
67436d795d | ||
|
|
14182e9db6 |
@@ -25,6 +25,10 @@ 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
|
||||||
BASE_AUX_LOAD_MW = 5.0 # control, instrumentation, misc.
|
BASE_AUX_LOAD_MW = 5.0 # control, instrumentation, misc.
|
||||||
|
NORMAL_CORE_POWER_MW = 3_000.0
|
||||||
|
TEST_MAX_POWER_MW = 4_000.0
|
||||||
|
PRIMARY_OUTLET_TARGET_K = 580.0
|
||||||
|
SECONDARY_OUTLET_TARGET_K = 520.0
|
||||||
# Threshold inventories (event counts) for flagging common poisons in diagnostics.
|
# Threshold inventories (event counts) for flagging common poisons in diagnostics.
|
||||||
KEY_POISON_THRESHOLDS = {
|
KEY_POISON_THRESHOLDS = {
|
||||||
"Xe": 1e20, # xenon
|
"Xe": 1e20, # xenon
|
||||||
|
|||||||
@@ -345,8 +345,14 @@ class ReactorDashboard:
|
|||||||
left_y,
|
left_y,
|
||||||
"Core",
|
"Core",
|
||||||
[
|
[
|
||||||
("Fuel Temp", f"{state.core.fuel_temperature:8.1f} K"),
|
(
|
||||||
("Core Power", f"{state.core.power_output_mw:8.1f} MW"),
|
"Fuel Temp",
|
||||||
|
f"{state.core.fuel_temperature:6.1f} K (Max {constants.CORE_MELTDOWN_TEMPERATURE:4.0f})",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Core Power",
|
||||||
|
f"{state.core.power_output_mw:6.1f} MW (Nom {constants.NORMAL_CORE_POWER_MW:4.0f}/Max {constants.TEST_MAX_POWER_MW:4.0f})",
|
||||||
|
),
|
||||||
("Neutron Flux", f"{state.core.neutron_flux:10.2e}"),
|
("Neutron Flux", f"{state.core.neutron_flux:10.2e}"),
|
||||||
("Rods", f"{self.reactor.control.rod_fraction:.3f}"),
|
("Rods", f"{self.reactor.control.rod_fraction:.3f}"),
|
||||||
("Rod Mode", "AUTO" if not self.reactor.control.manual_control else "MANUAL"),
|
("Rod Mode", "AUTO" if not self.reactor.control.manual_control else "MANUAL"),
|
||||||
@@ -362,10 +368,13 @@ class ReactorDashboard:
|
|||||||
[
|
[
|
||||||
("Pump1", self._pump_status(state.primary_pumps, 0)),
|
("Pump1", self._pump_status(state.primary_pumps, 0)),
|
||||||
("Pump2", self._pump_status(state.primary_pumps, 1)),
|
("Pump2", self._pump_status(state.primary_pumps, 1)),
|
||||||
("Flow", f"{state.primary_loop.mass_flow_rate:7.0f} kg/s"),
|
(
|
||||||
|
"Flow",
|
||||||
|
f"{state.primary_loop.mass_flow_rate:7.0f}/{self.reactor.primary_pump.nominal_flow * len(self.reactor.primary_pump_units):.0f} kg/s",
|
||||||
|
),
|
||||||
("Inlet Temp", f"{state.primary_loop.temperature_in:7.1f} K"),
|
("Inlet Temp", f"{state.primary_loop.temperature_in:7.1f} K"),
|
||||||
("Outlet Temp", f"{state.primary_loop.temperature_out:7.1f} K"),
|
("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} MPa"),
|
("Pressure", f"{state.primary_loop.pressure:5.2f}/{constants.MAX_PRESSURE:4.1f} MPa"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self._draw_section(
|
self._draw_section(
|
||||||
@@ -375,10 +384,14 @@ class ReactorDashboard:
|
|||||||
[
|
[
|
||||||
("Pump1", self._pump_status(state.secondary_pumps, 0)),
|
("Pump1", self._pump_status(state.secondary_pumps, 0)),
|
||||||
("Pump2", self._pump_status(state.secondary_pumps, 1)),
|
("Pump2", self._pump_status(state.secondary_pumps, 1)),
|
||||||
("Flow", f"{state.secondary_loop.mass_flow_rate:7.0f} kg/s"),
|
(
|
||||||
("Inlet Temp", f"{state.secondary_loop.temperature_in:7.1f} K"),
|
"Flow",
|
||||||
("Pressure", f"{state.secondary_loop.pressure:5.2f} MPa"),
|
f"{state.secondary_loop.mass_flow_rate:7.0f}/{self.reactor.secondary_pump.nominal_flow * len(self.reactor.secondary_pump_units):.0f} kg/s",
|
||||||
("Steam Quality", f"{state.secondary_loop.steam_quality:5.2f}"),
|
),
|
||||||
|
("Inlet Temp", f"{state.secondary_loop.temperature_in:7.1f} K (Target {constants.PRIMARY_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"),
|
||||||
|
("Steam Quality", f"{state.secondary_loop.steam_quality:5.2f}/1.00"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -394,6 +407,7 @@ class ReactorDashboard:
|
|||||||
"Turbine / Grid",
|
"Turbine / Grid",
|
||||||
[
|
[
|
||||||
("Turbines", " ".join(self._turbine_status_lines())),
|
("Turbines", " ".join(self._turbine_status_lines())),
|
||||||
|
("Rated Elec", f"{len(self.reactor.turbines)*self.reactor.turbines[0].rated_output_mw:7.1f} MW"),
|
||||||
("Unit1 Elec", f"{state.turbines[0].electrical_output_mw:7.1f} MW" if state.turbines else "n/a"),
|
("Unit1 Elec", f"{state.turbines[0].electrical_output_mw:7.1f} MW" if state.turbines else "n/a"),
|
||||||
(
|
(
|
||||||
"Unit2 Elec",
|
"Unit2 Elec",
|
||||||
@@ -410,6 +424,7 @@ class ReactorDashboard:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
right_y = self._draw_section(right_win, right_y, "Generators", self._generator_lines(state))
|
right_y = self._draw_section(right_win, right_y, "Generators", self._generator_lines(state))
|
||||||
|
right_y = self._draw_section(right_win, right_y, "Power Stats", self._power_lines(state))
|
||||||
right_y = self._draw_section(right_win, right_y, "Maintenance", self._maintenance_lines())
|
right_y = self._draw_section(right_win, right_y, "Maintenance", self._maintenance_lines())
|
||||||
self._draw_health_bars(right_win, right_y)
|
self._draw_health_bars(right_win, right_y)
|
||||||
|
|
||||||
@@ -548,8 +563,23 @@ class ReactorDashboard:
|
|||||||
for idx, gen in enumerate(state.generators):
|
for idx, gen in enumerate(state.generators):
|
||||||
status = "RUN" if gen.running else "START" if gen.starting else "OFF"
|
status = "RUN" if gen.running else "START" if gen.starting else "OFF"
|
||||||
spool = f" spool {gen.spool_remaining:4.1f}s" if gen.starting else ""
|
spool = f" spool {gen.spool_remaining:4.1f}s" if gen.starting else ""
|
||||||
lines.append((f"Gen{idx + 1}", f"{status} {gen.power_output_mw:6.1f} MW{spool}"))
|
lines.append((f"Gen{idx + 1}", f"{status} {gen.power_output_mw:6.1f}/{self.reactor.generators[idx].rated_output_mw:4.0f} MW{spool}"))
|
||||||
lines.append((f" Battery", f"{gen.battery_charge*100:5.1f}%"))
|
lines.append((f" Battery", f"{gen.battery_charge*100:5.1f}% out {gen.battery_output_mw:4.1f} MW"))
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def _power_lines(self, state: PlantState) -> list[tuple[str, str]]:
|
||||||
|
draws = getattr(state, "aux_draws", {}) or {}
|
||||||
|
primary_nom = constants.PUMP_POWER_MW * len(self.reactor.primary_pump_units)
|
||||||
|
secondary_nom = constants.PUMP_POWER_MW * len(self.reactor.secondary_pump_units)
|
||||||
|
lines = [
|
||||||
|
("Base Aux", f"{draws.get('base', 0.0):6.1f}/{constants.BASE_AUX_LOAD_MW:4.1f} MW"),
|
||||||
|
("Primary Aux", f"{draws.get('primary_pumps', 0.0):6.1f}/{primary_nom:4.1f} MW"),
|
||||||
|
("Secondary Aux", f"{draws.get('secondary_pumps', 0.0):6.1f}/{secondary_nom:4.1f} MW"),
|
||||||
|
("Aux Demand", f"{draws.get('total_demand', 0.0):6.1f} MW"),
|
||||||
|
("Aux Supplied", f"{draws.get('supplied', 0.0):6.1f} MW"),
|
||||||
|
("Gen Output", f"{draws.get('generator_output', 0.0):6.1f} MW"),
|
||||||
|
("Turbine Elec", f"{draws.get('turbine_output', 0.0):6.1f} MW"),
|
||||||
|
]
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def _draw_health_bars(self, win: "curses._CursesWindow", start_y: int) -> int:
|
def _draw_health_bars(self, win: "curses._CursesWindow", start_y: int) -> int:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class GeneratorState:
|
|||||||
power_output_mw: float
|
power_output_mw: float
|
||||||
battery_charge: float
|
battery_charge: float
|
||||||
status: str = "OFF"
|
status: str = "OFF"
|
||||||
|
battery_output_mw: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -48,9 +49,11 @@ class DieselGenerator:
|
|||||||
|
|
||||||
def step(self, state: GeneratorState, load_demand_mw: float, dt: float) -> float:
|
def step(self, state: GeneratorState, load_demand_mw: float, dt: float) -> float:
|
||||||
"""Advance generator dynamics and return delivered power."""
|
"""Advance generator dynamics and return delivered power."""
|
||||||
|
state.battery_output_mw = 0.0
|
||||||
if state.starting:
|
if state.starting:
|
||||||
state.spool_remaining = max(0.0, state.spool_remaining - dt)
|
state.spool_remaining = max(0.0, state.spool_remaining - dt)
|
||||||
state.power_output_mw = self.rated_output_mw * (1.0 - state.spool_remaining / max(self.spool_time, 1e-6))
|
state.power_output_mw = self.rated_output_mw * (1.0 - state.spool_remaining / max(self.spool_time, 1e-6))
|
||||||
|
state.battery_output_mw = min(0.5, load_demand_mw)
|
||||||
if state.spool_remaining <= 0.0:
|
if state.spool_remaining <= 0.0:
|
||||||
state.starting = False
|
state.starting = False
|
||||||
state.running = True
|
state.running = True
|
||||||
@@ -67,8 +70,8 @@ class DieselGenerator:
|
|||||||
if state.running:
|
if state.running:
|
||||||
state.battery_charge = min(1.0, state.battery_charge + 0.02 * dt)
|
state.battery_charge = min(1.0, state.battery_charge + 0.02 * dt)
|
||||||
elif state.starting:
|
elif state.starting:
|
||||||
state.battery_charge = max(0.0, state.battery_charge - 0.01 * dt)
|
state.battery_charge = max(0.0, state.battery_charge - 0.003 * dt)
|
||||||
else:
|
else:
|
||||||
state.battery_charge = max(0.0, state.battery_charge - 0.001 * dt)
|
state.battery_charge = max(0.0, state.battery_charge - 0.00005 * dt)
|
||||||
|
|
||||||
return state.power_output_mw
|
return state.power_output_mw
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class Reactor:
|
|||||||
turbine_unit_active: list[bool] = field(default_factory=lambda: [True, True, True])
|
turbine_unit_active: list[bool] = field(default_factory=lambda: [True, True, True])
|
||||||
shutdown: bool = False
|
shutdown: bool = False
|
||||||
meltdown: bool = False
|
meltdown: bool = False
|
||||||
generator_auto: bool = True
|
generator_auto: bool = False
|
||||||
poison_alerts: set[str] = field(default_factory=set)
|
poison_alerts: set[str] = field(default_factory=set)
|
||||||
maintenance_active: set[str] = field(default_factory=set)
|
maintenance_active: set[str] = field(default_factory=set)
|
||||||
|
|
||||||
@@ -95,6 +95,7 @@ class Reactor:
|
|||||||
self.control.rod_fraction = 0.95
|
self.control.rod_fraction = 0.95
|
||||||
self.shutdown = True
|
self.shutdown = True
|
||||||
self.meltdown = False
|
self.meltdown = False
|
||||||
|
self.generator_auto = False
|
||||||
self.primary_pump_active = False
|
self.primary_pump_active = False
|
||||||
self.secondary_pump_active = False
|
self.secondary_pump_active = False
|
||||||
self.primary_pump_units = [False] * len(self.primary_pump_units)
|
self.primary_pump_units = [False] * len(self.primary_pump_units)
|
||||||
@@ -201,15 +202,27 @@ class Reactor:
|
|||||||
self.secondary_pump_active and idx < len(self.secondary_pump_units) and self.secondary_pump_units[idx]
|
self.secondary_pump_active and idx < len(self.secondary_pump_units) and self.secondary_pump_units[idx]
|
||||||
for idx in range(2)
|
for idx in range(2)
|
||||||
]
|
]
|
||||||
aux_demand = constants.BASE_AUX_LOAD_MW + constants.PUMP_POWER_MW * (
|
any_units = any(primary_units_active) or any(secondary_units_active) or any(self.turbine_unit_active)
|
||||||
sum(primary_units_active) + sum(secondary_units_active)
|
any_generators = any(getattr(g, "running", False) or getattr(g, "starting", False) for g in state.generators)
|
||||||
)
|
aux_base = 0.0 if (self.shutdown and not any_units and not any_generators) else constants.BASE_AUX_LOAD_MW
|
||||||
|
aux_pump_primary = constants.PUMP_POWER_MW * sum(primary_units_active)
|
||||||
|
aux_pump_secondary = constants.PUMP_POWER_MW * sum(secondary_units_active)
|
||||||
|
aux_demand = aux_base + aux_pump_primary + aux_pump_secondary
|
||||||
turbine_electrical = state.total_electrical_output()
|
turbine_electrical = state.total_electrical_output()
|
||||||
generator_power = self._step_generators(state, aux_demand, turbine_electrical, dt)
|
generator_power = self._step_generators(state, aux_demand, turbine_electrical, dt)
|
||||||
aux_available = turbine_electrical + generator_power
|
aux_available = turbine_electrical + generator_power
|
||||||
power_ratio = 1.0 if aux_demand <= 0 else min(1.0, aux_available / aux_demand)
|
power_ratio = 1.0 if aux_demand <= 0 else min(1.0, aux_available / aux_demand)
|
||||||
if aux_demand > 0 and aux_available < 0.5 * aux_demand:
|
if aux_demand > 0 and aux_available < 0.5 * aux_demand:
|
||||||
LOGGER.warning("Aux power deficit: available %.1f/%.1f MW", aux_available, aux_demand)
|
LOGGER.warning("Aux power deficit: available %.1f/%.1f MW", aux_available, aux_demand)
|
||||||
|
state.aux_draws = {
|
||||||
|
"base": aux_base * power_ratio,
|
||||||
|
"primary_pumps": aux_pump_primary * power_ratio,
|
||||||
|
"secondary_pumps": aux_pump_secondary * power_ratio,
|
||||||
|
"total_demand": aux_demand,
|
||||||
|
"supplied": aux_available,
|
||||||
|
"generator_output": generator_power,
|
||||||
|
"turbine_output": turbine_electrical,
|
||||||
|
}
|
||||||
|
|
||||||
if self.primary_pump_active:
|
if self.primary_pump_active:
|
||||||
total_flow = 0.0
|
total_flow = 0.0
|
||||||
@@ -220,23 +233,25 @@ class Reactor:
|
|||||||
unit_enabled = (
|
unit_enabled = (
|
||||||
self.primary_pump_active and idx < len(self.primary_pump_units) and self.primary_pump_units[idx]
|
self.primary_pump_active and idx < len(self.primary_pump_units) and self.primary_pump_units[idx]
|
||||||
)
|
)
|
||||||
|
powered = power_ratio > 0.1
|
||||||
desired_flow = target_flow if unit_enabled else 0.0
|
desired_flow = target_flow if unit_enabled else 0.0
|
||||||
desired_pressure = target_pressure if unit_enabled else 0.5
|
desired_pressure = target_pressure if unit_enabled else 0.5
|
||||||
|
if not powered:
|
||||||
|
desired_flow = 0.0
|
||||||
|
desired_pressure = 0.5
|
||||||
pump_state.flow_rate = self._ramp_value(
|
pump_state.flow_rate = self._ramp_value(
|
||||||
pump_state.flow_rate, desired_flow, dt, self.primary_pump.spool_time
|
pump_state.flow_rate, desired_flow, dt, self.primary_pump.spool_time
|
||||||
)
|
)
|
||||||
pump_state.pressure = self._ramp_value(
|
pump_state.pressure = self._ramp_value(
|
||||||
pump_state.pressure, desired_pressure, dt, self.primary_pump.spool_time
|
pump_state.pressure, desired_pressure, dt, self.primary_pump.spool_time
|
||||||
)
|
)
|
||||||
pump_state.active = (unit_enabled and power_ratio > 0.05) or pump_state.flow_rate > 1.0
|
pump_state.active = unit_enabled and powered and pump_state.flow_rate > 1.0
|
||||||
if unit_enabled and pump_state.flow_rate < max(1.0, desired_flow * 0.8):
|
if not powered or not unit_enabled:
|
||||||
|
pump_state.status = "STOPPING" if pump_state.flow_rate > 1.0 else "OFF"
|
||||||
|
elif pump_state.flow_rate < max(1.0, desired_flow * 0.8):
|
||||||
pump_state.status = "STARTING"
|
pump_state.status = "STARTING"
|
||||||
elif not unit_enabled and pump_state.flow_rate > 1.0:
|
|
||||||
pump_state.status = "STOPPING"
|
|
||||||
elif pump_state.active:
|
|
||||||
pump_state.status = "RUN"
|
|
||||||
else:
|
else:
|
||||||
pump_state.status = "OFF"
|
pump_state.status = "RUN"
|
||||||
total_flow += pump_state.flow_rate
|
total_flow += pump_state.flow_rate
|
||||||
loop_pressure = max(loop_pressure, pump_state.pressure)
|
loop_pressure = max(loop_pressure, pump_state.pressure)
|
||||||
state.primary_loop.mass_flow_rate = total_flow
|
state.primary_loop.mass_flow_rate = total_flow
|
||||||
@@ -263,28 +278,30 @@ class Reactor:
|
|||||||
total_flow = 0.0
|
total_flow = 0.0
|
||||||
target_pressure = 12.0 * 0.75 + 2.0
|
target_pressure = 12.0 * 0.75 + 2.0
|
||||||
loop_pressure = 0.5
|
loop_pressure = 0.5
|
||||||
target_flow = self.secondary_pump.flow_rate(0.75)
|
target_flow = self.secondary_pump.flow_rate(0.75) * power_ratio
|
||||||
for idx, pump_state in enumerate(state.secondary_pumps):
|
for idx, pump_state in enumerate(state.secondary_pumps):
|
||||||
unit_enabled = (
|
unit_enabled = (
|
||||||
self.secondary_pump_active and idx < len(self.secondary_pump_units) and self.secondary_pump_units[idx]
|
self.secondary_pump_active and idx < len(self.secondary_pump_units) and self.secondary_pump_units[idx]
|
||||||
)
|
)
|
||||||
|
powered = power_ratio > 0.1
|
||||||
desired_flow = target_flow if unit_enabled else 0.0
|
desired_flow = target_flow if unit_enabled else 0.0
|
||||||
desired_pressure = target_pressure if unit_enabled else 0.5
|
desired_pressure = target_pressure if unit_enabled else 0.5
|
||||||
|
if not powered:
|
||||||
|
desired_flow = 0.0
|
||||||
|
desired_pressure = 0.5
|
||||||
pump_state.flow_rate = self._ramp_value(
|
pump_state.flow_rate = self._ramp_value(
|
||||||
pump_state.flow_rate, desired_flow, dt, self.secondary_pump.spool_time
|
pump_state.flow_rate, desired_flow, dt, self.secondary_pump.spool_time
|
||||||
)
|
)
|
||||||
pump_state.pressure = self._ramp_value(
|
pump_state.pressure = self._ramp_value(
|
||||||
pump_state.pressure, desired_pressure, dt, self.secondary_pump.spool_time
|
pump_state.pressure, desired_pressure, dt, self.secondary_pump.spool_time
|
||||||
)
|
)
|
||||||
pump_state.active = unit_enabled or pump_state.flow_rate > 1.0
|
pump_state.active = unit_enabled and powered and pump_state.flow_rate > 1.0
|
||||||
if unit_enabled and pump_state.flow_rate < max(1.0, desired_flow * 0.8):
|
if not powered or not unit_enabled:
|
||||||
|
pump_state.status = "STOPPING" if pump_state.flow_rate > 1.0 else "OFF"
|
||||||
|
elif pump_state.flow_rate < max(1.0, desired_flow * 0.8):
|
||||||
pump_state.status = "STARTING"
|
pump_state.status = "STARTING"
|
||||||
elif not unit_enabled and pump_state.flow_rate > 1.0:
|
|
||||||
pump_state.status = "STOPPING"
|
|
||||||
elif pump_state.active:
|
|
||||||
pump_state.status = "RUN"
|
|
||||||
else:
|
else:
|
||||||
pump_state.status = "OFF"
|
pump_state.status = "RUN"
|
||||||
total_flow += pump_state.flow_rate
|
total_flow += pump_state.flow_rate
|
||||||
loop_pressure = max(loop_pressure, pump_state.pressure)
|
loop_pressure = max(loop_pressure, pump_state.pressure)
|
||||||
state.secondary_loop.mass_flow_rate = total_flow
|
state.secondary_loop.mass_flow_rate = total_flow
|
||||||
@@ -365,13 +382,13 @@ class Reactor:
|
|||||||
turbine.step(state.secondary_loop, turbine_state, steam_power_mw=power_per_unit, dt=dt)
|
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:
|
if power_per_unit <= 0.0 and turbine_state.electrical_output_mw < 0.1:
|
||||||
turbine_state.status = "OFF"
|
turbine_state.status = "OFF"
|
||||||
elif turbine_state.electrical_output_mw < max(0.5, power_per_unit * 0.5):
|
elif turbine_state.electrical_output_mw < max(0.1 * turbine.rated_output_mw, 1.0):
|
||||||
turbine_state.status = "STARTING"
|
turbine_state.status = "STARTING"
|
||||||
else:
|
else:
|
||||||
turbine_state.status = "RUN"
|
turbine_state.status = "RUN"
|
||||||
else:
|
else:
|
||||||
self._spin_down_turbine(turbine_state, dt, turbine.spool_time)
|
self._spin_down_turbine(turbine_state, dt, turbine.spool_time)
|
||||||
turbine_state.status = "STOPPING" if turbine_state.electrical_output_mw > 0 else "OFF"
|
turbine_state.status = "STOPPING" if turbine_state.electrical_output_mw > 0.1 else "OFF"
|
||||||
self._dispatch_consumer_load(state, active_indices)
|
self._dispatch_consumer_load(state, active_indices)
|
||||||
|
|
||||||
def _reset_turbine_state(self, turbine_state: TurbineState) -> None:
|
def _reset_turbine_state(self, turbine_state: TurbineState) -> None:
|
||||||
@@ -397,6 +414,8 @@ class Reactor:
|
|||||||
turbine_state.load_supplied_mw = self._ramp_value(
|
turbine_state.load_supplied_mw = self._ramp_value(
|
||||||
turbine_state.load_supplied_mw, 0.0, dt, time_constant
|
turbine_state.load_supplied_mw, 0.0, dt, time_constant
|
||||||
)
|
)
|
||||||
|
if turbine_state.electrical_output_mw < 0.1:
|
||||||
|
turbine_state.electrical_output_mw = 0.0
|
||||||
|
|
||||||
def _dispatch_consumer_load(self, state: PlantState, active_indices: list[int]) -> None:
|
def _dispatch_consumer_load(self, state: PlantState, active_indices: list[int]) -> None:
|
||||||
total_electrical = sum(state.turbines[idx].electrical_output_mw for idx in active_indices)
|
total_electrical = sum(state.turbines[idx].electrical_output_mw for idx in active_indices)
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class PlantState:
|
|||||||
primary_pumps: list[PumpState] = field(default_factory=list)
|
primary_pumps: list[PumpState] = field(default_factory=list)
|
||||||
secondary_pumps: list[PumpState] = field(default_factory=list)
|
secondary_pumps: list[PumpState] = field(default_factory=list)
|
||||||
generators: list[GeneratorState] = field(default_factory=list)
|
generators: list[GeneratorState] = field(default_factory=list)
|
||||||
|
aux_draws: dict[str, float] = field(default_factory=dict)
|
||||||
time_elapsed: float = field(default=0.0)
|
time_elapsed: float = field(default=0.0)
|
||||||
|
|
||||||
def snapshot(self) -> dict[str, float]:
|
def snapshot(self) -> dict[str, float]:
|
||||||
@@ -113,6 +114,7 @@ class PlantState:
|
|||||||
sec_pumps_blob = data.get("secondary_pumps", [])
|
sec_pumps_blob = data.get("secondary_pumps", [])
|
||||||
generators_blob = data.get("generators", [])
|
generators_blob = data.get("generators", [])
|
||||||
generators = [GeneratorState(**g) for g in generators_blob]
|
generators = [GeneratorState(**g) for g in generators_blob]
|
||||||
|
aux_draws = data.get("aux_draws", {})
|
||||||
return cls(
|
return cls(
|
||||||
core=CoreState(**core_blob, fission_product_inventory=inventory, emitted_particles=particles),
|
core=CoreState(**core_blob, fission_product_inventory=inventory, emitted_particles=particles),
|
||||||
primary_loop=CoolantLoopState(**data["primary_loop"]),
|
primary_loop=CoolantLoopState(**data["primary_loop"]),
|
||||||
@@ -121,5 +123,6 @@ class PlantState:
|
|||||||
primary_pumps=[PumpState(**p) for p in prim_pumps_blob],
|
primary_pumps=[PumpState(**p) for p in prim_pumps_blob],
|
||||||
secondary_pumps=[PumpState(**p) for p in sec_pumps_blob],
|
secondary_pumps=[PumpState(**p) for p in sec_pumps_blob],
|
||||||
generators=generators,
|
generators=generators,
|
||||||
|
aux_draws=aux_draws,
|
||||||
time_elapsed=data.get("time_elapsed", 0.0),
|
time_elapsed=data.get("time_elapsed", 0.0),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -127,7 +127,10 @@ def test_secondary_pump_unit_toggle_can_restart_pump():
|
|||||||
reactor.secondary_pump_active = False
|
reactor.secondary_pump_active = False
|
||||||
reactor.secondary_pump_units = [False, False]
|
reactor.secondary_pump_units = [False, False]
|
||||||
|
|
||||||
reactor.step(state, dt=1.0, command=ReactorCommand(secondary_pumps={1: True}))
|
cmd = ReactorCommand(secondary_pumps={1: True}, generator_units={1: True})
|
||||||
|
for _ in range(5):
|
||||||
|
reactor.step(state, dt=1.0, command=cmd)
|
||||||
|
cmd = ReactorCommand(coolant_demand=0.75)
|
||||||
|
|
||||||
assert reactor.secondary_pump_units == [True, False]
|
assert reactor.secondary_pump_units == [True, False]
|
||||||
assert reactor.secondary_pump_active is True
|
assert reactor.secondary_pump_active is True
|
||||||
@@ -140,11 +143,9 @@ def test_primary_pumps_spool_up_over_seconds():
|
|||||||
reactor.secondary_pump_units = [False, False]
|
reactor.secondary_pump_units = [False, False]
|
||||||
# Enable both pumps and command full flow; spool should take multiple steps.
|
# Enable both pumps and command full flow; spool should take multiple steps.
|
||||||
target_flow = reactor.primary_pump.flow_rate(1.0) * len(reactor.primary_pump_units)
|
target_flow = reactor.primary_pump.flow_rate(1.0) * len(reactor.primary_pump_units)
|
||||||
reactor.step(
|
cmd = ReactorCommand(primary_pumps={1: True, 2: True}, generator_units={1: True}, coolant_demand=1.0)
|
||||||
state,
|
reactor.step(state, dt=1.0, command=cmd)
|
||||||
dt=1.0,
|
reactor.step(state, dt=1.0, command=ReactorCommand(coolant_demand=1.0))
|
||||||
command=ReactorCommand(primary_pumps={1: True, 2: True}, generator_units={1: True}, coolant_demand=1.0),
|
|
||||||
)
|
|
||||||
first_flow = state.primary_loop.mass_flow_rate
|
first_flow = state.primary_loop.mass_flow_rate
|
||||||
assert 0.0 < first_flow < target_flow
|
assert 0.0 < first_flow < target_flow
|
||||||
|
|
||||||
@@ -164,6 +165,8 @@ def test_full_rod_withdrawal_reaches_gigawatt_power():
|
|||||||
reactor.secondary_pump_active = True
|
reactor.secondary_pump_active = True
|
||||||
reactor.primary_pump_units = [True, True]
|
reactor.primary_pump_units = [True, True]
|
||||||
reactor.secondary_pump_units = [True, True]
|
reactor.secondary_pump_units = [True, True]
|
||||||
|
reactor.generator_auto = True
|
||||||
|
reactor.step(state, dt=1.0, command=ReactorCommand(generator_units={1: True}))
|
||||||
|
|
||||||
early_power = 0.0
|
early_power = 0.0
|
||||||
for step in range(60):
|
for step in range(60):
|
||||||
@@ -184,6 +187,8 @@ def test_partially_inserted_rods_hold_near_three_gw():
|
|||||||
reactor.secondary_pump_active = True
|
reactor.secondary_pump_active = True
|
||||||
reactor.primary_pump_units = [True, True]
|
reactor.primary_pump_units = [True, True]
|
||||||
reactor.secondary_pump_units = [True, True]
|
reactor.secondary_pump_units = [True, True]
|
||||||
|
reactor.generator_auto = True
|
||||||
|
reactor.step(state, dt=1.0, command=ReactorCommand(generator_units={1: True}))
|
||||||
|
|
||||||
for _ in range(120):
|
for _ in range(120):
|
||||||
reactor.step(state, dt=1.0)
|
reactor.step(state, dt=1.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user