Add enthalpy-based secondary boil-off and turbine mapping
This commit is contained in:
@@ -16,7 +16,7 @@ from .fuel import FuelAssembly, decay_heat_fraction
|
||||
from .generator import DieselGenerator, GeneratorState
|
||||
from .neutronics import NeutronDynamics
|
||||
from .state import CoolantLoopState, CoreState, PlantState, PumpState, TurbineState
|
||||
from .thermal import ThermalSolver, heat_transfer, saturation_pressure, temperature_rise
|
||||
from .thermal import ThermalSolver, heat_transfer, saturation_pressure, saturation_temperature, temperature_rise
|
||||
from .turbine import SteamGenerator, Turbine
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
@@ -126,6 +126,7 @@ class Reactor:
|
||||
steam_quality=0.0,
|
||||
inventory_kg=primary_nominal_mass * constants.PRIMARY_INVENTORY_TARGET,
|
||||
level=constants.PRIMARY_INVENTORY_TARGET,
|
||||
energy_j=primary_nominal_mass * constants.PRIMARY_INVENTORY_TARGET * constants.COOLANT_HEAT_CAPACITY * ambient,
|
||||
)
|
||||
secondary = CoolantLoopState(
|
||||
temperature_in=ambient,
|
||||
@@ -135,6 +136,7 @@ class Reactor:
|
||||
steam_quality=0.0,
|
||||
inventory_kg=secondary_nominal_mass * constants.SECONDARY_INVENTORY_TARGET,
|
||||
level=constants.SECONDARY_INVENTORY_TARGET,
|
||||
energy_j=secondary_nominal_mass * constants.SECONDARY_INVENTORY_TARGET * constants.COOLANT_HEAT_CAPACITY * ambient,
|
||||
)
|
||||
primary_pumps = [
|
||||
PumpState(active=self.primary_pump_active and self.primary_pump_units[idx], flow_rate=0.0, pressure=0.5)
|
||||
@@ -434,6 +436,17 @@ class Reactor:
|
||||
cooling_drop = min(40.0, max(10.0, 0.2 * excess))
|
||||
state.secondary_loop.temperature_in = max(env, state.secondary_loop.temperature_out - cooling_drop)
|
||||
|
||||
# Keep stored energies consistent with updated temperatures/quality.
|
||||
cp = constants.COOLANT_HEAT_CAPACITY
|
||||
primary_avg = 0.5 * (state.primary_loop.temperature_in + state.primary_loop.temperature_out)
|
||||
state.primary_loop.energy_j = max(0.0, state.primary_loop.inventory_kg * cp * primary_avg)
|
||||
sat_temp_sec = saturation_temperature(max(0.05, state.secondary_loop.pressure))
|
||||
sec_liquid_energy = state.secondary_loop.inventory_kg * cp * min(state.secondary_loop.temperature_out, sat_temp_sec)
|
||||
sec_latent = state.secondary_loop.inventory_kg * state.secondary_loop.steam_quality * constants.STEAM_LATENT_HEAT
|
||||
superheat = max(0.0, state.secondary_loop.temperature_out - sat_temp_sec)
|
||||
sec_superheat = state.secondary_loop.inventory_kg * cp * superheat if state.secondary_loop.steam_quality >= 1.0 else 0.0
|
||||
state.secondary_loop.energy_j = max(0.0, sec_liquid_energy + sec_latent + sec_superheat)
|
||||
|
||||
state.primary_to_secondary_delta_t = max(0.0, state.primary_loop.temperature_out - state.secondary_loop.temperature_in)
|
||||
state.heat_exchanger_efficiency = 0.0 if total_power <= 0 else min(1.0, max(0.0, transferred / max(1e-6, total_power)))
|
||||
|
||||
@@ -578,7 +591,13 @@ class Reactor:
|
||||
if loop.mass_flow_rate <= 0.0 or loop.steam_quality <= 0.0:
|
||||
return
|
||||
steam_mass = loop.mass_flow_rate * loop.steam_quality * constants.SECONDARY_STEAM_LOSS_FRACTION * dt
|
||||
if steam_mass <= 0.0:
|
||||
return
|
||||
prev_mass = max(1e-6, loop.inventory_kg)
|
||||
loop.inventory_kg = max(0.0, loop.inventory_kg - steam_mass)
|
||||
# Scale stored energy with the remaining mass to keep specific enthalpy consistent.
|
||||
ratio = max(0.0, loop.inventory_kg) / prev_mass
|
||||
loop.energy_j *= ratio
|
||||
nominal = self._nominal_inventory(constants.SECONDARY_LOOP_VOLUME_M3)
|
||||
loop.level = min(1.2, max(0.0, loop.inventory_kg / nominal)) if nominal > 0 else 0.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user