Equipment Components¶
WaterHeater¶
- class ecoengine.objects.components.heating.WaterHeater.WaterHeater(performance_map: PerformanceMap | None, control_schedule: list[str] | None, control_map: dict[str, Controls] | None, fuel_type: Literal['electric', 'gas'] = 'electric')[source]¶
Bases:
objectRepresents a water heater. And owns a PerformanceMap, a control schedule, and a control map. Tracks its own active/inactive state. The Performance map can be used to simulate heat pump, gas fired, or electric resitance.
Control schedule and map¶
- control_schedulelist[str]
24-element list (one entry per hour of the day). Each value is a string key into control_map, selecting which Controls object is active during that hour. Standard keys are
"normal","loadUp", and"shed".- control_mapdict[str, Controls]
Maps schedule keys to Controls objects. For a simple single-mode system use
{"normal": controls}withcontrol_schedule = ["normal"] * 24.When the map contains only
"normal", the system is not load shifting. When it also contains"loadUp"and/or"shed"keys, load-shift sizing and simulation logic is activated.
Construction¶
Use one of the factory class methods rather than calling __init__ directly:
- WaterHeater.from_nominal_capacity(nominal_capacity_kbtuh, control_schedule, control_map)
Creates a NominalPerformanceMap (constant-output placeholder). Use this during preliminary sizing before a real equipment model is selected.
- WaterHeater.from_model_name(model_name, control_schedule, control_map)
Loads a PerformanceMap from the equipment model registry by name. Use this when the specific HPWH model is known.
- __init__(performance_map: PerformanceMap | None, control_schedule: list[str] | None, control_map: dict[str, Controls] | None, fuel_type: Literal['electric', 'gas'] = 'electric') None[source]¶
- Parameters:
performance_map (PerformanceMap | None) – Performance map for this HPWH model. If None, all capacity/power queries return None. Prefer the factory class methods.
control_schedule (list[str] | None) – 24-element list mapping each hour to a key in control_map. None when no control logic is configured.
control_map (dict[str, Controls] | None) – Maps schedule strings to Controls objects. None when no control logic is configured.
fuel_type ("electric" | "gas") – Fuel source for this heater. Default is “electric”.
- classmethod from_nominal_capacity(nominal_capacity_kbtuh: float, control_schedule: list[str] | None, control_map: dict[str, Controls] | None, fuel_type: Literal['electric', 'gas'] = 'electric') WaterHeater[source]¶
Create a WaterHeater with a constant-capacity placeholder performance map.
The heater will report nominal_capacity_kbtuh for every OAT and water temperature query. Use this during preliminary sizing before a specific HPWH model has been selected.
- Parameters:
nominal_capacity_kbtuh (float) – Fixed heating output capacity [kBTU/hr].
control_schedule (list[str] | None) – 24-element list mapping each hour to a key in control_map.
control_map (dict[str, Controls] | None) – Maps schedule strings to Controls objects.
fuel_type ("electric" | "gas") – Fuel source for this heater. Default is “electric”.
- Return type:
- classmethod from_model_name(model_name: str, control_schedule: list[str] | None, control_map: dict[str, Controls] | None, num_units: int = 1, design_inlet_temp_f: float = 50.0, nominal_capacity_kbtuh: float | None = None, fuel_type: Literal['electric', 'gas'] = 'electric') WaterHeater[source]¶
Create a WaterHeater by loading its PerformanceMap from the equipment model registry using the model name string.
- Parameters:
model_name (str) – Equipment model identifier as it appears in the model registry (e.g.
'Rheem_PROPH80_T2_RH380-30').control_schedule (list[str] | None) – 24-element list mapping each hour to a key in control_map.
control_map (dict[str, Controls] | None) – Maps schedule strings to Controls objects.
num_units (int) – Number of identical units deployed; outputs scale linearly. Default 1.
design_inlet_temp_f (float) – Cold-water inlet temperature used when not supplied per-call. Default 50 °F.
nominal_capacity_kbtuh (float | None) – Total system capacity for ER fallback sizing. Default None.
fuel_type ("electric" | "gas") – Fuel source for this heater. Default is “electric”.
- Return type:
- get_controls_for_hour(hour_of_day: int) Controls | None[source]¶
Return the Controls object active during the given hour.
- get_capacity_kbtuh(oat_f: float, outlet_temp_f: float, inlet_temp_f: float | None = None) float | None[source]¶
Return heating output capacity [kBTU/hr] at current conditions.
- get_power_in_kw(oat_f: float, outlet_temp_f: float, inlet_temp_f: float | None = None) float | None[source]¶
Return electrical power input [kW] at current conditions.
- get_output_kbtuh(oat_f: float, outlet_temp_f: float, inlet_temp_f: float | None = None) float[source]¶
Return actual heating output this timestep: capacity if active, 0 if not.
- get_outlet_temp_f(hour_of_day: int) float[source]¶
Look up the active Outlet Temperature for Controls for the given hour
- Parameters:
hour_of_day (int) – Hour of the day (0-23), used to select the active Controls from the control schedule.
- Raises:
ValueError – If no Controls are configured for the given hour.
- update_state(storage_tank: StorageTank, hour_of_day: int) None[source]¶
Look up the active Controls for the given hour, then update active/inactive state based on current tank condition.
If the heater is currently ON, check should_turn_off(). If the heater is currently OFF, check should_turn_on().
- Parameters:
storage_tank (StorageTank)
hour_of_day (int) – Hour of the day (0-23), used to select the active Controls from the control schedule.
- Raises:
ValueError – If no Controls are configured for the given hour.
Controls¶
- class ecoengine.objects.components.heating.Controls.Controls(on_sensor_fract: float, on_trigger_t_f: float, off_sensor_fract: float, off_trigger_t_f: float, outlet_temp_f: float)[source]¶
Bases:
objectSetpoints for one operating mode of a WaterHeater.
A WaterHeater carries a control_schedule (24-length list of int) and a control_map (dict[int, Controls]) so that different Controls objects can be active at different hours of the day. Each Controls object represents a single mode’s setpoints — there is no load-shift scheduling logic here.
Sensor position convention¶
- ON sensor (upper, on_sensor_fract >= off_sensor_fract):
Heater turns ON when this sensor drops below on_trigger_t_f.
- OFF sensor (lower):
Heater turns OFF when this sensor rises above off_trigger_t_f.
- __init__(on_sensor_fract: float, on_trigger_t_f: float, off_sensor_fract: float, off_trigger_t_f: float, outlet_temp_f: float) None[source]¶
- Parameters:
on_sensor_fract (float) – Fractional tank height of the ON (upper) temperature sensor (0 = bottom, 1 = top). Must be >= off_sensor_fract.
on_trigger_t_f (float) – Temperature at on_sensor_fract that triggers the heater ON [°F].
off_sensor_fract (float) – Fractional tank height of the OFF (lower) temperature sensor (0 = bottom, 1 = top). Must be <= on_sensor_fract.
off_trigger_t_f (float) – Temperature at off_sensor_fract that triggers the heater OFF [°F].
outlet_temp_f (float) – Target hot-water outlet temperature for this operating mode [°F]. Typically equal to storage_temp_f for normal operation, but may differ for load-up or other modes.
- should_turn_on(storage_tank: StorageTank) bool[source]¶
Return True if the heater should turn on given the current tank state.
The heater turns ON when the upper (on) sensor drops below
on_trigger_t_f.- Parameters:
storage_tank (StorageTank)
- Return type:
- should_turn_off(storage_tank: StorageTank) bool[source]¶
Return True if the heater should turn off given the current tank state.
The heater turns OFF when the lower (off) sensor rises to or above
off_trigger_t_f.- Parameters:
storage_tank (StorageTank)
- Return type:
Performance Maps¶
- class ecoengine.objects.components.heating.PerformanceMap.PerformanceMap(map_data: object, model_name: str = '')[source]¶
Bases:
objectWraps HPWH performance map data to predict real-world heating capacity and power input as a function of outdoor air temperature and water temperature.
Construction¶
Use the factory class method rather than calling __init__ directly:
PerformanceMap.from_model_name(model_name, ...)Loads the appropriate concrete subclass from the equipment model registry:
PklPerformanceMap— pickle-based LinearND interpolator (lab data)HPWHsimPerformanceMap— coefficient-based polynomial (HPWHsim model)
NominalPerformanceMap(nominal_capacity_kbtuh)Constant-output placeholder for use during preliminary sizing. Do not change this subclass.
API notes¶
outlet_temp_f— outlet water temperature leaving the heater [°F]. In a stratified-tank simulation this istop_temp_f(the storage setpoint).inlet_temp_f— cold water entering the heater from the tank bottom [°F]. Optional; falls back to thedesign_inlet_temp_fstored at construction when not provided.
- classmethod from_model_name(model_name: str, num_units: int = 1, design_inlet_temp_f: float = 50.0, nominal_capacity_kbtuh: float | None = None) PerformanceMap[source]¶
Load a PerformanceMap from the equipment model registry by name.
Returns a
PklPerformanceMapwhen pickle interpolator files are available for the model, otherwise anHPWHsimPerformanceMap.- Parameters:
model_name (str) – Equipment model identifier as it appears in
maps.jsonnum_units (int) – Number of identical heat pump units deployed. All capacity and power outputs are scaled by this factor. Default 1.
design_inlet_temp_f (float) – Cold-water inlet temperature used as fallback when
inlet_temp_fis not passed toget_capacity_kbtuh/get_power_in_kw. Default 50 °F.nominal_capacity_kbtuh (float | None) – Total system output capacity at design conditions [kBTU/hr]. Required for the ER fallback path (OAT below performance-map minimum); if None the fallback returns 0 kBTU/hr.
- Raises:
ValueError – If
model_nameis not found in the registry, or the registry entry has neither pkl nor perfmap data.
- get_capacity_kbtuh(oat_f: float, outlet_temp_f: float, inlet_temp_f: float | None = None) float | None[source]¶
Return heating output [kBTU/hr] at the given conditions.
- get_power_in_kw(oat_f: float, outlet_temp_f: float, inlet_temp_f: float | None = None) float | None[source]¶
Return electrical power input [kW] at the given conditions.
- class ecoengine.objects.components.heating.PerformanceMap.NominalPerformanceMap(nominal_capacity_kbtuh: float, model_name: str = '')[source]¶
Bases:
PerformanceMapConstant-output performance map for use during preliminary sizing, when a real equipment model is not yet selected.
Every capacity query returns the fixed nominal_capacity_kbtuh regardless of outdoor air temperature or water temperature — analogous to how ClimateZone.from_design_conditions() returns a constant OAT for all timesteps. Power and COP queries remain stubs until a real map is assigned.
- class ecoengine.objects.components.heating.PerformanceMap.PklPerformanceMap(model_name: str, output_interpolator: object, input_interpolator: object, unique_oats: list[float], inTs_and_outTs_by_oat: list[list[tuple[float, list[float]]]], inlet_min: float, inlet_max: float, default_out_high_kw: float, default_in_high_kw: float, default_out_low_kw: float, default_in_low_kw: float, num_units: int, is_two_input: bool, secondary_hx: bool, hx_increase: float, nominal_capacity_kbtuh: float | None, design_inlet_temp_f: float)[source]¶
Bases:
PerformanceMapPerformance map backed by scipy
LinearNDInterpolatorpickle files.Single-pass models interpolate over (inlet_temp, outlet_temp, OAT). Multipass and certain named models use a two-input grid (inlet_temp, OAT).
Out-of-bounds handling (mirrors PrefMapTracker behaviour)¶
OAT ≥ oat_max → use stored default high-OAT capacity/power values.
OAT < oat_min → Electric Resistance fallback: output = input =
nominal_capacity_kbtuh / num_units(COP = 1).NaN in range → attempt to snap inlet/outlet to nearest valid grid point (
_force_closest); if still NaN, assume COP = 1.5.
- __init__(model_name: str, output_interpolator: object, input_interpolator: object, unique_oats: list[float], inTs_and_outTs_by_oat: list[list[tuple[float, list[float]]]], inlet_min: float, inlet_max: float, default_out_high_kw: float, default_in_high_kw: float, default_out_low_kw: float, default_in_low_kw: float, num_units: int, is_two_input: bool, secondary_hx: bool, hx_increase: float, nominal_capacity_kbtuh: float | None, design_inlet_temp_f: float) None[source]¶
- get_capacity_kbtuh(oat_f: float, outlet_temp_f: float, inlet_temp_f: float | None = None) float[source]¶
Return heating output [kBTU/hr] at the given conditions.
- class ecoengine.objects.components.heating.PerformanceMap.HPWHsimPerformanceMap(model_name: str, perfmap: list[dict], num_units: int, is_multipass: bool, nominal_capacity_kbtuh: float | None, design_inlet_temp_f: float)[source]¶
Bases:
PerformanceMapPerformance map using the HPWHsim polynomial coefficient model.
Multi-entry maps (most models)¶
A list of OAT bracket entries, each with
T_F,COP_coeffs(3 terms), andinputPower_coeffs(3 terms, producing Watts).At each bracket:
value = c0 + c1·x + c2·x²wherexis the condenser (inlet) water temperature. The result is linearly interpolated between the two bracketing OAT entries.Single-entry maps (rare)¶
Single-pass: 11-term full quadratic in (OAT, outlet_T, inlet_T).
Multipass: 6-term quadratic in (OAT, inlet_temp_f).
OAT below the minimum bracket → Electric Resistance fallback (COP = 1).
- __init__(model_name: str, perfmap: list[dict], num_units: int, is_multipass: bool, nominal_capacity_kbtuh: float | None, design_inlet_temp_f: float) None[source]¶
- get_capacity_kbtuh(oat_f: float, outlet_temp_f: float, inlet_temp_f: float | None = None) float[source]¶
Return heating output [kBTU/hr] at the given conditions.
Storage Tanks¶
- class ecoengine.objects.components.storage.StorageTank.StorageTank[source]¶
Bases:
ABCAbstract base class for all storage tank models.
Defines the interface that WaterHeater, Controls, and DHWSystem rely on. Concrete subclasses implement the thermal model (stratified vs. mixed).
Every subclass must expose
total_volume_galas a plain attribute and implement the five simulation methods listed below.- abstractmethod initialize(storage_temp_f: float, cold_temp_f: float, percent_useable: float) None[source]¶
Set initial tank thermal state before a simulation begins.
- abstractmethod get_temperature_at_fraction(fract: float) float[source]¶
Return water temperature at fractional tank height (0=bottom, 1=top). Used by Controls to decide whether to fire the heater.
- abstractmethod get_usable_volume_supplyT_gal(supply_temp_f: float) float[source]¶
Return gallons currently at or above supply temperature.
- abstractmethod draw(volume_supplyT_gal: float, cold_temp_f: float, supply_temp_f: float, outlet_temp_f: float) None[source]¶
Remove DHW demand from the tank and replace with cold make-up water.
- abstractmethod heat(kbtuh: float, duration_min: float, outlet_temp_f: float) None[source]¶
Apply heat from active water heaters for one timestep.
- abstractmethod add_recirc_return(flow_gpm: float, return_temp_f: float, duration_min: float, supply_temp_f: float | None = None) None[source]¶
Apply recirculation loop heat loss to the tank.
- Parameters:
flow_gpm (float) – Recirculation loop flow rate [GPM].
return_temp_f (float) – Temperature of water returning from the recirc loop [°F].
duration_min (float) – Length of the timestep [minutes].
supply_temp_f (float | None) – DHW delivery temperature [°F]. This determines how much heat the recirc loop actually loses:
loss = flow × rho_cp × (supply_temp - return_temp). IfNone, defaults tooutlet_temp_f(the tank’s current hot-end temperature), which gives the correct result for TM tanks where the supply and storage temperatures are equal. Pass explicitly for primary storage tanks (SPRTP) wheresupply_temp_f < storage_temp_f.
- abstractmethod get_average_draw_temp_f(draw_gal: float) float[source]¶
Return the volume-weighted average temperature of the top
draw_galphysical gallons.Used by SwingSystem to determine the effective feed temperature from the primary storage tank when the hot zone may be partially depleted.
- abstractmethod draw_physical_gal(gal: float, inlet_temp_f: float, supply_temp_f: float | None = None) None[source]¶
Remove
galphysical gallons from the top of the tank and replace with cold make-up water at the bottom.Unlike
draw(), no supply-temperature conversion is applied — the caller provides physical gallons directly.supply_temp_fis used by stratified tanks to floor_delta_galat the no-usable-hot-water state rather than the absolute fully-cold state.
- class ecoengine.objects.components.storage.StratifiedTank.StratifiedTank(total_volume_gal: float, strat_slope: float = 2.8)[source]¶
Bases:
StorageTankStratified storage tank model using a continuous linear temperature profile.
Temperature profile¶
The tank is hot on top and cold on bottom. Temperature as a function of height is described by three regions:
- T(x_pct) = inlet_temp_f x_pct <= x_cold
= strat_slope * (x_pct + shift_pct) + strat_inter x_cold < x_pct < x_hot = outlet_temp_f x_pct >= x_hot
where x_pct is height as a percentage (0 = bottom, 100 = top) and shift_pct = delta_gal / total_volume_gal * 100 translates the gallons-based running tally into a percentage shift of the thermocline.
delta_gal bookkeeping¶
_delta_galtracks how much the thermocline has shifted from its initialized position, measured in gallons:draw()decreases_delta_gal— cold water enters the bottom, the thermocline rises (less hot water available).heat()increases_delta_gal— cold water is heated and moves to the top, the thermocline falls (more hot water available).
- __init__(total_volume_gal: float, strat_slope: float = 2.8) None[source]¶
- Parameters:
total_volume_gal (float) – Total physical tank volume [gallons].
strat_slope (float) – Temperature gradient through the transition zone between cold and hot layers [°F per percentage-point of tank height]. Higher values mean a sharper thermocline (better stratification). Defaults to 2.8, calibrated for a standard 12-node tank. DHWSystem subclasses that model different schematics may set a different value here.
- initialize(storage_temp_f: float, cold_temp_f: float, percent_useable: float) None[source]¶
Set initial temperature stratification profile.
Places the cold boundary (where T = cold_temp_f) at
(1 - percent_useable) * 100percent height, so the toppercent_useablefraction of the tank starts at storage temperature.
- get_temperature_at_fraction(fract: float) float[source]¶
Return interpolated water temperature at a fractional tank height.
- get_usable_volume_supplyT_gal(supply_temp_f: float) float[source]¶
Return gallons of water currently at or above supply temperature.
- draw(volume_supplyT_gal: float, cold_temp_f: float, supply_temp_f: float, outlet_temp_f: float) None[source]¶
Remove hot water from the top of the tank and replace with cold at the bottom, representing DHW delivery to building occupants.
When the hot zone at
outlet_temp_fis deep enough to cover the entire draw, the standard conversion is used:- physical_vol = volume_supplyT_gal
× (supply_temp_f − cold_temp_f) / (outlet_temp_f − cold_temp_f)
When the hot zone is partially depleted, the drawn block dips into the transition zone and its average temperature falls below
outlet_temp_f. In that case,get_average_draw_temp_fis used to binary-search for the physical volume whose actual supply-temp yield equals the demand.If even drawing the full tank cannot satisfy the demand (true outage), the entire tank volume is drawn and the caller detects the shortfall via
get_usable_volume_supplyT_gal() == 0.Decreases
_delta_gal(thermocline rises → less hot water).- Parameters:
volume_supplyT_gal (float) – DHW demand in supply-temperature gallons [gal].
cold_temp_f (float) – Incoming cold water temperature [°F]. Stored for subsequent temperature queries.
supply_temp_f (float) – System supply (delivery) temperature [°F].
outlet_temp_f (float) – Current maximum hot water temperature at the tank outlet [°F].
- heat(kbtuh: float, duration_min: float, outlet_temp_f: float) None[source]¶
Apply heat from active water heaters for one timestep.
Models the HPWH drawing cold water from the bottom of the tank, heating it to
outlet_temp_f, and returning it to the top.Increases
_delta_gal(thermocline falls → more hot water), capped when the tank is fully heated.
- add_recirc_return(flow_gpm: float, return_temp_f: float, duration_min: float, supply_temp_f: float | None = None) None[source]¶
Apply recirculation loop heat loss to the tank.
The recirc loop draws water from the supply line at
supply_temp_f, circulates it through the building, and returns it atreturn_temp_f. The heat lost in the loop —flow × rho_cp × (supply_temp - return_temp)— is extracted from storage. Total tank volume is unchanged; only the energy content (and thus the hot-zone depth) decreases.- Parameters:
flow_gpm (float) – Recirculation loop flow rate [GPM].
return_temp_f (float) – Temperature of water returning from the recirc loop [°F].
duration_min (float) – Length of the timestep [minutes].
supply_temp_f (float | None) – DHW delivery temperature [°F]. Defaults to
outlet_temp_f(correct for TM tanks where supply == storage temperature). Pass explicitly whensupply_temp_f < storage_temp_f(e.g. SPRTP primary storage) to avoid over-counting the heat loss.
- get_average_draw_temp_f(draw_gal: float) float[source]¶
Return the volume-weighted average temperature of the top
draw_galphysical gallons.Analytically integrates the stratified temperature profile over the draw zone (top of tank downward), splitting the zone into cold, transition, and hot sub-regions.
- draw_physical_gal(gal: float, inlet_temp_f: float, supply_temp_f: float | None = None) None[source]¶
Remove
galphysical gallons from the top of the tank and replace with cold make-up water at the bottom.Decreases
_delta_galdirectly by the physical volume (thermocline rises). Unlikedraw(), no supply-temperature conversion is applied — the caller provides physical gallons directly.- Parameters:
gal (float) – Physical gallons to remove [gal].
inlet_temp_f (float) – Incoming cold water temperature [°F].
supply_temp_f (float | None) – Hot-water delivery temperature [°F]. When provided,
_delta_galis floored at the state where the tank top is atsupply_temp_f(no more usable hot water). WhenNone, the absolute fully-cold floor (inlet_temp_fat top) is used instead.
- get_stratification_factor(on_fract: float, supply_temp_f: float, storage_temp_f: float) float[source]¶
Return the stratification factor: the fraction of total tank volume that is usable at supply temperature given the ON aquastat position.
- Matches the sizing formula used in DHWSystem:
strat_factor_pct = (storage_temp_f - supply_temp_f) / strat_slope usable_fraction = strat_factor_pct * (1 - on_fract)
- class ecoengine.objects.components.storage.MixedStorageTank.MixedStorageTank(total_volume_gal: float)[source]¶
Bases:
StorageTankFully-mixed (uniform temperature) storage tank model.
Unlike the stratified StratifiedTank, the entire volume is assumed to be at a single uniform temperature at all times. This matches the behaviour of a temperature-maintenance (TM) tank in a parallel-loop system, where the tank is small and continuously stirred by the recirc loop.
Interface contract¶
get_temperature_at_fraction()returns the same value for any height.get_usable_volume_supplyT_gal()returns the full tank volume when the temperature is at or above supply, otherwise 0.heat()raises the tank temperature proportionally to heat added.draw()cools the tank proportionally to cold make-up water added.add_recirc_return()applies a net temperature drop equal to the recirc heat loss for the timestep.
- __init__(total_volume_gal: float) None[source]¶
- Parameters:
total_volume_gal (float) – Total physical tank volume [gallons].
- initialize(storage_temp_f: float, cold_temp_f: float, percent_useable: float) None[source]¶
Set the uniform tank temperature before a simulation begins.
For a fully-mixed tank,
percent_useabledoes not have a spatial meaning; the tank is simply initialized atstorage_temp_f.
- get_temperature_at_fraction(fract: float) float[source]¶
Return the uniform tank temperature (identical at all heights).
- get_usable_volume_supplyT_gal(supply_temp_f: float) float[source]¶
Return full tank volume if at or above supply temperature, else 0.
- draw(volume_supplyT_gal: float, cold_temp_f: float, supply_temp_f: float, outlet_temp_f: float) None[source]¶
Remove DHW demand from the tank and replace with cold make-up water.
Uses a simple energy-balance mix: physically removes hot water at the current tank temperature and replaces it with cold water, cooling the tank uniformly.
The physical volume removed is adjusted for the temperature difference between storage and supply (the same conversion used by StratifiedTank).
- Parameters:
volume_supplyT_gal (float) – DHW demand in supply-temperature gallons [gal].
cold_temp_f (float) – Incoming cold water temperature [°F].
supply_temp_f (float) – System supply (delivery) temperature [°F].
outlet_temp_f (float) – Current hot water delivery temperature — used for physical-volume conversion (same formula as StratifiedTank).
- heat(kbtuh: float, duration_min: float, outlet_temp_f: float) None[source]¶
Apply heat from active water heaters for one timestep.
Raises the uniform tank temperature proportionally to heat added, capped at
outlet_temp_f.
- add_recirc_return(flow_gpm: float, return_temp_f: float, duration_min: float) None[source]¶
Apply the net temperature drop from recirc loop losses.
The recirc loop continuously draws hot water from the tank at the current temperature and returns it cooled to
return_temp_f. For a fully-mixed tank, the net effect is a uniform temperature drop computed from the energy removed:ΔT = flow_gpm × duration_min × (return_temp_f − tank_temp) / total_volume_gal
(ΔT is negative when return_temp_f < tank_temp, i.e., heat is lost.)
- apply_fixed_heat_loss_kbtuh(kbtuh: float, duration_min: float) None[source]¶
Apply a fixed heat loss rate to the tank regardless of current temperature.
Used by the swing tank simulation to apply recirculation heat loss at the same constant rate used during sizing (
RecircSystem.get_recirc_loss_kbtuh), so that sizing and runtime physics remain consistent.
- mix_primary_inflow(gal: float, primary_temp_f: float) None[source]¶
Model primary hot water flowing into the swing tank while the same volume exits to the building.
The total tank volume is unchanged; the incoming primary water at
primary_temp_fdisplaces an equal volume of the existing tank contents, raising or lowering the uniform temperature via an energy balance:T_new = (gal × primary_temp + (V - gal) × T_curr) / V
- get_average_draw_temp_f(draw_gal: float) float[source]¶
Return the uniform tank temperature (fully mixed — no stratification).
- draw_physical_gal(gal: float, inlet_temp_f: float, supply_temp_f: float | None = None) None[source]¶
Remove
galphysical gallons and replace with cold make-up water.For a fully-mixed tank this is an energy-balance mix: the removed hot water is replaced by
galgallons of cold water atinlet_temp_f.supply_temp_fis accepted for interface compatibility but unused — a fully-mixed tank has no thermocline to floor.