diff --git a/src/reactor_sim/constants.py b/src/reactor_sim/constants.py index 828db81..bc2fc5d 100644 --- a/src/reactor_sim/constants.py +++ b/src/reactor_sim/constants.py @@ -25,6 +25,8 @@ GENERATOR_SPOOL_TIME = 10.0 # seconds to reach full output # Auxiliary power assumptions PUMP_POWER_MW = 12.0 # MW draw per pump unit BASE_AUX_LOAD_MW = 5.0 # control, instrumentation, misc. +NORMAL_CORE_POWER_MW = 3_000.0 +TEST_MAX_POWER_MW = 4_000.0 # Threshold inventories (event counts) for flagging common poisons in diagnostics. KEY_POISON_THRESHOLDS = { "Xe": 1e20, # xenon diff --git a/src/reactor_sim/dashboard.py b/src/reactor_sim/dashboard.py index ec5b825..0682870 100644 --- a/src/reactor_sim/dashboard.py +++ b/src/reactor_sim/dashboard.py @@ -345,8 +345,14 @@ class ReactorDashboard: left_y, "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}"), ("Rods", f"{self.reactor.control.rod_fraction:.3f}"), ("Rod Mode", "AUTO" if not self.reactor.control.manual_control else "MANUAL"), @@ -362,7 +368,10 @@ class ReactorDashboard: [ ("Pump1", self._pump_status(state.primary_pumps, 0)), ("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"), ("Outlet Temp", f"{state.primary_loop.temperature_out:7.1f} K"), ("Pressure", f"{state.primary_loop.pressure:5.2f} MPa"), @@ -394,6 +403,7 @@ class ReactorDashboard: "Turbine / Grid", [ ("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"), ( "Unit2 Elec", @@ -410,6 +420,8 @@ 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, "Power Stats", self._power_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, "Maintenance", self._maintenance_lines()) self._draw_health_bars(right_win, right_y) @@ -548,10 +560,25 @@ class ReactorDashboard: for idx, gen in enumerate(state.generators): 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 "" - 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}%")) 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 + def _draw_health_bars(self, win: "curses._CursesWindow", start_y: int) -> int: height, width = win.getmaxyx() inner_width = width - 4 diff --git a/src/reactor_sim/reactor.py b/src/reactor_sim/reactor.py index 0621eb7..b67f205 100644 --- a/src/reactor_sim/reactor.py +++ b/src/reactor_sim/reactor.py @@ -201,15 +201,25 @@ class Reactor: self.secondary_pump_active and idx < len(self.secondary_pump_units) and self.secondary_pump_units[idx] for idx in range(2) ] - aux_demand = constants.BASE_AUX_LOAD_MW + constants.PUMP_POWER_MW * ( - sum(primary_units_active) + sum(secondary_units_active) - ) + aux_base = 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() generator_power = self._step_generators(state, aux_demand, turbine_electrical, dt) aux_available = turbine_electrical + generator_power 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: 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: total_flow = 0.0 diff --git a/src/reactor_sim/state.py b/src/reactor_sim/state.py index 20f1203..0ae53f9 100644 --- a/src/reactor_sim/state.py +++ b/src/reactor_sim/state.py @@ -74,6 +74,7 @@ class PlantState: primary_pumps: list[PumpState] = field(default_factory=list) secondary_pumps: list[PumpState] = 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) def snapshot(self) -> dict[str, float]: @@ -113,6 +114,7 @@ class PlantState: sec_pumps_blob = data.get("secondary_pumps", []) generators_blob = data.get("generators", []) generators = [GeneratorState(**g) for g in generators_blob] + aux_draws = data.get("aux_draws", {}) return cls( core=CoreState(**core_blob, fission_product_inventory=inventory, emitted_particles=particles), primary_loop=CoolantLoopState(**data["primary_loop"]), @@ -121,5 +123,6 @@ class PlantState: primary_pumps=[PumpState(**p) for p in prim_pumps_blob], secondary_pumps=[PumpState(**p) for p in sec_pumps_blob], generators=generators, + aux_draws=aux_draws, time_elapsed=data.get("time_elapsed", 0.0), )