Add radial fuel/clad split and Chen-like CHF surrogate
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -8,7 +8,7 @@
|
||||
- [x] Flesh out condenser behavior: vacuum pump limits, cooling water temperature coupling, and dynamic back-pressure with fouling.
|
||||
- [ ] Dashboard polish: compact turbine/generator rows, color critical warnings (SCRAM/heat-sink), and reduce repeated log noise.
|
||||
- [ ] Dashboard multi-page view (F1/F2): retain numeric view on F1; future F2 schematic should mirror real PWR layout with ASCII art, flow/relief status, and minimal animations; add help/status hints and size checks; keep perf sane.
|
||||
- [ ] Core thermal realism: add a simple radial fuel model (pellet/rim/clad temps) with burnup-driven conductivity drop and gap conductance; upgrade CHF/DNB correlation (e.g., Groeneveld/Chen) parameterized by pressure, mass flux, and quality, then calibrate margins.
|
||||
- [x] Core thermal realism: add a simple radial fuel model (pellet/rim/clad temps) with burnup-driven conductivity drop and gap conductance; upgrade CHF/DNB correlation (e.g., Groeneveld/Chen) parameterized by pressure, mass flux, and quality, then calibrate margins.
|
||||
- [ ] 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.
|
||||
- [ ] 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.
|
||||
- [ ] 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.
|
||||
|
||||
@@ -21,6 +21,8 @@ class CoreState:
|
||||
power_output_mw: float # MW thermal
|
||||
burnup: float # fraction of fuel consumed
|
||||
clad_temperature: float | None = None # Kelvin
|
||||
pellet_center_temperature: float | None = None # Kelvin, peak centerline surrogate
|
||||
gap_conductance: float = 1.0 # surrogate factor 0-1
|
||||
dnb_margin: float | None = None # ratio to critical heat flux surrogate
|
||||
subcooling_margin: float | None = None # K until boiling
|
||||
xenon_inventory: float = 0.0
|
||||
@@ -32,6 +34,8 @@ class CoreState:
|
||||
def __post_init__(self) -> None:
|
||||
if self.clad_temperature is None:
|
||||
self.clad_temperature = self.fuel_temperature
|
||||
if self.pellet_center_temperature is None:
|
||||
self.pellet_center_temperature = self.fuel_temperature
|
||||
if self.dnb_margin is None:
|
||||
self.dnb_margin = 1.0
|
||||
if self.subcooling_margin is None:
|
||||
@@ -131,6 +135,8 @@ class PlantState:
|
||||
core_blob.pop("dnb_margin", None)
|
||||
core_blob.pop("subcooling_margin", None)
|
||||
core_blob.setdefault("clad_temperature", core_blob.get("fuel_temperature", 295.0))
|
||||
core_blob.setdefault("pellet_center_temperature", core_blob.get("fuel_temperature", 295.0))
|
||||
core_blob.setdefault("gap_conductance", 1.0)
|
||||
turbines_blob = data.get("turbines")
|
||||
if turbines_blob is None:
|
||||
# Compatibility with previous single-turbine snapshots.
|
||||
|
||||
@@ -119,13 +119,18 @@ class ThermalSolver:
|
||||
heating = (0.002 * max(0.0, power_mw) + 0.01 * max(0.0, residual_power_mw)) * dt
|
||||
cooling = 0.025 * max(0.0, core.fuel_temperature - primary.temperature_out) * dt
|
||||
core.fuel_temperature += heating - cooling
|
||||
# Simple clad/fuel split: clad lags fuel and is cooled by coolant.
|
||||
# Simple radial split: fuel -> clad conduction with burnup-driven conductivity drop and gap penalty.
|
||||
gap_penalty = 1.0 + 2.0 * core.burnup
|
||||
conductivity = 0.03 / gap_penalty
|
||||
conduction = conductivity * max(0.0, core.fuel_temperature - (core.clad_temperature or primary.temperature_out)) * dt
|
||||
clad = core.clad_temperature or primary.temperature_out
|
||||
conduction = 0.02 * max(0.0, core.fuel_temperature - clad) * dt
|
||||
clad_cooling = 0.05 * max(0.0, clad - primary.temperature_out) * dt
|
||||
clad_cooling = 0.06 * max(0.0, clad - primary.temperature_out) * dt
|
||||
clad = max(primary.temperature_out, clad + conduction - clad_cooling)
|
||||
core.fuel_temperature = max(primary.temperature_out, core.fuel_temperature - conduction)
|
||||
# Keep fuel temperature bounded and never below the coolant outlet temperature.
|
||||
# Peak pellet centerline surrogate based on power and burnup conductivity loss.
|
||||
peak_delta = 20.0 * (core.power_output_mw / max(1.0, constants.NORMAL_CORE_POWER_MW)) * (1.0 + core.burnup)
|
||||
core.pellet_center_temperature = min(constants.MAX_CORE_TEMPERATURE, core.fuel_temperature + peak_delta)
|
||||
# Keep temperatures bounded and never below coolant outlet.
|
||||
core.fuel_temperature = min(core.fuel_temperature, constants.MAX_CORE_TEMPERATURE)
|
||||
core.clad_temperature = min(clad, constants.MAX_CORE_TEMPERATURE)
|
||||
core.subcooling_margin = max(0.0, saturation_temperature(primary.pressure) - primary.temperature_out)
|
||||
@@ -176,13 +181,15 @@ class ThermalSolver:
|
||||
self._resolve_secondary_state(secondary)
|
||||
|
||||
def _critical_heat_flux(self, primary: CoolantLoopState) -> float:
|
||||
"""Rough CHF surrogate using mass flux and pressure."""
|
||||
# Use a coarse mass-flux and pressure scaling to emulate higher CHF with more flow/pressure.
|
||||
mass_flux = max(1.0, primary.mass_flow_rate / 50.0) # kg/m2-s surrogate
|
||||
flux_factor = max(0.5, min(3.0, (mass_flux / 200.0) ** 0.5))
|
||||
pressure_factor = 0.5 + 0.5 * min(1.5, primary.pressure / max(0.1, constants.MAX_PRESSURE))
|
||||
base_chf = 1.0e7 # W/m2 surrogate
|
||||
return base_chf * flux_factor * pressure_factor
|
||||
"""CHF surrogate with pressure, mass flux, and subcooling influence (Chen-like)."""
|
||||
area = self._core_surface_area()
|
||||
mass_flux = max(1.0, primary.mass_flow_rate / max(1.0, area)) # kg/s per m2 surrogate
|
||||
pressure_factor = 0.6 + 0.4 * min(1.0, primary.pressure / max(0.1, constants.MAX_PRESSURE))
|
||||
subcool = max(0.0, saturation_temperature(primary.pressure) - primary.temperature_out)
|
||||
subcool_factor = max(0.2, min(1.0, subcool / 30.0))
|
||||
flux_factor = max(0.4, min(3.0, (mass_flux / 500.0) ** 0.5))
|
||||
base_chf = 1.2e7 # W/m2 surrogate baseline
|
||||
return base_chf * flux_factor * pressure_factor * subcool_factor
|
||||
|
||||
def _core_surface_area(self) -> float:
|
||||
# Simple surrogate: area scaling with volume^(2/3)
|
||||
|
||||
Reference in New Issue
Block a user