Add boron reactivity trim and power measurement smoothing

This commit is contained in:
Codex Agent
2025-11-28 20:28:46 +01:00
parent 41b2206a0f
commit 5469f142a7
7 changed files with 44 additions and 3 deletions

View File

@@ -3,6 +3,7 @@
- Reactor model: two-loop but tuned to RBMK-like pressures (nominal ~7 MPa for both loops). Loop pressure clamps to saturation baseline when pumps are off; pumps ramp flow/pressure over spool time when stopping or starting.
- Feedwater & level: secondary steam drum uses shrink/swell-aware level sensing to drive a feedwater valve; makeup flow scales with steam draw toward the target level instead of instant inventory clamps.
- Chemistry/fouling: plant tracks dissolved O2/boron/sodium; impurities plus high temp/steam draw increase HX fouling (reduces UA) and add condenser fouling/back-pressure. Oxygen degasses with steam; impurity ingress accelerates when venting.
- Reactivity bias: boron ppm now biases shutdown reactivity; a very slow trim nudges boron toward the power setpoint after ~300s of operation.
- Turbines: produce zero output unless steam quality is present and effective steam flow is >10 kg/s. Turbine panel shows steam availability (enthalpy × quality × mass flow) and steam enthalpy instead of loop pressure; condenser pressure/temperature/fouling shown with nominal bounds.
- Generators: two diesel units, rated 50 MW, spool time 10s. Auto mode default `False`; manual toggles b/v. Auto stops when no load. Relief valves toggles l (primary) / ; (secondary) and displayed per loop.
- Pumps: per-unit controls g/h (primary), j/k (secondary). Flow/pressure ramp down over spool when pumps stop. Pump status thresholds use >0.1 kg/s to show STOPPING.

View File

@@ -1,7 +1,7 @@
## C.O.R.E. feature set
- **Core physics**: point-kinetics with per-bank delayed neutron precursors, temperature feedback, fuel burnup penalty, xenon/iodine buildup with decay and burn-out, and rod-bank worth curves.
- **Rod control**: three rod banks with weighted worth; auto controller chases 3 GW setpoint with safety backoff; manual mode with staged bank motion and SCRAM; state persists across runs.
- **Rod control**: three rod banks with weighted worth; auto controller chases 3 GW setpoint with safety backoff and filtered power feedback; manual mode with staged bank motion and SCRAM; state persists across runs. Soluble boron bias contributes slow negative reactivity and trims toward the setpoint.
- **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 plus steam-drum mass/energy balance with latent heat and a shrink/swell-aware feedwater valve controller; dissolved oxygen/sodium drive HX fouling that reduces effective UA.
- **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.

View File

@@ -12,6 +12,7 @@
- [ ] Transient protection ladder: add dP/dt and dT/dt trips for SG overfill/depressurization and rod-run-in alarms; implement graded warn/arm/trip stages surfaced on the dashboard.
- [x] Chemistry & fouling: track dissolved oxygen/boron and corrosion/fouling that degrade HX efficiency and condenser vacuum; let feedwater temperature/chemistry affect steam purity/back-pressure.
- [x] Balance-of-plant dynamics: steam-drum level controller with shrink/swell, feedwater valve model, turbine throttle governor/overspeed trip, and improved load-follow tied to grid demand ramps.
- [ ] Neutronics/feedback smoothing: add detector/measurement lag, fuel→clad→coolant transport delays, shared boron trim for fine regulation, and retune rod gains/rate limits to reduce power hunting while keeping safety margins intact.
- [ ] Component wear & maintenance: make wear depend on duty cycle and off-nominal conditions (cavitation, high ΔP, high temp); add preventive maintenance scheduling and show next-due in the dashboard.
- [ ] Scenarios & tooling: presets for cold start, load-follow, and fault injection (pump fail, relief stuck) with seedable randomness; snapshot diff tooling to compare saved states.
- [ ] Incremental realism plan:

View File

@@ -77,6 +77,8 @@ CHEM_SODIUM_DEFAULT_PPM = 5.0
HX_FOULING_RATE = 1e-5 # fouling increment per second scaled by impurities/temp
HX_FOULING_HEAL_RATE = 5e-6 # cleaning/settling when cool/low steam
HX_FOULING_MAX_PENALTY = 0.25 # fractional UA loss cap
BORON_WORTH_PER_PPM = 8e-6 # delta rho per ppm relative to baseline boron
BORON_TRIM_RATE_PPM_PER_S = 0.02 # slow boron trim toward setpoint when near target
# Threshold inventories (event counts) for flagging common poisons in diagnostics.
KEY_POISON_THRESHOLDS = {
"Xe": 1e20, # xenon

View File

@@ -24,6 +24,7 @@ class ControlSystem:
manual_control: bool = False
rod_banks: list[float] = field(default_factory=lambda: [0.5, 0.5, 0.5])
rod_target: float = 0.5
_filtered_power_mw: float = 0.0
def update_rods(self, state: CoreState, dt: float) -> float:
if not self.rod_banks or len(self.rod_banks) != len(constants.CONTROL_ROD_BANK_WEIGHTS):
@@ -35,7 +36,22 @@ class ControlSystem:
self.rod_target = clamp(self.rod_fraction, 0.0, 0.95)
self._advance_banks(self.rod_target, dt)
return self.rod_fraction
error = (state.power_output_mw - self.setpoint_mw) / self.setpoint_mw
raw_power = state.power_output_mw
# Begin filtering once we're in the vicinity of the setpoint to avoid chasing noise.
if self._filtered_power_mw <= 0.0:
self._filtered_power_mw = raw_power
if raw_power > 0.7 * self.setpoint_mw:
alpha = clamp(dt / 6.0, 0.0, 1.0) # ~6s time constant
self._filtered_power_mw += alpha * (raw_power - self._filtered_power_mw)
measured_power = self._filtered_power_mw
else:
measured_power = raw_power
error = (measured_power - self.setpoint_mw) / self.setpoint_mw
# Deadband near setpoint to prevent dithering; only apply when we're close to target.
if measured_power > 0.9 * self.setpoint_mw and abs(error) < 0.01:
self._advance_banks(self.rod_target, dt)
return self.rod_fraction
# When power is low (negative error) withdraw rods; when high, insert them.
adjustment = error * 0.35
adjustment = clamp(adjustment, -constants.CONTROL_ROD_SPEED * dt, constants.CONTROL_ROD_SPEED * dt)

View File

@@ -25,10 +25,11 @@ def xenon_poisoning(flux: float) -> float:
@dataclass
class NeutronDynamics:
base_shutdown_bias: float = -0.014
shutdown_bias: float = -0.014
beta_effective: float = 0.0065
delayed_decay_const: float = 0.08 # 1/s effective precursor decay
external_source_coupling: float = 1e-6
shutdown_bias: float = -0.014
iodine_yield: float = 1e-6 # inventory units per MW*s
iodine_decay_const: float = 1.0 / 66000.0 # ~18h
xenon_decay_const: float = 1.0 / 33000.0 # ~9h

View File

@@ -64,6 +64,8 @@ class Reactor:
# Balance-of-plant controls
self.feedwater_valve = 0.5
self._last_steam_out_kg_s = 0.0
# Slow chemistry/boron trim control
self._boron_trim_active = True
if not self.primary_pump_units or len(self.primary_pump_units) != 2:
self.primary_pump_units = [True, True]
if not self.secondary_pump_units or len(self.secondary_pump_units) != 2:
@@ -197,6 +199,9 @@ class Reactor:
state.core
)
self.neutronics.update_poisons(state.core, dt)
# Apply soluble boron reactivity bias (slow trim).
boron_delta = state.boron_ppm - constants.CHEM_BORON_DEFAULT_PPM
self.neutronics.shutdown_bias = self.neutronics.base_shutdown_bias - boron_delta * constants.BORON_WORTH_PER_PPM
self.neutronics.step(state.core, rod_fraction, dt, external_source_rate=decay_neutron_source, rod_banks=self.control.rod_banks)
prompt_power, fission_rate, fission_event = self.fuel.prompt_energy_rate(
@@ -431,6 +436,7 @@ class Reactor:
self._apply_secondary_boiloff(state, dt)
self._update_secondary_level(state, dt)
self._update_chemistry(state, dt)
self._apply_boron_trim(state, dt)
steam_draw = self._step_turbine_bank(state, transferred, dt)
if steam_draw > 0.0:
@@ -658,6 +664,20 @@ class Reactor:
loop.level = min(1.2, max(0.0, loop.inventory_kg / nominal_mass))
self._last_steam_out_kg_s = steam_out
def _apply_boron_trim(self, state: PlantState, dt: float) -> None:
"""Slow soluble boron trim to hold power near setpoint; acts only near target."""
if not self._boron_trim_active or self.control.manual_control or self.shutdown:
return
if state.time_elapsed < 300.0:
return
if self.control.setpoint_mw <= 0.0:
return
error = (state.core.power_output_mw - self.control.setpoint_mw) / self.control.setpoint_mw
if abs(error) < 0.02:
return
delta = constants.BORON_TRIM_RATE_PPM_PER_S * error * dt
state.boron_ppm = min(constants.CHEM_MAX_PPM, max(0.0, state.boron_ppm + delta))
def _update_chemistry(self, state: PlantState, dt: float) -> None:
"""Track dissolved species and fouling impacts on HX and condenser."""
env = constants.ENVIRONMENT_TEMPERATURE