Ground-source heat pump boiler (GSHPB)

The GSHPB family pairs the shared refrigerant cycle with a ground-loop source side (vertical borehole field) and a DHW tank sink.

Overview

GSHPB solves the closed refrigerant cycle against a borehole heat exchanger characterised by a precomputed g-function. The class is tmhp.GroundSourceHeatPumpBoiler. Composed variants extend it the same way ASHPB’s do:

  • tmhp.GSHPB_STC_preheat

  • tmhp.GSHPB_STC_tank

  • tmhp.GSHPB_STC_ground

  • tmhp.GSHPB_STC_routed

  • tmhp.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_tank=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.

g-function vs ln(t/t_s) for three rectangular borehole field geometries: 1×1, 2×2, and 4×4.

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

Single-node DHW tank, implicit per-step solve — the same demand-side sink used by the air-source and water-source boiler families.

Composed variants

Usage patterns for the tank-side variants follow the same demand-side composition pattern as the air-source boiler family — see Air-source heat pump boiler (ASHPB) for full STC preheat and PV + ESS examples. The ground-side STC variants route collected solar heat into the borehole field instead of, or in exclusive alternation with, the tank.

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_tank=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)

STC injects collected heat into the borehole loop for seasonal ground charging; the DHW tank balance remains heat-pump-only.

from tmhp import GSHPB_STC_ground
from tmhp.subsystems import SolarThermalCollector

stc = SolarThermalCollector(A_stc=4.0, stc_tilt=35.0, stc_azimuth=180.0)
model = GSHPB_STC_ground(stc=stc, ref="R410A", N_1=1, N_2=1, H_b=150.0)

A per-step router sends solar heat either to the tank or to the borehole field. The default policy serves a cold tank first, then charges the ground.

from tmhp import GSHPB_STC_routed
from tmhp.subsystems import SolarThermalCollector

stc = SolarThermalCollector(A_stc=4.0, stc_tilt=35.0, stc_azimuth=180.0)
model = GSHPB_STC_routed(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: GroundSourceHeatPumpBoiler

GSHPB + 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() → sets T_tank_w_in_override_K when 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 core X_tank_w_in [W] column (X_in_tank_add = 0).

type stc:

SolarThermalCollector

param stc:

Pure physics engine. No mode constraint required.

type stc:

SolarThermalCollector

type **kwargs:

param **kwargs:

Forwarded to GroundSourceHeatPumpBoiler.

raises TypeError:

If stc is not a SolarThermalCollector instance.

__init__(*, stc, **kwargs)[source]
Parameters:

stc (SolarThermalCollector)

STC with stratified tank

class tmhp.GSHPB_STC_tank(*, stc, **kwargs)[source]

Bases: GroundSourceHeatPumpBoiler

GSHPB + 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’s thermal gain exceeds the pump electricity required to circulate the loop.

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:

SolarThermalCollector

param stc:

Pure physics engine. No mode constraint required.

type stc:

SolarThermalCollector

type **kwargs:

param **kwargs:

Forwarded to GroundSourceHeatPumpBoiler.

raises TypeError:

If stc is not a SolarThermalCollector instance.

__init__(*, stc, **kwargs)[source]
Parameters:

stc (SolarThermalCollector)

STC to ground loop

class tmhp.GSHPB_STC_ground(*, stc, **kwargs)[source]

Bases: GroundSourceHeatPumpBoiler

GSHPB + SolarThermalCollector in ground-loop (borehole) placement.

Parameters:

stc (SolarThermalCollector)

__init__(*, stc, **kwargs)[source]
Parameters:

stc (SolarThermalCollector)

STC routed between tank and ground

class tmhp.GSHPB_STC_routed(*, stc, solar_router=None, **kwargs)[source]

Bases: GroundSourceHeatPumpBoiler

GSHPB + SolarThermalCollector with a per-timestep ground/tank router.

Parameters:
__init__(*, stc, solar_router=None, **kwargs)[source]
Parameters:

PV + ESS

class tmhp.GSHPB_PV_ESS(*, pv, ess=None, eta_inv=0.95, T_inv_K=313.15, **kwargs)[source]

Bases: GroundSourceHeatPumpBoiler

GSHPB scenario where the heat pump is supplied by PV + ESS + Grid.

The PV/ESS routing is resolved synchronously inside _augment_results after 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:

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:

  1. Refrigerant Cycle (Vapor-Compression): Evaluates thermodynamic states using CoolProp, enforcing superheat/subcool margins.

  2. 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.

  3. Expansion Valve: Modeled as an isenthalpic expansion device (constant enthalpy) that throttles the refrigerant from the condensing pressure down to the evaporating pressure.

  4. Heat Exchangers (Condenser & Evaporator):

    • Condenser: Placed inside the hot-water tank (hydronic), utilizing a static overall heat transfer coefficient (UA_tank_hx).

    • Evaporator: Coupled to a borehole heat exchanger (BHE) fluid loop, acting as a secondary heat exchanger to absorb heat from the circulating ground fluid.

  5. Thermal Storage Tank: Modeled with lumped-capacitance and DHW mixing logic.

  6. 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_cmp_ref=None, eta_cmp_isen=None, eta_cmp_vol=None, eta_cmp=None, UA_tank_hx=None, UA_ground=None, 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, tank_model='lumped', n_tank_nodes=10, condenser_node=None, 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', ground_coupler=None, T_sur=20.0, dT_hx_min=0.5, PR_cycle_min=1.5, PR_cycle_max=20.0, rps_min=10.0, rps_max=150.0, *, refrigerant=None, V_disp_cmp=None, eta_cmp_mech=None, UA_tank=None, UA_cond_design=None, UA_evap_design=None)[source]

Bases: object

Ground 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_cmp_ref (Optional[float])

  • eta_cmp_isen (Union[float, Callable, None])

  • eta_cmp_vol (Union[float, Callable, None])

  • eta_cmp (Union[float, Callable, None])

  • UA_tank_hx (Optional[float])

  • UA_ground (Optional[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)

  • tank_model (str)

  • n_tank_nodes (int)

  • condenser_node (Optional[int])

  • 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)

  • ground_coupler (Optional[GroundCoupler])

  • T_sur (float)

  • dT_hx_min (float)

  • PR_cycle_min (float)

  • PR_cycle_max (float)

  • rps_min (float)

  • rps_max (float)

  • refrigerant (Optional[str])

  • V_disp_cmp (Optional[float])

  • eta_cmp_mech (Union[float, Callable, None])

  • UA_tank (Optional[float])

  • UA_cond_design (Optional[float])

  • UA_evap_design (Optional[float])

__init__(ref='R410A', V_cmp_ref=None, eta_cmp_isen=None, eta_cmp_vol=None, eta_cmp=None, UA_tank_hx=None, UA_ground=None, 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, tank_model='lumped', n_tank_nodes=10, condenser_node=None, 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', ground_coupler=None, T_sur=20.0, dT_hx_min=0.5, PR_cycle_min=1.5, PR_cycle_max=20.0, rps_min=10.0, rps_max=150.0, *, refrigerant=None, V_disp_cmp=None, eta_cmp_mech=None, UA_tank=None, UA_cond_design=None, UA_evap_design=None)[source]
Parameters:
  • ref (str)

  • V_cmp_ref (Optional[float])

  • eta_cmp_isen (Union[float, Callable, None])

  • eta_cmp_vol (Union[float, Callable, None])

  • eta_cmp (Union[float, Callable, None])

  • UA_tank_hx (Optional[float])

  • UA_ground (Optional[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)

  • tank_model (str)

  • n_tank_nodes (int)

  • condenser_node (Optional[int])

  • 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)

  • ground_coupler (Optional[GroundCoupler])

  • T_sur (float)

  • dT_hx_min (float)

  • PR_cycle_min (float)

  • PR_cycle_max (float)

  • rps_min (float)

  • rps_max (float)

  • refrigerant (Optional[str])

  • V_disp_cmp (Optional[float])

  • eta_cmp_mech (Union[float, Callable, None])

  • UA_tank (Optional[float])

  • UA_cond_design (Optional[float])

  • UA_evap_design (Optional[float])

step(state, inputs, dt_s)[source]

Reject point-state stepping — GSHPB is history-dependent (#165 P0b).

_compute_bhe_superposition reads the entire past borehole load-pulse history each timestep, so a point-state step() kernel (as defined for ASHP/ASHPB) would silently corrupt the ground temperature drift. Use analyze_dynamic(), or implement a step() that carries the borehole pulse vector inside its state.

Parameters:
  • inputs (dict)

  • dt_s (float)

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, T_sur_schedule=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_tank, 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_tank) 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_tank (float) – Target condenser heat rate [W].

  • T0 (float) – Dead-state / outdoor-air temperature [°C] (for exergy calculations).

  • return_dict (bool) – If True return 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 returned E_cmp [W] / Q_ref_tank [W] / cop_sys [-] are still usable in that case — only "cycle_invalid" forces an off-mode fallback (E_cmp=0, COP=NaN). Branch on E_cmp [W] > 0 rather than failure_reason == "none" if you only want to discard truly broken results.

Return type:

dict | pd.DataFrame

postprocess_exergy(df)[source]

Compute GSHPB-specific exergy variables.

Parameters:

df (DataFrame)

Return type:

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: GroundSourceHeatPumpBoiler

GSHPB + 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() → sets T_tank_w_in_override_K when 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 core X_tank_w_in [W] column (X_in_tank_add = 0).

type stc:

SolarThermalCollector

param stc:

Pure physics engine. No mode constraint required.

type stc:

SolarThermalCollector

type **kwargs:

param **kwargs:

Forwarded to GroundSourceHeatPumpBoiler.

raises TypeError:

If stc is not a SolarThermalCollector instance.

__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: GroundSourceHeatPumpBoiler

GSHPB + 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’s thermal gain exceeds the pump electricity required to circulate the loop.

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:

SolarThermalCollector

param stc:

Pure physics engine. No mode constraint required.

type stc:

SolarThermalCollector

type **kwargs:

param **kwargs:

Forwarded to GroundSourceHeatPumpBoiler.

raises TypeError:

If stc is not a SolarThermalCollector instance.

__init__(*, stc, **kwargs)[source]
Parameters:

stc (SolarThermalCollector)

GSHPB with a SolarThermalCollector charging the ground loop (not the tank).

Counterpart to GSHPB_STC_tank. Solar heat charges the ground OR the tank, never both at once — this scenario routes it to the ground.

Physical configuration

The collector loop circulates ground-loop fluid (inlet = current borehole fluid temperature T_bhe_f), is warmed by solar irradiance, and returns to the borehole field, injecting the collected heat into the ground (seasonal / inter-seasonal charging). This raises the ground temperature and therefore the heat-pump source temperature — improving COP later — at the cost of the collector pump electricity. Activation requires a net heat gain (collector outlet hotter than the ground fluid).

Implementation

The base GroundSourceHeatPumpBoiler is left unchanged (byte-identical). This subclass:

  • _run_subsystems: evaluates the collector at the ground-fluid inlet and stores the net solar heat Q_solar [W] to inject into the ground.

  • _compute_bhe_superposition: subtracts Q_solar from the HP ground load (net Q_bhe = HP extraction solar injection) before the standard ground superposition, so a net injection warms the borehole field. The tank energy balance is untouched (solar heat does not enter the tank in this scenario).

With zero irradiance the collector is inactive and results reduce to the base GSHPB exactly.

class tmhp.gshpb_stc_ground.GSHPB_STC_ground(*, stc, **kwargs)[source]

Bases: GroundSourceHeatPumpBoiler

GSHPB + SolarThermalCollector in ground-loop (borehole) placement.

Parameters:

stc (SolarThermalCollector)

__init__(*, stc, **kwargs)[source]
Parameters:

stc (SolarThermalCollector)

GSHPB with a SolarThermalCollector routed per-timestep to the ground OR tank.

Unifies GSHPB_STC_ground and GSHPB_STC_tank: at each timestep a routing decision sends the collected solar heat to either the borehole field (seasonal / inter-seasonal ground charging, raising the HP source temperature and COP) or the storage tank (immediate DHW preheat) — exclusively, never both at once.

The two destinations enter the dynamic solve through different couplings, so the routed model gates each per step:

  • tanknet Q_STC enters the tank energy balance (implicit residual),

    exactly as GSHPB_STC_tank; the ground load is untouched.

  • groundnet solar heat is subtracted from the HP ground load before the

    borehole superposition, exactly as GSHPB_STC_ground; the tank balance is untouched.

  • off : no solar; the step reduces to the base GSHPB.

Routing as a control input

The decision is produced by a callable solar_router evaluated every step, so it is a natural per-timestep decision variable for an MPC — an optimiser can supply the route in place of the default policy. The default (default_solar_router()) is a greedy rule: serve the tank when it is below its lower setpoint (a DHW need), otherwise dump surplus solar into the ground. The router is only consulted when solar is actually available (positive irradiance and the collector schedule on); the chosen destination is still gated by a net-heat-gain feasibility check (collector outlet hotter than the sink), falling back to off if infeasible.

With zero irradiance the collector is inactive and results reduce to the base GSHPB exactly (only extra reporting columns are added).

tmhp.gshpb_stc_routed.default_solar_router(*, T_tank_w, T_tank_lower, **_)[source]

Greedy exclusive policy: serve the tank when cold, else charge the ground.

Called only when solar is available. Returns "tank" while the tank is below its lower setpoint (immediate DHW need has priority), otherwise "ground" to bank surplus solar in the borehole field for later COP gain. Extra keyword state (hour_of_day, T_bhe, T0) is accepted and ignored so a custom router can use it without changing this signature.

Parameters:
  • T_tank_w (float)

  • T_tank_lower (float)

  • _ (Any)

Return type:

str

class tmhp.gshpb_stc_routed.GSHPB_STC_routed(*, stc, solar_router=None, **kwargs)[source]

Bases: GroundSourceHeatPumpBoiler

GSHPB + SolarThermalCollector with a per-timestep ground/tank router.

Parameters:
__init__(*, stc, solar_router=None, **kwargs)[source]
Parameters:

GSHPB Scenario: Heat Pump driven by PV + ESS with Grid/Dump integration.

Energy routing (all logic lives here, subsystems are pure physics):

  1. PV generation → pv.calc_performance()

  2. DC routing: - PV surplus → ess.charge(); leftover → dump - PV deficit → ess.discharge(); leftover → grid import

  3. Inverter conversion loss applied to DC supply

  4. 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: GroundSourceHeatPumpBoiler

GSHPB scenario where the heat pump is supplied by PV + ESS + Grid.

The PV/ESS routing is resolved synchronously inside _augment_results after 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: