Subsystems¶
Composable subsystems that the *_stc_* and *_pv_ess model
variants plug onto the core cycle, tank backends, plus the
UV-treatment subsystem.
Solar thermal collector¶
- class tmhp.subsystems.SolarThermalCollector(A_stc=2.0, stc_tilt=35.0, stc_azimuth=180.0, A_stc_pipe=2.0, alpha_stc=0.95, h_o_stc=15.0, h_r_stc=2.0, k_ins_stc=0.03, x_air_stc=0.01, x_ins_stc=0.05, preheat_start_hour=6.0, preheat_end_hour=18.0, dV_stc_w=0.001, E_stc_pump=50.0)[source]¶
Bases:
objectSolar Thermal Collector (STC) — pure physics engine.
Bundles collector geometry, optical and thermal properties, and pump parameters. This class is a stateless physics calculator: given physical inputs it returns a performance dict. All simulation orchestration (activation logic, result assembly, exergy calculation) is the responsibility of the scenario class that uses this engine.
The public API consists of:
calc_performance()— single-timestep thermal performanceis_preheat_on()— schedule check
- Parameters:
A_stc (float) – Collector gross area [m²].
stc_tilt (float) – Tilt from horizontal [°].
stc_azimuth (float) – Azimuth angle (180 = south) [°].
A_stc_pipe (float) – Pipe surface area [m²].
alpha_stc (float) – Absorptivity [–].
h_o_stc (float) – External convective coefficient [W/(m²·K)].
h_r_stc (float) – Radiative coefficient [W/(m²·K)].
k_ins_stc (float) – Insulation conductivity [W/(m·K)].
x_air_stc (float) – Air gap thickness [m].
x_ins_stc (float) – Insulation thickness [m].
preheat_start_hour (float) – Preheat window start hour.
preheat_end_hour (float) – Preheat window end hour.
dV_stc_w (float) – Default STC loop flow rate [m³/s].
E_stc_pump (float) – STC pump rated power [W].
-
A_stc:
float= 2.0¶
-
stc_tilt:
float= 35.0¶
-
stc_azimuth:
float= 180.0¶
-
A_stc_pipe:
float= 2.0¶
-
alpha_stc:
float= 0.95¶
-
h_o_stc:
float= 15.0¶
-
h_r_stc:
float= 2.0¶
-
k_ins_stc:
float= 0.03¶
-
x_air_stc:
float= 0.01¶
-
x_ins_stc:
float= 0.05¶
-
preheat_start_hour:
float= 6.0¶
-
preheat_end_hour:
float= 18.0¶
-
dV_stc_w:
float= 0.001¶
-
E_stc_pump:
float= 50.0¶
- calc_overall_heat_transfer_coeff()[source]¶
Compute overall U-value from parallel resistances.
The collector has two heat-loss paths in parallel:
Path 1 (top): radiation gap ‖ air gap → external conv
Path 2 (bottom): insulation → external conv
- Returns:
Overall heat-loss coefficient [W/(m²·K)].
- Return type:
float
- calc_performance(I_DN_stc, I_dH_stc, T_stc_w_in_K, T0_K, dV_stc=None, is_active=True)[source]¶
Compute STC thermal performance for one timestep.
- Parameters:
I_DN_stc (float) – Direct-normal irradiance [W/m²].
I_dH_stc (float) – Diffuse-horizontal irradiance [W/m²].
T_stc_w_in_K (float) – Inlet water temperature [K].
T0_K (float) – Dead-state temperature [K].
dV_stc (float | None) – Override flow rate [m³/s]; defaults to
self.dV_stc_w.is_active (bool) – If
False, return NaN-filled dict.
- Returns:
Performance results including:
I_sol_stc,Q_sol_stc,Q_stc_w_in,Q_stc_w_out,Q_stc_pump_w_out,ksi_stc,T_stc_w_out_K,T_stc_pump_w_out_K,T_stc_w_in_K,T_stc_K,Q_l_stc.- Return type:
dict
- is_preheat_on(hour_of_day)[source]¶
Check whether hour_of_day falls in the window.
- Parameters:
hour_of_day (float) – Hour within the day (0–24).
- Return type:
bool
- __init__(A_stc=2.0, stc_tilt=35.0, stc_azimuth=180.0, A_stc_pipe=2.0, alpha_stc=0.95, h_o_stc=15.0, h_r_stc=2.0, k_ins_stc=0.03, x_air_stc=0.01, x_ins_stc=0.05, preheat_start_hour=6.0, preheat_end_hour=18.0, dV_stc_w=0.001, E_stc_pump=50.0)¶
- Parameters:
A_stc (
float)stc_tilt (
float)stc_azimuth (
float)A_stc_pipe (
float)alpha_stc (
float)h_o_stc (
float)h_r_stc (
float)k_ins_stc (
float)x_air_stc (
float)x_ins_stc (
float)preheat_start_hour (
float)preheat_end_hour (
float)dV_stc_w (
float)E_stc_pump (
float)
Photovoltaic system¶
- class tmhp.subsystems.PhotovoltaicSystem(A_pv=5.0, alp_pv=0.9, pv_tilt=35.0, pv_azimuth=180.0, h_o=15.0, eta_pv=0.15, beta_pv=0.0045, T_ref_pv_K=298.15, eta_ctrl=0.95, T_ctrl_K=308.15)[source]¶
Bases:
objectPhotovoltaic System (PV + Charge Controller) — pure physics engine.
Computes PV energy generation from irradiance inputs. This class is a stateless physics calculator: given physical inputs it returns a performance dict. All routing logic (ESS charge/discharge, Grid import, dump) is the responsibility of the scenario class.
The public API:
calc_performance()— single-timestep PV generation
- Parameters:
A_pv (
float)alp_pv (
float)pv_tilt (
float)pv_azimuth (
float)h_o (
float)eta_pv (
float)beta_pv (
float)T_ref_pv_K (
float)eta_ctrl (
float)T_ctrl_K (
float)
-
A_pv:
float= 5.0¶
-
alp_pv:
float= 0.9¶
-
pv_tilt:
float= 35.0¶
-
pv_azimuth:
float= 180.0¶
-
h_o:
float= 15.0¶
-
eta_pv:
float= 0.15¶
-
beta_pv:
float= 0.0045¶
-
T_ref_pv_K:
float= 298.15¶
-
eta_ctrl:
float= 0.95¶
-
T_ctrl_K:
float= 308.15¶
- calc_performance(I_DN, I_dH, T0_K)[source]¶
Compute PV generation for one timestep.
- Parameters:
I_DN (float) – Direct-normal irradiance [W/m²].
I_dH (float) – Diffuse-horizontal irradiance [W/m²].
T0_K (float) – Dead-state (ambient) temperature [K].
- Returns:
Keys:
I_sol_pv,T_pv_K,eta_pv_actual,E_pv_out,E_ctrl_out,Q_l_pv,Q_l_ctrl,X_sol,X_pv_out,X_ctrl_out,X_c_pv,X_c_ctrl,X_l_pv,X_l_ctrl.- Return type:
dict
- __init__(A_pv=5.0, alp_pv=0.9, pv_tilt=35.0, pv_azimuth=180.0, h_o=15.0, eta_pv=0.15, beta_pv=0.0045, T_ref_pv_K=298.15, eta_ctrl=0.95, T_ctrl_K=308.15)¶
- Parameters:
A_pv (
float)alp_pv (
float)pv_tilt (
float)pv_azimuth (
float)h_o (
float)eta_pv (
float)beta_pv (
float)T_ref_pv_K (
float)eta_ctrl (
float)T_ctrl_K (
float)
Energy storage system¶
- class tmhp.subsystems.EnergyStorageSystem(C_ess_max=3600000.0, SOC_init=0.0, SOC_min=0.1, SOC_max=1.0, eta_ess_chg=0.9, eta_ess_dis=0.9, T_ess_K=313.15)[source]¶
Bases:
objectEnergy Storage System (Battery) — pure physics engine.
Accepts charging / discharging requests (DC power, [W]) and enforces capacity and SOC limits. Internal losses and exergy destruction are computed from round-trip efficiencies.
State (
SOC_ess) is updated in-place at each call tocharge()ordischarge(). Routing decisions (which request to fulfil and in what order) are entirely the responsibility of the scenario class.- Parameters:
C_ess_max (float) – Rated energy capacity [J]. Default 3.6 MJ (= 1 kWh).
SOC_init (float) – Initial state of charge [–].
SOC_min (float) – Minimum allowable SOC [–] (depth-of-discharge guard).
SOC_max (float) – Maximum allowable SOC [–].
eta_ess_chg (float) – Charging efficiency (electricity-in to stored) [–].
eta_ess_dis (float) – Discharging efficiency (stored to electricity-out) [–].
T_ess_K (float) – Representative battery temperature used for entropy calc [K].
-
C_ess_max:
float= 3600000.0¶
-
SOC_init:
float= 0.0¶
-
SOC_min:
float= 0.1¶
-
SOC_max:
float= 1.0¶
-
eta_ess_chg:
float= 0.9¶
-
eta_ess_dis:
float= 0.9¶
-
T_ess_K:
float= 313.15¶
-
SOC_ess:
float¶
- charge(E_req_chg, dt, T0_K)[source]¶
Request to charge the ESS with E_req_chg [W] for dt [s].
Returns a dict with keys
E_ess_chg,E_ess_dis,SOC_essplus exergy keys.- Parameters:
E_req_chg (
float)dt (
float)T0_K (
float)
- Return type:
dict
- discharge(E_req_dis, dt, T0_K)[source]¶
Request to discharge E_req_dis [W] from the ESS for dt [s].
Returns a dict with keys
E_ess_dis(actual),E_ess_chg,SOC_essplus exergy keys.- Parameters:
E_req_dis (
float)dt (
float)T0_K (
float)
- Return type:
dict
- __init__(C_ess_max=3600000.0, SOC_init=0.0, SOC_min=0.1, SOC_max=1.0, eta_ess_chg=0.9, eta_ess_dis=0.9, T_ess_K=313.15)¶
- Parameters:
C_ess_max (
float)SOC_init (
float)SOC_min (
float)SOC_max (
float)eta_ess_chg (
float)eta_ess_dis (
float)T_ess_K (
float)
UV lamp¶
- class tmhp.subsystems.UVLamp(lamp_watts=0.0, exposure_sec=0.0, num_switching=1, period_sec=10800)[source]¶
Bases:
objectUV disinfection lamp subsystem.
The lamp switches on periodically (
num_switchingtimes perperiod_sec, each forexposure_sec). All electrical input is converted to heat inside the tank (Q_contribution = E_uv).- Parameters:
lamp_watts (float) – Rated lamp power [W].
exposure_sec (float) – Duration of each on-cycle [s].
num_switching (int) – Number of on-cycles per period.
period_sec (float) – Switching period [s] (default 3 h = 10 800 s).
-
lamp_watts:
float= 0.0¶
-
exposure_sec:
float= 0.0¶
-
num_switching:
int= 1¶
-
period_sec:
float= 10800¶
- step(ctx, ctrl, dt, T_tank_w_in_K)[source]¶
Compute UV lamp state for one timestep.
- Parameters:
ctx (
StepContext)ctrl (
ControlState)dt (
float)T_tank_w_in_K (
float)
- Return type:
dict
- assemble_results(ctx, ctrl, step_state, T_solved_K)[source]¶
Report UV power for DataFrame output.
- Parameters:
ctx (
StepContext)ctrl (
ControlState)step_state (
dict)T_solved_K (
float)
- Return type:
dict
- calc_exergy(df, T0_K)[source]¶
UV exergy = electricity (handled by E→X conversion).
No additional post-processing needed. Returns
None.- Parameters:
df (
DataFrame)T0_K (
Series)
- Return type:
SubsystemExergy|None
- __init__(lamp_watts=0.0, exposure_sec=0.0, num_switching=1, period_sec=10800)¶
- Parameters:
lamp_watts (
float)exposure_sec (
float)num_switching (
int)period_sec (
float)
UV treatment¶
UV water treatment utility functions.
Functions for UV lamp power scheduling, turbidity-based parameter lookup, and required exposure time calculation (Radial Model).
- tmhp.uv_treatment.calc_uv_lamp_power(current_time_s, period_sec, num_switching, exposure_sec, lamp_watts)[source]¶
Calculate UV lamp power at a given time instant.
The lamp switches on
num_switchingtimes perperiod_sec, each activation lastingexposure_sec.- Parameters:
current_time_s (float) – Current simulation time [s].
period_sec (float) – Switching period (e.g. 3 h → 10800 s).
num_switching (int) – Number of on-cycles per period.
exposure_sec (float) – Duration of each on-cycle [s].
lamp_watts (float) – Rated lamp power [W].
- Returns:
Instantaneous lamp power [W] (0 or
lamp_watts).- Return type:
float
- tmhp.uv_treatment.get_uv_params_from_turbidity(turbidity_ntu)[source]¶
Return UV parameters from a turbidity lookup table.
Table data based on Table 1. Effect of Turbidity on UVT, UV Absorbance, UV Intensity, and Exposure Time.
- Parameters:
turbidity_ntu (float) – Turbidity value [NTU].
- Returns:
Keys:
uv_absorbance,uv_transmittance_percent,reference_intensity_mw_cm2,reference_exposure_time_sec.- Return type:
dict
- tmhp.uv_treatment.calc_uv_exposure_time(radius_cm, uvc_output_W, lamp_arc_length_cm, target_dose_mj_cm2=186, turbidity_ntu=0.25)[source]¶
Calculate required UV lamp exposure time via Radial Model.
Reference: ADA453967.pdf — Radial Model for UV disinfection.
- Parameters:
radius_cm (float) – Tank radius (lamp-to-wall distance) [cm].
uvc_output_W (float) – UV-C output power of the lamp [W].
lamp_arc_length_cm (float) – Arc length of the lamp [cm].
target_dose_mj_cm2 (float) – Target germicidal dose [mJ/cm²]. Default 186 (EPA 4-log virus).
turbidity_ntu (float) – Water turbidity [NTU].
- Returns:
Required single-exposure time [min].
- Return type:
float
Stratified tank¶
Multi-node (1-D finite-volume) stratified thermal-storage tank.
Implements the multi-node model of Cadau et al. (Development and Analysis of a
Multi-Node Dynamic Model for the Simulation of Stratified Thermal Energy
Storage, Energies 2019, 12:4275): the tank is divided into N vertically
stacked nodes (node 0 = top/hottest, node N-1 = bottom/coldest), each with a
uniform temperature T_i. The per-node energy balance combines
advection — port inflow/outflow and inter-node vertical flow, upwinded by the flow direction (Cadau Eq. 4-6);
pseudo-conduction —
k·(T_{i-1}-T_i) - k·(T_i-T_{i+1})between neighbouring nodes, lumping conductive + convective exchange; andambient loss —
UA_i·(T_i - T_amb)through the side wall.
It is integrated implicitly (backward Euler) as a tridiagonal solve, so the
update is unconditionally stable and smooth in the state — a property the legacy
single-node lumped tank lacks and that an MPC-internal model needs. The lumped
fully-mixed tank is exactly the N=1 limit of this model.
This is a standalone, testable component; wiring it into
GroundSourceHeatPumpBoiler as a swappable tank backend is a separate step.
Conventions¶
Node index increases downward:
T[0]top (hot),T[N-1]bottom (cold).charge_flow[m³/s] ≥ 0 (HP charging) enters the top node atT_chargeand exits the bottom node (hot return to top, cold draw to HP) — a downward internal flow.draw_flow[m³/s] ≥ 0 (load) draws hot water from the top node and admits cold makeupT_makeupat the bottom node — an upward internal flow.The net inter-node flow is
charge_flow - draw_flow(downward positive); advection is upwinded by its direction (Cadau Eq. 4-6).Temperatures in °C; energy balances are temperature-difference based so the reference cancels.
- class tmhp.stratified_tank.StratifiedTank(n_nodes, volume, height, *, k_eff=0.606, ua=0.0, rho=1000, cp=4186)[source]¶
Bases:
objectMulti-node stratified hot-water tank (implicit tridiagonal stepper).
- Parameters:
n_nodes (int) – Number of vertical nodes
N(N=1recovers the lumped tank).volume (float) – Total tank volume [m³].
height (float) – Tank height [m] (sets node thickness
dz = height/Nand the cross-sectional areavolume/heightfor conduction).k_eff (float, optional) – Effective inter-node conductivity [W/m/K] (water conduction plus any turbulent-mixing enhancement). Defaults to water (
k_w).ua (float, optional) – Total tank-to-ambient loss coefficient
UA[W/K], split uniformly across nodes. Defaults to 0 (adiabatic).rho (float, optional) – Water density [kg/m³] and specific heat [J/kg/K]; default to the project constants. Assumed constant (Cadau: density assumed constant per node).
cp (float, optional) – Water density [kg/m³] and specific heat [J/kg/K]; default to the project constants. Assumed constant (Cadau: density assumed constant per node).
- __init__(n_nodes, volume, height, *, k_eff=0.606, ua=0.0, rho=1000, cp=4186)[source]¶
- Parameters:
n_nodes (
int)volume (
float)height (
float)k_eff (
float)ua (
float)rho (
float)cp (
float)
- reset(T_init)[source]¶
Set node temperatures (scalar = uniform, or length-N array).
- Return type:
ndarray
- property stored_energy: float¶
Sensible energy relative to 0 °C [J] (
Σ m_node·cp·T_i).
- step(dt, *, charge_flow=0.0, T_charge=0.0, draw_flow=0.0, T_makeup=10.0, q_source=None, T_amb=20.0)[source]¶
Advance one timestep (backward Euler, charge + draw + heat source).
- Parameters:
dt (float) – Timestep [s].
charge_flow (float, optional) – HP charge flow [m³/s] (hot
T_chargeinto top, out at bottom).T_charge (float, optional) – Charge inlet temperature [°C] (used when
charge_flow > 0).draw_flow (float, optional) – Load draw flow [m³/s] (hot from top, cold
T_makeupinto bottom).T_makeup (float, optional) – Cold makeup temperature [°C] (used when
draw_flow > 0).q_source (float or array-like, optional) – Internal heat input [W] from an immersed heater/condenser. A scalar is applied to the top node; a length-
Narray is applied per node.T_amb (float, optional) – Ambient temperature [°C] for the side-wall loss.
- Returns:
T(new node temperatures),T_top(= hot draw outlet),T_outlet(bottom = cold HP return).- Return type:
dict
Hybrid stratified tank¶
1-D hybrid continuous–discrete multi-node stratified tank (Cruz-Loredo 2023).
Implements the hybrid thermocline model of De la Cruz-Loredo et al.
(Experimental validation of a hybrid 1-D multi-node model of a hot water
thermal energy storage tank, Applied Energy 2023, 332:120556). It augments the
standard multi-node model (StratifiedTank) with a
flat thermocline barrier at vertical position y_th that travels in plug
flow at v_th = V̇/A_c.
The key device against numerical diffusion: while charging, the advective inflow
into each node uses a discrete reference temperature of its upstream neighbour
that is frozen until the thermocline front passes that neighbour’s mid-height
(y_mid). The transition therefore propagates at the physical front speed
instead of smearing across nodes. This is the charge-only thermocline form
(Cruz-Loredo Eq. 7): discharge/idle destroys the barrier and the model reverts to
the standard continuous multi-node behaviour.
Targets the plant / ground-truth role (high fidelity, non-smooth); the smooth
StratifiedTank (Cadau) targets the MPC-internal
role. Same geometry/units conventions as StratifiedTank (node 0 = top/hot).
- class tmhp.hybrid_tank.HybridStratifiedTank(n_nodes, volume, height, *, k_eff=0.606, ua=0.0, rho=1000, cp=4186)[source]¶
Bases:
objectHybrid continuous–discrete multi-node tank with a flat thermocline.
- Parameters:
n_nodes (
int)volume (
float)height (
float)k_eff (
float)ua (
float)rho (
float)cp (
float)
- __init__(n_nodes, volume, height, *, k_eff=0.606, ua=0.0, rho=1000, cp=4186)[source]¶
- Parameters:
n_nodes (
int)volume (
float)height (
float)k_eff (
float)ua (
float)rho (
float)cp (
float)
- property stored_energy: float¶
- step(dt, *, charge_flow=0.0, T_charge=0.0, draw_flow=0.0, T_makeup=10.0, T_amb=20.0)[source]¶
Advance one timestep.
Pure charge (
charge_flow > 0, draw_flow == 0) activates the hybrid frozen-reference thermocline; draw/idle/mixed flow uses the standard continuous multi-node update (the barrier is destroyed).- Parameters:
dt (
float)charge_flow (
float)T_charge (
float)draw_flow (
float)T_makeup (
float)T_amb (
float)
- Return type:
dict