Ground-source heat pump boiler (GSHPB)¶
The GSHPB family pairs the shared refrigerant cycle with a
ground-loop source side (vertical borehole field) and the same DHW
tank sink as ASHPB.
Overview¶
GSHPB solves the closed refrigerant cycle against a borehole heat
exchanger characterised by a precomputed g-function. The class is
tmhp.GroundSourceHeatPumpBoiler. Three composed variants
extend it the same way ASHPB’s do:
tmhp.GSHPB_STC_preheattmhp.GSHPB_STC_tanktmhp.GSHPB_PV_ESS
Base usage¶
from tmhp import GroundSourceHeatPumpBoiler
gshpb = GroundSourceHeatPumpBoiler(
ref="R410A",
N_1=1, N_2=1, # single borehole
H_b=150.0, # depth [m]
)
result = gshpb.analyze_steady(
T_tank_w=55.0,
T_source=10.0, # ground-loop fluid inlet [°C]
Q_ref_cond=8_000,
)
Source-side mechanics¶
For ground-source models, the source-side dynamics are encoded in a
g-function — the dimensionless thermal response of a borehole
field to a unit heat-extraction step. tmhp precomputes the
g-function once via
pygfunction and
interpolates it during the simulation, so the per-step cost stays
constant whether the field is one borehole or a hundred.
Dimensionless g-function for three rectangular borehole-field
geometries. The 1 × 1 field is the single-borehole baseline;
2 × 2 and 4 × 4 diverge as borehole-to-borehole thermal
interference accumulates over the multi-year horizon. Generated
by scripts/visualization/g_function_curve.py.¶
Sink-side mechanics¶
Same as ASHPB — single-node DHW tank, implicit per-step solve.
Composed variants¶
Usage patterns for the three variants are identical to ASHPB’s — see Air-source heat pump boiler (ASHPB) for full STC preheat and PV + ESS examples. Only the base class swaps; the schedules and routing are unchanged.
The standalone GSHPB: borehole-field g-function source, R32 cycle, DHW tank charge.
from tmhp import GroundSourceHeatPumpBoiler
gshpb = GroundSourceHeatPumpBoiler(
ref="R410A",
N_1=1, N_2=1,
H_b=150.0,
)
result = gshpb.analyze_steady(
T_tank_w=55.0,
T_source=10.0,
Q_ref_cond=8_000,
)
Adds a flat-plate STC that preheats mains water entering the tank. Reduces the tank-charge duty the heat pump has to deliver.
from tmhp import GSHPB_STC_preheat
from tmhp.subsystems import SolarThermalCollector
stc = SolarThermalCollector(A_stc=4.0, stc_tilt=35.0, stc_azimuth=180.0)
model = GSHPB_STC_preheat(stc=stc, ref="R410A", N_1=1, N_2=1, H_b=150.0)
STC charges a separate top node of a stratified tank; the heat pump charges the bottom. Top-of-tank water is drawn first.
from tmhp import GSHPB_STC_tank
from tmhp.subsystems import SolarThermalCollector
stc = SolarThermalCollector(A_stc=4.0, stc_tilt=35.0, stc_azimuth=180.0)
model = GSHPB_STC_tank(stc=stc, ref="R410A", N_1=1, N_2=1, H_b=150.0)
Photovoltaic generation + ESS preferentially feeds the compressor and auxiliaries.
from tmhp import GSHPB_PV_ESS
from tmhp.subsystems import EnergyStorageSystem, PhotovoltaicSystem
model = GSHPB_PV_ESS(
pv=PhotovoltaicSystem(),
ess=EnergyStorageSystem(),
ref="R410A",
N_1=1, N_2=1, H_b=150.0,
)
STC preheat¶
- class tmhp.GSHPB_STC_preheat(*, stc, **kwargs)[source]
Bases:
GroundSourceHeatPumpBoilerGSHPB + SolarThermalCollector in mains_preheat placement.
Physical configuration¶
The STC preheats mains cold water before it enters the storage tank. The raised inlet temperature (
T_tank_w_in_override_K) reduces the thermal load on the heat pump compressor.Orchestration responsibility¶
This class owns all simulation logic for the STC:
_run_subsystems: activation probe +calc_performance()→ setsT_tank_w_in_override_Kwhen active_augment_results: result column assembly (uses pump outlet temperature directly; no re-evaluation at solved tank temp)_postprocess: STC exergy calculation. Tank boundary corrections are not applied because the preheated inlet temperature is already reflected in the coreX_tank_w_in [W]column (X_in_tank_add = 0).
- type stc:
- param stc:
Pure physics engine. No
modeconstraint required.- type stc:
SolarThermalCollector
- type **kwargs:
- param **kwargs:
Forwarded to
GroundSourceHeatPumpBoiler.- raises TypeError:
If stc is not a
SolarThermalCollectorinstance.
- __init__(*, stc, **kwargs)[source]
- Parameters:
stc (
SolarThermalCollector)
STC with stratified tank¶
- class tmhp.GSHPB_STC_tank(*, stc, **kwargs)[source]
Bases:
GroundSourceHeatPumpBoilerGSHPB + SolarThermalCollector in tank_circuit placement.
Physical configuration¶
The STC collector loop is connected directly to the storage tank. The STC draws water from the tank, heats it via solar energy, and returns it through the pump. The STC is activated only when the collector outlet temperature exceeds the current tank temperature.
Orchestration responsibility¶
This class owns all simulation logic for the STC:
_run_subsystems: activation probe +calc_performance()_build_residual_fn: fully implicit coupling to tank solver_augment_results: result column assembly (re-evaluates at solved tank temperature for accuracy)_postprocess: STC exergy calculation and tank boundary correction (X_tot,Xc_tank)
- type stc:
- param stc:
Pure physics engine. No
modeconstraint required.- type stc:
SolarThermalCollector
- type **kwargs:
- param **kwargs:
Forwarded to
GroundSourceHeatPumpBoiler.- raises TypeError:
If stc is not a
SolarThermalCollectorinstance.
- __init__(*, stc, **kwargs)[source]
- Parameters:
stc (
SolarThermalCollector)
PV + ESS¶
- class tmhp.GSHPB_PV_ESS(*, pv, ess=None, eta_inv=0.95, T_inv_K=313.15, **kwargs)[source]
Bases:
GroundSourceHeatPumpBoilerGSHPB scenario where the heat pump is supplied by PV + ESS + Grid.
The PV/ESS routing is resolved synchronously inside
_augment_resultsafter the HP electrical load is known. No 1-step lag: the PV energy is allocated to the exact HP load produced in the same timestep.- Parameters:
pv (PhotovoltaicSystem) – Pure-physics PV + charge-controller model.
ess (EnergyStorageSystem) – Pure-physics battery model with
charge()/discharge().eta_inv (float) – Inverter DC→AC efficiency [–].
T_inv_K (float) – Inverter temperature for entropy calculation [K].
**kwargs – Forwarded to
GroundSourceHeatPumpBoiler.
- __init__(*, pv, ess=None, eta_inv=0.95, T_inv_K=313.15, **kwargs)[source]
- Parameters:
pv (
PhotovoltaicSystem)ess (
Optional[EnergyStorageSystem])eta_inv (
float)T_inv_K (
float)
API reference¶
Integrated System Model: Ground Source Heat Pump Boiler (GSHPB).
This system class orchestrates the dynamic interaction between distinct thermodynamic sub-components to simulate the overall heating performance. While implemented as an integrated model, its physical calculations represent the behavior of:
Refrigerant Cycle (Vapor-Compression): Evaluates thermodynamic states using CoolProp, enforcing superheat/subcool margins.
Heat Pump Compressor: Models the compression process using isentropic and volumetric efficiencies to compute the actual discharge enthalpy and mass flow rate. The compressor power is determined from the enthalpy difference and the mass flow rate.
Expansion Valve: Modeled as an isenthalpic expansion device (constant enthalpy) that throttles the refrigerant from the condensing pressure down to the evaporating pressure.
Heat Exchangers (Condenser & Evaporator):
Condenser: Placed inside the hot-water tank (hydronic), utilizing a static overall heat transfer coefficient (UA_cond_design).
Evaporator: Coupled to a borehole heat exchanger (BHE) fluid loop, acting as a secondary heat exchanger to absorb heat from the circulating ground fluid.
Thermal Storage Tank: Modeled with lumped-capacitance and DHW mixing logic.
Ground Source Heat Exchanger (Borefield): A dynamic multi-borehole simulation using pygfunction-based g-functions. It tracks the transient thermal response of the ground, enabling robust modeling of long-term ground temperature drift due to continuous heat extraction.
At each time step the model finds the minimum-power operating point via 1D Brent optimization over the evaporator approach temperature difference, while the condenser temperature is solved analytically.
Note
See the project paper for the underlying refrigerant-cycle theory and the pygfunction-based borehole heat-exchanger model used here.
- class tmhp.ground_source_heat_pump_boiler.GroundSourceHeatPumpBoiler(ref='R410A', V_disp_cmp=0.0005, eta_cmp_isen=None, eta_cmp_vol=None, eta_cmp_mech=0.855, UA_cond_design=500, UA_evap_design=500, T0=0.0, Ts=16.0, T_tank_w_upper_bound=65.0, T_tank_w_lower_bound=55.0, T_mix_w_out=40.0, T_tank_w_in=15.0, hp_capacity=8000.0, dV_mix_w_out_max=0.0001, r0=0.2, H=0.8, x_shell=0.01, x_ins=0.05, k_shell=25, k_ins=0.03, h_o=15, N_1=1, N_2=1, B=6.0, D_b=0, H_b=200, r_b=0.08, R_b=None, k_g=1.5, k_p=0.4, r_out=0.016, r_in=0.013, D_s=0.025, dV_b_f_lpm=24, k_s=2.0, c_s=800, rho_s=2000, E_pmp=200, dT_superheat=3.0, dT_subcool=3.0, tank_always_full=True, prevent_simultaneous_flow=False, tank_level_lower_bound=0.5, tank_level_upper_bound=1.0, dV_tank_w_in_refill=0.001, hp_on_schedule=None, stc=None, pv=None, uv=None, t_max_s=31536000, dt_s=3600, boundary_condition='uniform_temperature', *, refrigerant=None)[source]¶
Bases:
objectGround source heat pump boiler with BHE and lumped-tank model.
The refrigerant cycle is resolved via CoolProp with user-specified superheat / subcool margins. An optimizer minimises total cycle electrical input subject to NTU-based evaporator constraints and analytical condenser temperature relations.
- Parameters:
ref (
str)V_disp_cmp (
float)eta_cmp_isen (
Union[float,Callable,None])eta_cmp_vol (
Union[float,Callable,None])eta_cmp_mech (
float|Callable)UA_cond_design (
float)UA_evap_design (
float)T0 (
float)Ts (
float)T_tank_w_upper_bound (
float)T_tank_w_lower_bound (
float)T_mix_w_out (
float)T_tank_w_in (
float)hp_capacity (
float)dV_mix_w_out_max (
float)r0 (
float)H (
float)x_shell (
float)x_ins (
float)k_shell (
float)k_ins (
float)h_o (
float)N_1 (
int)N_2 (
int)B (
float)D_b (
float)H_b (
float)r_b (
float)R_b (
Optional[float])k_g (
float)k_p (
float)r_out (
float)r_in (
float)D_s (
float)dV_b_f_lpm (
float)k_s (
float)c_s (
float)rho_s (
float)E_pmp (
float)dT_superheat (
float)dT_subcool (
float)tank_always_full (
bool)prevent_simultaneous_flow (
bool)tank_level_lower_bound (
float)tank_level_upper_bound (
float)dV_tank_w_in_refill (
float)hp_on_schedule (
Optional[list[tuple[float,float]]])stc (
Optional[SolarThermalCollector])t_max_s (
float)dt_s (
float)boundary_condition (
str)refrigerant (
Optional[str])
- __init__(ref='R410A', V_disp_cmp=0.0005, eta_cmp_isen=None, eta_cmp_vol=None, eta_cmp_mech=0.855, UA_cond_design=500, UA_evap_design=500, T0=0.0, Ts=16.0, T_tank_w_upper_bound=65.0, T_tank_w_lower_bound=55.0, T_mix_w_out=40.0, T_tank_w_in=15.0, hp_capacity=8000.0, dV_mix_w_out_max=0.0001, r0=0.2, H=0.8, x_shell=0.01, x_ins=0.05, k_shell=25, k_ins=0.03, h_o=15, N_1=1, N_2=1, B=6.0, D_b=0, H_b=200, r_b=0.08, R_b=None, k_g=1.5, k_p=0.4, r_out=0.016, r_in=0.013, D_s=0.025, dV_b_f_lpm=24, k_s=2.0, c_s=800, rho_s=2000, E_pmp=200, dT_superheat=3.0, dT_subcool=3.0, tank_always_full=True, prevent_simultaneous_flow=False, tank_level_lower_bound=0.5, tank_level_upper_bound=1.0, dV_tank_w_in_refill=0.001, hp_on_schedule=None, stc=None, pv=None, uv=None, t_max_s=31536000, dt_s=3600, boundary_condition='uniform_temperature', *, refrigerant=None)[source]¶
- Parameters:
ref (
str)V_disp_cmp (
float)eta_cmp_isen (
Union[float,Callable,None])eta_cmp_vol (
Union[float,Callable,None])eta_cmp_mech (
float|Callable)UA_cond_design (
float)UA_evap_design (
float)T0 (
float)Ts (
float)T_tank_w_upper_bound (
float)T_tank_w_lower_bound (
float)T_mix_w_out (
float)T_tank_w_in (
float)hp_capacity (
float)dV_mix_w_out_max (
float)r0 (
float)H (
float)x_shell (
float)x_ins (
float)k_shell (
float)k_ins (
float)h_o (
float)N_1 (
int)N_2 (
int)B (
float)D_b (
float)H_b (
float)r_b (
float)R_b (
Optional[float])k_g (
float)k_p (
float)r_out (
float)r_in (
float)D_s (
float)dV_b_f_lpm (
float)k_s (
float)c_s (
float)rho_s (
float)E_pmp (
float)dT_superheat (
float)dT_subcool (
float)tank_always_full (
bool)prevent_simultaneous_flow (
bool)tank_level_lower_bound (
float)tank_level_upper_bound (
float)dV_tank_w_in_refill (
float)hp_on_schedule (
Optional[list[tuple[float,float]]])stc (
Optional[SolarThermalCollector])t_max_s (
float)dt_s (
float)boundary_condition (
str)refrigerant (
Optional[str])
- analyze_dynamic(simulation_period_sec, dt_s, T_tank_w_init_C, dhw_usage_schedule, T0_schedule, I_DN_schedule=None, I_dH_schedule=None, T_sup_w_schedule=None, tank_level_init=1.0, result_save_csv_path=None)[source]¶
- Parameters:
simulation_period_sec (
float)dt_s (
float)T_tank_w_init_C (
float)tank_level_init (
float)
- Return type:
DataFrame
- analyze_steady(T_tank_w, T_source, Q_ref_cond, T0=0.0, *, return_dict=True)[source]¶
Run a steady-state performance snapshot.
Evaluates the refrigerant cycle at a given operating point (
T_tank_w,T_source,Q_ref_cond) without solving the tank energy balance or tracking dynamic flows.- Parameters:
T_tank_w (float) – Tank water temperature [°C] — treated as a given input.
T_source (float) – Source fluid temperature entering the heat pump [°C].
Q_ref_cond (float) – Target condenser heat rate [W].
T0 (float) – Dead-state / outdoor-air temperature [°C] (for exergy calculations).
return_dict (bool) – If
Truereturn dict; else single-row DataFrame.
- Returns:
Cycle state plus diagnostic flags. Notable keys:
"converged"(bool) — True only when the HX optimisation and the SciPy optimiser both succeeded."failure_reason"(str) — one of"none","cycle_invalid","hx_not_converged", or"optimizer_failed".
Important: GSHPB frequently reports
failure_reason="hx_not_converged"on realistic operating points because its inner NTU/HX residual tolerance is strict. The returnedE_cmp [W]/Q_ref_cond [W]/cop_sys [-]are still usable in that case — only"cycle_invalid"forces an off-mode fallback (E_cmp=0, COP=NaN). Branch onE_cmp [W] > 0rather thanfailure_reason == "none"if you only want to discard truly broken results.- Return type:
dict | pd.DataFrame
GSHPB with SolarThermalCollector — mains_preheat placement.
Phase 3 restructuring: all simulation orchestration logic
(activation probe, result assembly, exergy calculation) is
implemented directly in this class. SolarThermalCollector
is used purely as a physics engine (calc_performance()),
with no dependency on step(), assemble_results(), or
calc_exergy().
Usage¶
from enex_analysis import SolarThermalCollector
from enex_analysis.gshpb_stc_preheat import GSHPB_STC_preheat
stc = SolarThermalCollector(A_stc=4.0)
model = GSHPB_STC_preheat(
stc=stc,
ref="R134a",
hp_capacity=15_000.0,
T_tank_w_lower_bound=55.0,
T_tank_w_upper_bound=65.0,
T_mix_w_out=42.0,
)
df = model.analyze_dynamic(...)
- class tmhp.gshpb_stc_preheat.GSHPB_STC_preheat(*, stc, **kwargs)[source]¶
Bases:
GroundSourceHeatPumpBoilerGSHPB + SolarThermalCollector in mains_preheat placement.
Physical configuration¶
The STC preheats mains cold water before it enters the storage tank. The raised inlet temperature (
T_tank_w_in_override_K) reduces the thermal load on the heat pump compressor.Orchestration responsibility¶
This class owns all simulation logic for the STC:
_run_subsystems: activation probe +calc_performance()→ setsT_tank_w_in_override_Kwhen active_augment_results: result column assembly (uses pump outlet temperature directly; no re-evaluation at solved tank temp)_postprocess: STC exergy calculation. Tank boundary corrections are not applied because the preheated inlet temperature is already reflected in the coreX_tank_w_in [W]column (X_in_tank_add = 0).
- type stc:
- param stc:
Pure physics engine. No
modeconstraint required.- type stc:
SolarThermalCollector
- type **kwargs:
- param **kwargs:
Forwarded to
GroundSourceHeatPumpBoiler.- raises TypeError:
If stc is not a
SolarThermalCollectorinstance.
- __init__(*, stc, **kwargs)[source]¶
- Parameters:
stc (
SolarThermalCollector)
GSHPB with SolarThermalCollector — tank_circuit placement.
Phase 3 restructuring: all simulation orchestration logic
(activation probe, result assembly, exergy calculation) is
implemented directly in this class. SolarThermalCollector
is used purely as a physics engine (calc_performance()),
with no dependency on step(), assemble_results(), or
calc_exergy().
Usage¶
from enex_analysis import SolarThermalCollector
from enex_analysis.gshpb_stc_tank import GSHPB_STC_tank
stc = SolarThermalCollector(A_stc=4.0)
model = GSHPB_STC_tank(
stc=stc,
ref="R134a",
hp_capacity=15_000.0,
T_tank_w_lower_bound=55.0,
T_tank_w_upper_bound=65.0,
T_mix_w_out=42.0,
)
df = model.analyze_dynamic(...)
- class tmhp.gshpb_stc_tank.GSHPB_STC_tank(*, stc, **kwargs)[source]¶
Bases:
GroundSourceHeatPumpBoilerGSHPB + SolarThermalCollector in tank_circuit placement.
Physical configuration¶
The STC collector loop is connected directly to the storage tank. The STC draws water from the tank, heats it via solar energy, and returns it through the pump. The STC is activated only when the collector outlet temperature exceeds the current tank temperature.
Orchestration responsibility¶
This class owns all simulation logic for the STC:
_run_subsystems: activation probe +calc_performance()_build_residual_fn: fully implicit coupling to tank solver_augment_results: result column assembly (re-evaluates at solved tank temperature for accuracy)_postprocess: STC exergy calculation and tank boundary correction (X_tot,Xc_tank)
- type stc:
- param stc:
Pure physics engine. No
modeconstraint required.- type stc:
SolarThermalCollector
- type **kwargs:
- param **kwargs:
Forwarded to
GroundSourceHeatPumpBoiler.- raises TypeError:
If stc is not a
SolarThermalCollectorinstance.
- __init__(*, stc, **kwargs)[source]¶
- Parameters:
stc (
SolarThermalCollector)
GSHPB Scenario: Heat Pump driven by PV + ESS with Grid/Dump integration.
Energy routing (all logic lives here, subsystems are pure physics):
PV generation →
pv.calc_performance()DC routing: - PV surplus →
ess.charge(); leftover → dump - PV deficit →ess.discharge(); leftover → grid importInverter conversion loss applied to DC supply
Grid import covers any remaining AC shortfall
- class tmhp.gshpb_pv_ess.GSHPB_PV_ESS(*, pv, ess=None, eta_inv=0.95, T_inv_K=313.15, **kwargs)[source]¶
Bases:
GroundSourceHeatPumpBoilerGSHPB scenario where the heat pump is supplied by PV + ESS + Grid.
The PV/ESS routing is resolved synchronously inside
_augment_resultsafter the HP electrical load is known. No 1-step lag: the PV energy is allocated to the exact HP load produced in the same timestep.- Parameters:
pv (PhotovoltaicSystem) – Pure-physics PV + charge-controller model.
ess (EnergyStorageSystem) – Pure-physics battery model with
charge()/discharge().eta_inv (float) – Inverter DC→AC efficiency [–].
T_inv_K (float) – Inverter temperature for entropy calculation [K].
**kwargs – Forwarded to
GroundSourceHeatPumpBoiler.
- __init__(*, pv, ess=None, eta_inv=0.95, T_inv_K=313.15, **kwargs)[source]¶
- Parameters:
pv (
PhotovoltaicSystem)ess (
Optional[EnergyStorageSystem])eta_inv (
float)T_inv_K (
float)