From 2c3f9e3b4524eee07f050d127dcf66cf54f190eb Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sun, 23 Nov 2025 23:12:13 +0100 Subject: [PATCH] Quantize manual rod steps to 0.025 --- src/reactor_sim/constants.py | 1 + src/reactor_sim/control.py | 7 ++++++- src/reactor_sim/dashboard.py | 8 +++++--- tests/test_control.py | 40 ++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 tests/test_control.py diff --git a/src/reactor_sim/constants.py b/src/reactor_sim/constants.py index 354b693..d8df13d 100644 --- a/src/reactor_sim/constants.py +++ b/src/reactor_sim/constants.py @@ -14,6 +14,7 @@ MAX_PRESSURE = 15.0 # MPa typical PWR primary loop limit CONTROL_ROD_SPEED = 0.03 # fraction insertion per second CONTROL_ROD_WORTH = 0.042 # delta rho contribution when fully withdrawn CONTROL_ROD_BANK_WEIGHTS = (0.4, 0.35, 0.25) +ROD_MANUAL_STEP = 0.025 STEAM_TURBINE_EFFICIENCY = 0.34 GENERATOR_EFFICIENCY = 0.96 ENVIRONMENT_TEMPERATURE = 295.0 # K diff --git a/src/reactor_sim/control.py b/src/reactor_sim/control.py index c1328ec..f4456d6 100644 --- a/src/reactor_sim/control.py +++ b/src/reactor_sim/control.py @@ -45,7 +45,7 @@ class ControlSystem: return self.rod_fraction def set_rods(self, fraction: float) -> float: - self.rod_target = clamp(fraction, 0.0, 0.95) + self.rod_target = self._quantize_manual(fraction) self._advance_banks(self.rod_target, 0.0) LOGGER.info("Manual rod target set to %.3f", self.rod_target) return self.rod_target @@ -127,6 +127,11 @@ class ControlSystem: def _sync_fraction(self) -> None: self.rod_fraction = self.effective_insertion() + def _quantize_manual(self, fraction: float) -> float: + step = constants.ROD_MANUAL_STEP + quantized = round(fraction / step) * step + return clamp(quantized, 0.0, 0.95) + def save_state( self, diff --git a/src/reactor_sim/dashboard.py b/src/reactor_sim/dashboard.py index f00ee5b..b6ee139 100644 --- a/src/reactor_sim/dashboard.py +++ b/src/reactor_sim/dashboard.py @@ -177,10 +177,10 @@ class ReactorDashboard: self._toggle_turbine_unit(idx) elif ch in (ord("+"), ord("=")): # Insert rods (increase fraction) - self._queue_command(ReactorCommand(rod_position=self._clamped_rod(0.05))) + self._queue_command(ReactorCommand(rod_position=self._clamped_rod(constants.ROD_MANUAL_STEP))) elif ch == ord("-"): # Withdraw rods (decrease fraction) - self._queue_command(ReactorCommand(rod_position=self._clamped_rod(-0.05))) + self._queue_command(ReactorCommand(rod_position=self._clamped_rod(-constants.ROD_MANUAL_STEP))) elif ch == ord("["): demand = self._current_demand() - 50.0 self._queue_command(ReactorCommand(consumer_demand=max(0.0, demand))) @@ -710,7 +710,9 @@ class ReactorDashboard: def _clamped_rod(self, delta: float) -> float: new_fraction = self.reactor.control.rod_fraction + delta - return max(0.0, min(0.95, new_fraction)) + step = constants.ROD_MANUAL_STEP + quantized = round(new_fraction / step) * step + return max(0.0, min(0.95, quantized)) def _install_log_capture(self) -> None: if self._log_handler: diff --git a/tests/test_control.py b/tests/test_control.py new file mode 100644 index 0000000..91d0317 --- /dev/null +++ b/tests/test_control.py @@ -0,0 +1,40 @@ +import pytest + +from reactor_sim.control import ControlSystem +from reactor_sim import constants +from reactor_sim.state import CoreState + + +def _core_state() -> CoreState: + return CoreState( + fuel_temperature=300.0, + neutron_flux=1e5, + reactivity_margin=0.0, + power_output_mw=0.0, + burnup=0.0, + ) + + +def test_manual_rods_quantized_to_step(): + control = ControlSystem() + control.manual_control = True + core = _core_state() + + control.set_rods(0.333) + assert control.rod_target == 0.325 + control.update_rods(core, dt=100.0) + assert control.rod_fraction == pytest.approx(0.325, rel=1e-6) + + control.increment_rods(0.014) + assert control.rod_target == pytest.approx(0.35) + control.update_rods(core, dt=100.0) + assert control.rod_fraction == pytest.approx(0.35, rel=1e-6) + + # Clamp upper bound + control.set_rods(1.0) + control.update_rods(core, dt=100.0) + assert control.rod_fraction == 0.95 + + +def test_dashboard_step_constant_exposed(): + assert constants.ROD_MANUAL_STEP == 0.025