Skip to main content

heatpumpmodel

Single-source implementation of the steady-state (bin-style) heat-pump performance model of

A. Rogeau, R. Vieubled, M. de la Ruche, G. Girard, "A generic methodology for mapping the performance of various heat pump configurations considering part-load behavior", Energy and Buildings 2024, https://doi.org/10.1016/j.enbuild.2024.114471

numpy is the only dependency. The package holds physics only: it knows nothing about a building stock, a weather reader or a simulation pipeline. Callers pass hourly arrays in and keep their own integration layer.

It exists so that buildingmodel and building_eload share one copy of the physics instead of two that drift apart (buildingmodel issue #47).

Install

pip install heatpumpmodel
# or, from a checkout:
pip install -e ".[dev]"

Use

import numpy as np
from heatpumpmodel import (
    Emitter, HeatPumpConfig, Mode, System, Technology,
    compute_T_base, hourly_power_split, scop, size_heat_pump,
)

# hourly series over a year: outdoor temperature (°C), relative humidity (%),
# and heat demand in any single consistent unit (it cancels in every ratio).
t_out = ...
rh = ...
demand = np.clip(20.0 - t_out, 0.0, None)

cfg = HeatPumpConfig(System.A_W, Mode.M, Emitter.MT, Technology.ON_OFF)
sizing = size_heat_pump(cfg, demand, t_out, compute_T_base(t_out))
p_h, p_e, p_h_backup = hourly_power_split(cfg, sizing, demand, t_out, rh=rh)

print(scop(p_h, p_e))            # seasonal COP

seasonal_performance(cfg, sizing, demand, t_out, rh=rh) bundles {"scop", "ecr", "peak_share"} in one call.

Configuration axes

Axis Values
System A_A (air/air), A_W (air/water), G_W (ground/water)
Mode M, M_SB, BA (bivalent alternative), BP (bivalent parallel)
Emitter FH 35 °C, LT 45 °C, MT 55 °C, HT 65 °C, FAN_COIL
Technology ON_OFF, BI_COMPRESSOR, INVERTER

Sub-models (COPCurve, TwoBranchCOPCurve, DefrostModel, PartLoadModel, WeatherCompensation) are dataclasses on HeatPumpConfig and can be replaced with manufacturer-specific fits.

The A/W COP curve (behavioural break in 0.3.0)

The default A/W full-load curve is TwoBranchCOPCurve: a defrost-degraded branch (5.60 − 0.09·ΔT + 0.0005·ΔT², T_ext ≤ −3 °C) and a frost-free branch (9.302 − 0.223·ΔT + 0.0017·ΔT², T_ext ≥ +6 °C), linearly interpolated in the outdoor-air temperature between the two. Before 0.3.0 only the degraded branch existed, applied at every outdoor temperature — the collapsed form of the source's Eq. (3.8) that the authors' published code produces. A/W SCOP therefore rises — 4.4 % to 19.5 % across the emitter × technology × mode grid on a synthetic Paris-like year, most in mild weather; A/A and G/W are unaffected.

Because heating_capacity (Eq. 6) reads the same curve, it is a capacity change too: for A/W, M mode, floor heating, inverter on that year, capacity at T_so = +10 °C is +31 %, mean capacity over heating hours +22 %, hours below minimum modulation go 843 → 1402, T_biv2 11.8 → 9.7 °C and ECR 81.1 → 89.7. Q_nom/P_e_nom are unchanged in M / M+SB mode (their design point sits on the degraded branch for any T_base ≤ −3 °C) but move slightly in BA/BP, whose backup bisection runs on the branch-aware capacity.

The best case of that grid (FH, inverter, BA) now reaches SCOP ≈ 4.67, at or above the top class of the paper's own European map — headroom that follows from implementing the printed source faithfully, not from any empirical validation. To reproduce pre-0.3.0 A/W numbers (and to reproduce the paper's Fig. 4, which is an output of the collapsed curve):

from heatpumpmodel import HeatPumpConfig, LEGACY_A_W_SINGLE_CURVE, System, Mode, Emitter, Technology

cfg = HeatPumpConfig(System.A_W, Mode.M, Emitter.MT, Technology.ON_OFF,
                     cop_curve=LEGACY_A_W_SINGLE_CURVE)

TwoBranchCOPCurve.cop_fl requires the source (outdoor-air) temperature and raises if it is missing — so COP_CURVES[System.A_W].cop_fl(dT) without t_so, which used to return an array, now raises. See its docstring in heatpumpmodel/core.py for the provenance chain (PhD thesis tel-02969503, not Ruhnau et al.) and for the documented overlap between the branch structure and DefrostModel — which also means an A/W-vs-A/A SCOP gap now straddles two different defrost representations and partly reflects curve lineage, not physics.

Check the sizing solve status on BA/BP

sizing = size_heat_pump(cfg, demand, t_out, compute_T_base(t_out))
if not sizing.converged:                 # sizing.solve_status names which rail was hit
    ...                                  # surface it; do not publish a railed Q_nom

For Mode.BA / Mode.BP, Q_nom is solved by bisection for a 10 % backup share. When that target is unreachable the solver returns a bracket rail (4·h_base or 1e-3·h_base) as a best-effort size, and every quantity derived from it is an artefact of the search interval rather than a sizing. Since 0.3.0 HeatPumpSizing.solve_status (SizingSolveStatus.CONVERGED / RAILED_LOW / RAILED_HIGH) and the derived converged property make that visible. converged means "the solve was not railed" — it does not certify that the 10 % target was met to any tolerance, and it is also the default for the closed-form M / M+SB rule, so check Q_nom for plausibility as well.

From French DPE data

heatpumpmodel.dpe maps buildingdata's heat_pump_type / heating_emitter_type / heat_pump_installation_period DPE columns onto a HeatPumpConfig, so buildingmodel and building_eload resolve the same building to the same machine in both the static and dynamic stages instead of each guessing independently:

from heatpumpmodel import HeatPumpConfig, System, Mode, Emitter, Technology, config_from_dpe

default = HeatPumpConfig(System.A_W, Mode.M, Emitter.MT, Technology.ON_OFF)
cfg = config_from_dpe("air/air", "air", "[2015, 2100]", default=default)

Any DPE column that is None falls back to the matching axis of default; a present value outside the module's vocabulary raises ValueError rather than drifting silently. See heatpumpmodel/dpe.py for the vocabulary vintage and the full mapping tables.

Conventions

  • Temperatures in °C, ΔT gaps in K.
  • Powers/demand in one arbitrary, self-cancelling unit.
  • Relative humidity in percent [0, 100] — the EPW convention.
  • Air-source configs (A/A, A/W) require an rh series: a missing, all-NaN, all-zero or partially non-finite rh, a [0, 1] fraction-convention series, or any value outside [0, 100] raises ValueError rather than silently skipping the defrost derate (which would leave SCOP ~5 % optimistic). The checks are whole-series: one bad summer hour fails the call.
  • Every effective COP is floored at 1.0 — a heat pump never draws more electricity than the resistance backup would for the same heat.

Documentation

doc/heat_pump_model_spec.md is the implementation contract: equation-by-equation mapping to the paper, coefficient provenance (including the values resolved from the authors' Zenodo code rather than the PDF), and the documented deviations.

Tests

pytest                          # hermetic suite, synthetic climate
HEATPUMPMODEL_PARIS_EPW=/path/to/paris.epw pytest -m integration

The integration test reproduces the paper's Fig. 4 SCOP values and needs the authors' Paris-Montsouris TMY EPW; it skips when that file is not supplied. See its docstring for why an ERA5 Paris record does not substitute.

Licence

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

heatpumpmodel-0.3.0.tar.gz (55.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

heatpumpmodel-0.3.0-py3-none-any.whl (54.4 kB view details)

Uploaded Python 3

File details

Details for the file heatpumpmodel-0.3.0.tar.gz.

File metadata

  • Download URL: heatpumpmodel-0.3.0.tar.gz
  • Upload date:
  • Size: 55.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.15

File hashes

Hashes for heatpumpmodel-0.3.0.tar.gz
Algorithm Hash digest
SHA256 9f14a3cb7c33d909d61cc74b44b9143611d7cb3e5b441c04e702176066644a8a
MD5 27775e818847e5b3a1f80496f6489d8d
BLAKE2b-256 ab36c9201e21bd37c921ae60b13e834f7d15f229aea37d8e78cbeab57ec8cc2a

See more details on using hashes here.

File details

Details for the file heatpumpmodel-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: heatpumpmodel-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 54.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.15

File hashes

Hashes for heatpumpmodel-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 adf4aab6488354824aadc0683e3e982192071823825cd9a1a945ece1c1a3ac13
MD5 ae0b63f6e1df93720e5e99fe01372fa8
BLAKE2b-256 89f684c2f3e72a04f98fbcffa2a91d9a02656c15f6d9160a92d71be6333fff1d

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.0

2 files

This release

0.3.0 This release

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page