feat: improve manual rod control and reactor power

This commit is contained in:
Andrii Prokhorov
2025-11-21 17:59:25 +02:00
parent 6e8520c925
commit 2400ce669e
6 changed files with 33 additions and 15 deletions

View File

@@ -19,6 +19,7 @@ class ReactorCommand:
power_setpoint: float | None = None
consumer_online: bool | None = None
consumer_demand: float | None = None
rod_manual: bool | None = None
@classmethod
def scram_all(cls) -> "ReactorCommand":

View File

@@ -16,4 +16,4 @@ GENERATOR_EFFICIENCY = 0.96
ENVIRONMENT_TEMPERATURE = 295.0 # K
AMU_TO_KG = 1.660_539_066_60e-27
MEV_TO_J = 1.602_176_634e-13
ELECTRON_FISSION_CROSS_SECTION = 5e-23 # cm^2, tuned for simulation scale
ELECTRON_FISSION_CROSS_SECTION = 5e-16 # cm^2, tuned for simulation scale

View File

@@ -21,8 +21,11 @@ def clamp(value: float, lo: float, hi: float) -> float:
class ControlSystem:
setpoint_mw: float = 3_000.0
rod_fraction: float = 0.5
manual_control: bool = False
def update_rods(self, state: CoreState, dt: float) -> float:
if self.manual_control:
return self.rod_fraction
error = (state.power_output_mw - self.setpoint_mw) / self.setpoint_mw
adjustment = -error * 0.3
adjustment = clamp(adjustment, -constants.CONTROL_ROD_SPEED * dt, constants.CONTROL_ROD_SPEED * dt)
@@ -50,6 +53,11 @@ class ControlSystem:
self.setpoint_mw = clamp(megawatts, 100.0, 4_000.0)
LOGGER.info("Power setpoint %.0f -> %.0f MW", previous, self.setpoint_mw)
def set_manual_mode(self, manual: bool) -> None:
if self.manual_control != manual:
self.manual_control = manual
LOGGER.info("Rod control %s", "manual" if manual else "automatic")
def coolant_demand(self, primary: CoolantLoopState) -> float:
desired_temp = 580.0
error = (primary.temperature_out - desired_temp) / 100.0

View File

@@ -46,6 +46,7 @@ class ReactorDashboard:
DashboardKey("[/]", "Adjust consumer demand /+50 MW"),
DashboardKey("s", "Setpoint 250 MW"),
DashboardKey("d", "Setpoint +250 MW"),
DashboardKey("a", "Toggle auto rod control"),
]
def run(self) -> None:
@@ -112,23 +113,28 @@ class ReactorDashboard:
self._queue_command(ReactorCommand(power_setpoint=self.reactor.control.setpoint_mw - 250.0))
elif ch in (ord("d"), ord("D")):
self._queue_command(ReactorCommand(power_setpoint=self.reactor.control.setpoint_mw + 250.0))
elif ch in (ord("a"), ord("A")):
self._queue_command(ReactorCommand(rod_manual=not self.reactor.control.manual_control))
def _queue_command(self, command: ReactorCommand) -> None:
if self.pending_command is None:
self.pending_command = command
return
for field in command.__dataclass_fields__: # type: ignore[attr-defined]
value = getattr(command, field)
if value is None or value is False:
continue
if field == "rod_step" and getattr(self.pending_command, field) is not None:
setattr(
self.pending_command,
field,
getattr(self.pending_command, field) + value,
)
else:
setattr(self.pending_command, field, value)
else:
for field in command.__dataclass_fields__: # type: ignore[attr-defined]
value = getattr(command, field)
if value is None or value is False:
continue
if field == "rod_step" and getattr(self.pending_command, field) is not None:
setattr(
self.pending_command,
field,
getattr(self.pending_command, field) + value,
)
else:
setattr(self.pending_command, field, value)
if self.pending_command.rod_position is not None and self.pending_command.rod_manual is None:
self.pending_command.rod_manual = True
def _next_command(self, _: float, __: PlantState) -> Optional[ReactorCommand]:
cmd = self.pending_command

View File

@@ -182,7 +182,10 @@ class Reactor:
self._set_turbine_state(False)
if command.power_setpoint is not None:
self.control.set_power_setpoint(command.power_setpoint)
if command.rod_manual is not None:
self.control.set_manual_mode(command.rod_manual)
if command.rod_position is not None:
self.control.set_manual_mode(True)
overrides["rod_fraction"] = self.control.set_rods(command.rod_position)
self.shutdown = self.shutdown or command.rod_position >= 0.95
elif command.rod_step is not None: