Integrations¶
Optional adapters for coupling TMHP to building-energy simulators and co-simulation masters. These modules are intentionally kept out of the top-level package import path so the core package remains usable without the adapter-specific runtimes.
For usage-first guides, model boundaries, and simulator wiring, see EnergyPlus Python Plugin and FMI FMU export.
Install the FMI dependencies with the integrations extra:
uv sync --extra integrations --locked
The EnergyPlus adapter is different: pyenergyplus is supplied by an
EnergyPlus installation and is not a PyPI dependency.
Interoperability contracts¶
FMI co-simulation is exposed through separate FMI 2.0 and FMI 3.0 adapters.
FMI 2.0 uses PythonFMU; FMI 3.0 uses PythonFMU3. The generated FMUs are
tool-coupling artifacts: Python, TMHP, CoolProp, NumPy, and SciPy must be
available in the importing environment. The FMU model descriptions are validated
with FMPy in tests. The XML declares units for power, temperature, DHW draw, and
COP so importers can perform basic unit checks. The slaves reject invalid
importer inputs (non-finite time/input values, non-positive communication step
sizes, or negative DHW draw) before advancing the internal step() state and
expose failure_reason="invalid_input". The FMI 3.0 slave reports that case
as a discarded step with early return.
EnergyPlus coupling uses the Python Plugin DataExchange API. The adapter first
resolves and validates all handles, then reads only finite boundary values
before calling analyze_steady(). Invalid boundary data is reported through
issue_severe(), the plant actuator is driven to a safe off state, and the
plugin returns a non-zero status instead of silently computing with bad data.
Compressor electricity is exposed with explicit plugin-global units:
tmhp_E_cmp_J receives timestep energy in joules by multiplying cycle power
in watts by EnergyPlus’s fractional-hour system_time_step(), while optional
tmhp_E_cmp_W receives the instantaneous cycle power. Older IDFs that
declare only tmhp_E_cmp are still accepted; that legacy global is treated as
a joule sink.
Adapter package¶
Building-energy-simulation co-simulation adapters (#165).
Optional subpackage — each adapter needs an extra dependency that the core
tmhp package does not require, so this package is not imported by
tmhp/__init__.py; import tmhp stays usable without those deps and only
from tmhp.integrations... import ... pulls them in.
Adapters depend only on the public seams of the heat-pump models
(AirSourceHeatPumpBoiler.step() / .analyze_steady()), never on private
helpers:
tmhp.integrations.fmu(#165 P1) — FMI 2.0 FMU wrappingstep()(the dynamic kernel; the FMU owns the storage-tank state). Needspythonfmu.tmhp.integrations.fmu3— FMI 3.0 Co-Simulation FMU wrapping the samestep()seam. Needspythonfmu3.tmhp.integrations.energyplus_plugin(#165 P2) — EnergyPlus Python Plugin surrogating the ASHPB as aPlantComponent:UserDefinedthroughanalyze_steady()(the steady-state seam; EnergyPlus owns theWaterHeater:Mixedtank state). Runs inside EnergyPlus’s embedded CPython, so it needs the bundledpyenergyplus(not pip-installable).
- class tmhp.integrations.TmhpAshpbSlave(**kwargs)[source]¶
Bases:
Fmi2SlaveASHPB single-timestep co-simulation kernel (FMI 2.0).
- Parameters:
kwargs (
Any)
- author = 'BET Lab'¶
- description = 'tmhp ASHPB one-dt co-simulation kernel (FMI 2.0)'¶
- class tmhp.integrations.TmhpAshpbFmi3Slave(**kwargs)[source]¶
Bases:
Fmi3SlaveASHPB single-timestep co-simulation kernel (FMI 3.0).
- Parameters:
kwargs (
Any)
- author = 'BET Lab'¶
- description = 'TMHP ASHPB one-dt co-simulation kernel (FMI 3.0)'¶
- to_xml(model_options=None)[source]¶
Build a static FMI 3.0 model description for PythonFMU3.
- Parameters:
model_options (
Optional[dict[str,str]])- Return type:
Element
- class tmhp.integrations.TmhpPlantInit[source]¶
Bases:
EnergyPlusPluginOne-shot sizing actuators so the plant loop dispatches load to the ASHPB.
Bind to the
PlantComponent:UserDefinedinitialization program-calling manager (E+ runs it once before plant sizing).
- class tmhp.integrations.TmhpPlantSurrogate[source]¶
Bases:
EnergyPlusPluginPer-call ASHPB driver: read loop inlet + outdoor, solve, actuate.
Bind to the
PlantComponent:UserDefinedsimulation program-calling manager. Exposes compressor electricity through the plugin globaltmhp_E_cmp_J(declare it inPythonPlugin:Variablesand meter it as summed joules).tmhp_E_cmp_Wcan be declared for averaged power output.
FMI 2.0 co-simulation¶
FMI 2.0 co-simulation FMU wrapping the tmhp ASHPB step() kernel (#165 P1).
The slave advances AirSourceHeatPumpBoiler one communication
step at a time through the public step() seam, so any FMI master (fmpy,
OMSimulator, Dymola, …) can drive the refrigerant-cycle-resolved heat-pump
model as a co-simulation component.
Build (PythonFMU CLI or API):
pythonfmu build -f src/tmhp/integrations/fmu.py <project_folder>
# or: FmuBuilder.build_FMU(".../fmu.py", dest="out/")
Run:
import fmpy
fmpy.simulate_fmu("TmhpAshpbSlave.fmu", ...)
Lead track is FMI 2.0 (co-simulation only): tmhp exposes no continuous
state derivatives, and 2.0 co-sim is the most broadly importable flavour.
Boundary outputs are sanitized to avoid non-finite numeric values, while
converged and failure_reason preserve step-level diagnostics for the
importing master.
Note
Native-wheel caveat (CoolProp/numpy/scipy): the FMU is a tool-coupling
artifact, not a hermetic binary — the importing environment must provide
tmhp and its native dependencies for the chosen (OS, arch, Python-ABI).
No save-state/rollback at this scope (single-pass co-sim only).
- class tmhp.integrations.fmu.TmhpAshpbSlave(**kwargs)[source]¶
Bases:
Fmi2SlaveASHPB single-timestep co-simulation kernel (FMI 2.0).
- Parameters:
kwargs (
Any)
- author = 'BET Lab'¶
- description = 'tmhp ASHPB one-dt co-simulation kernel (FMI 2.0)'¶
FMI 3.0 co-simulation¶
FMI 3.0 co-simulation FMU wrapping the TMHP ASHPB step() kernel.
This adapter mirrors tmhp.integrations.fmu at the TMHP boundary but uses
pythonfmu3 and the FMI 3.0 Fmi3StepResult return contract. It targets
FMI 3.0 Co-Simulation only: the FMU owns the ASHPB dynamic state and advances it
once for each importer communication step.
Build:
pythonfmu3 build -f src/tmhp/integrations/fmu3.py .
The adapter intentionally does not expose FMI 3.0 clocks, Scheduled Execution, or array variables. Those features are useful for embedded controls and multi-rate models; TMHP’s current ASHPB FMU boundary is scalar Co-Simulation.
- class tmhp.integrations.fmu3.TmhpAshpbFmi3Slave(**kwargs)[source]¶
Bases:
Fmi3SlaveASHPB single-timestep co-simulation kernel (FMI 3.0).
- Parameters:
kwargs (
Any)
- author = 'BET Lab'¶
- description = 'TMHP ASHPB one-dt co-simulation kernel (FMI 3.0)'¶
- to_xml(model_options=None)[source]¶
Build a static FMI 3.0 model description for PythonFMU3.
- Parameters:
model_options (
Optional[dict[str,str]])- Return type:
Element
EnergyPlus Python Plugin¶
EnergyPlus Python Plugin adapter for the tmhp ASHPB (#165 P2).
Surrogates AirSourceHeatPumpBoiler inside EnergyPlus as a
PlantComponent:UserDefined device. Each plant-solver call hands the plugin
the loop inlet temperature, mass flow, specific heat, and load request (plus the
site outdoor drybulb); the plugin solves the refrigerant cycle with the public
analyze_steady() seam (#165 P0) and writes back the outlet-temperature and
mass-flow actuators, routing compressor electricity to a metered output.
Why analyze_steady (not step()): EnergyPlus owns the storage-tank state
through WaterHeater:Mixed and asks the component for a steady answer each
timestep (given the inlet/outdoor conditions and the loop load, what is the
leaving temperature and the electric input). The dynamic step() kernel — and
its tank model — is instead what the FMU adapter (#165 P1,
tmhp.integrations.fmu) wraps. The two BES adapters therefore ride on
different seams of the same model.
Runtime: EnergyPlus runs Python plugins in its embedded CPython, so
pyenergyplus is provided by the EnergyPlus install (not pip-installable) and
tmhp + native deps (CoolProp/numpy/scipy) must be built for that
interpreter’s ABI and pointed to via PythonPlugin:SearchPaths. Verify with an
import-only smoke plugin first — a wrong-ABI CoolProp wheel fails silently.
Configuration (environment variables, all optional):
|
refrigerant (default |
|
nominal HP capacity in W (default |
|
named parameter preset (default empty = model defaults) |
|
compressor displacement in cm³ (required with preset) |
|
rated outdoor fan flow in m³/s (required with preset) |
|
|
|
design loop volume flow in m³/s (default |
|
plugin global receiving timestep energy [J]
(default |
|
optional plugin global receiving power [W]
(default |
|
if set, append a per-call/tally log to this path |
IDF wiring (see the issue #180 / the validated demo for a full example): one
PlantComponent:UserDefined named ASHPB_UserDefined with an init manager
bound to TmhpPlantInit and a simulation manager bound to
TmhpPlantSurrogate, a PythonPlugin:Variables global
tmhp_E_cmp_J for summed energy metering, and optionally tmhp_E_cmp_W for
averaged power reporting. The legacy global tmhp_E_cmp is still accepted as
an energy sink for older IDFs.
API strings (actuator component type "Plant Connection 1"; internal
variables "... for Plant Connection 1") are verified against EnergyPlus
24.2.0; reconfirm them for other releases.
- class tmhp.integrations.energyplus_plugin.TmhpPlantInit[source]¶
Bases:
EnergyPlusPluginOne-shot sizing actuators so the plant loop dispatches load to the ASHPB.
Bind to the
PlantComponent:UserDefinedinitialization program-calling manager (E+ runs it once before plant sizing).
- class tmhp.integrations.energyplus_plugin.TmhpPlantSurrogate[source]¶
Bases:
EnergyPlusPluginPer-call ASHPB driver: read loop inlet + outdoor, solve, actuate.
Bind to the
PlantComponent:UserDefinedsimulation program-calling manager. Exposes compressor electricity through the plugin globaltmhp_E_cmp_J(declare it inPythonPlugin:Variablesand meter it as summed joules).tmhp_E_cmp_Wcan be declared for averaged power output.