API reference

Manual reference for the public API. Everything is imported from the top level:

import enerhabitat as eh
# eh.Location, eh.System, eh.System2D, eh.HollowBlock, eh.Slab, eh.Fill,
# eh.config, eh.config2d

Location

Reads an EPW file and builds the average day of a month.

loc = eh.Location("./epw/example.epw")

Attributes: the EPW path is stored in file; assigning a new path reloads the header. The rest are read-only, recovered from the EPW header:

Attribute Type Description
file str Path to the EPW file (writable)
city str City, from the EPW header
latitude, longitude float Degrees
altitude float Metres
timezone datetime.timezone Fixed offset built from the EPW decimal UTC offset (local standard time, no DST; fractional offsets like +5.5/+5.75 preserved)

Methods:

  • meanDay(day="15", month="current", year="current"): average-day DataFrame (Ta; Ig GHI, Ib DNI, Id DHI; Tn, DeltaTn; solar position), cached; see how it is built. month selects which EPW rows are averaged; day and year only set the synthetic date used for the solar geometry and the index. In a TMY, year=2025 does not select observations from 2025.
  • info(): prints the attributes.
  • flag(): dict with metadata of the last meanDay() call (e.g. flag()["date"]).

System (1D)

A multilayer wall or roof tied to a Location.

wall = eh.System(location=loc, tilt=90, azimuth=0, absortance=0.8, layers=[])

Attributes:

Attribute Type Description
location Location Climate source
tilt float Degrees from horizontal: 0 roof, 90 vertical wall. Any tilt is accepted in 1D (\(I_s\) is transposed by pvlib and \(RF\) varies linearly from 3.9 °C at 0° to 0 at 90°); the 2D elements only admit 0/90
azimuth float Surface azimuth, pvlib convention (N=0, E=90, S=180, W=270)
absortance float Solar absorptance of the outdoor surface, [0, 1]; validated, so values outside the range raise ValueError
layers list[tuple[str, float]] (material, thickness_m), outside → inside
setpoint float AC setpoint override; None (default) → the neutrality temperature \(T_n\) (?@eq-tn in the 1D model). Ignored by solve()

Read-only results (energy per unit surface area over the converged day, in J/(m²·day); divide by 3600 for Wh or 3.6e6 for kWh):

Attribute Set by Meaning
energy_transfer solve() Energy delivered to the indoor air
cooling_energy solveAC() Cooling demand to hold \(T_n\)
heating_energy solveAC() Heating demand to hold \(T_n\)
days both Day-to-day iterations used by the last solve (capped at 60)
day_error both Final day-to-day error (°C): max of the solid-field mean change and the indoor-state change
converged both True if the periodic regime was reached; otherwise a RuntimeWarning is emitted
energy_imbalance solve() Energy-closure check \(\|Q_{in}-Q_{out}\|/\max(Q_{in},Q_{out})\) (≈ 0 when periodic); None after solveAC()
Tso, Tsi both Read-only surface temperature Series (outdoor/indoor surface, °C) of the last solve, on the Tsa() grid; None before solving. They concatenate directly with Ti and Tsa()

Methods:

  • Tsa(): sun–air temperature and Is for the current absorptance/orientation/month, on the solver’s time grid (results concatenate directly with solve() output).
  • solve(): free-running indoor temperature Ti (pandas.Series named "Ti"; use .to_frame("Ti") when a DataFrame is needed).
  • solveAC(): Ti held at \(T_n\) (constant pandas.Series); sets cooling_energy/heating_energy.
  • add_layer(material, width) / remove_layer(index): edit the stack (appends at the inside).
  • info().

The caches (in 1D and 2D alike) compare the current inputs on every call: Tsa() recomputes when absortance, tilt, azimuth, the meanDay selection, the Location or the configuration change, and the solvers additionally watch layers, including in-place mutations (.append(...)). solve() and solveAC() keep separate caches, so alternating modes reuses each result. A prior manual Tsa() call is optional, for inspection.

System2D

Mirror of System for cross-sections that are heterogeneous across their width. Same Location, tilt, azimuth, absortance, setpoint, Tsa(), solve(), solveAC() and result attributes. Differences:

  • layers contains, besides ordinary (material, thickness) tuples, exactly one 2D element: a HollowBlock or a Slab. The element’s thickness is derived from its geometry.
  • The stack accepts at most 7 layers, including the 2D element; the section builder uses the fixed seven slots (L1…L7) inherited from the C engine. Exceeding it raises ValueError: at most 7 layers (including the 2D element). The 1D System has no layer limit.
  • Orientation is validated: HollowBlock requires tilt = 90, Slab tilt = 0.
  • Additional outputs beyond the 1D set: Tfield, the Thueco Series and the inspectors preview() / section_report().
  • Extra attributes:
Attribute Description
days Day-to-day iterations used by the last solve
converged True if the last solve met tol_day and every inner step converged; a RuntimeWarning is emitted otherwise
day_error Final day-to-day error (°C) of the last solve: max over the solid field, \(\|\Delta T_i\|\) and each cavity \(\|\Delta T_h\|\)
inner_iterations Largest inner sweep count observed in the last solve
energy_imbalance Energy-closure check \(\|Q_{in}-Q_{out}\|/\max(Q_{in},Q_{out})\) of the last free-running solve (≈ 0 when periodic); None after solveAC()
Tso, Tsi Read-only surface temperature Series (outer/inner-surface mean, °C) of the last solve, on the Tsa() grid; None before solving. As in 1D, they concatenate directly with Ti and Tsa(). Sampling convention inherited from the C: Tso is recorded at the start of each time step, Ti/Tsi at the end
Thueco Read-only cavity-air temperature Series (°C): the mean over the N cavities in Slab, NaN with Fill.SOLID; None before solving
Tfield Temperature field (nx, ny) in °C at the end of the last simulated day (outside → inside along the second axis), useful for cross-section plots
  • Section inspector (before solving):
wall.section_report()   # table: node types + materials (k, ρc, y-range)
wall.preview()          # to-scale panels (matplotlib, extra `viz`; ASCII fallback)
wall.section()          # NT / k / rhoc arrays + mesh

HollowBlock

Concrete hollow block for walls (tilt = 90): a shell of one material with one cavity per repeating cell.

block = eh.HollowBlock(
    material      = "Concreto",
    fill_type     = eh.Fill.AIR,     # or eh.Fill.SOLID
    fill_material = None,            # required if SOLID
    emissivity    = 0.9,             # cavity-wall emissivity (used if AIR)
    geometry      = {...},           # see below, metres
)
geometry key Meaning
web Half web (rib) thickness; the full web between cavities is 2·web
block_width Cavity width
cover_top Outer shell
cavity Cavity height (through the thickness)
cover_bottom Inner shell

Cell width = 2·web + block_width; thickness = cover_top + cavity + cover_bottom.

Slab

Joist-and-block (vigueta y bovedilla) unit for roofs (tilt = 0): compression topping, L-shaped rib (web + foot) at each cell edge, filler block, and n_cavities equal cavities.

slab = eh.Slab(
    rib_material     = "Concreto",
    block_material   = "Bovedilla",
    topping_material = "Concreto",
    fill_type        = eh.Fill.AIR,   # or eh.Fill.SOLID (+ fill_material)
    fill_material    = None,
    emissivity       = 0.9,
    geometry         = {...},
)
geometry key Meaning
web, foot Rib web and foot (the L profile)
shoulder Filler block between rib and cavities
n_cavities, cavity_width Number and width of the equal cavities
topping Compression topping thickness
topping_cap Topping above the rib web (the web rises below this line)
cover_top, cavity, cover_bottom Filler-block band through the thickness

Cell width = 2·(web+foot) + (n+1)·shoulder + n·cavity_width; thickness = topping + cover_top + cavity + cover_bottom.

Fill

Enum for the cavity content of HollowBlock/Slab: Fill.AIR (cavity physics: radiation + Nusselt convection, needs emissivity) or Fill.SOLID (conduction only, needs fill_material).

config (global)

Global singleton; changing it affects all subsequent computations.

Attribute Default Description
file "materials.ini" Materials file path
La 2.5 m Indoor air depth
Nx 200 1D control volumes
ho 13 W/(m²·K) Outdoor film coefficient (NOM)
hi 8.1 W/(m²·K) Indoor film coefficient for walls (NOM, vertical-surface value)
hi_up 9.4 W/(m²·K) Indoor coefficient for roofs, upward heat flow (NOM)
hi_down 6.6 W/(m²·K) Indoor coefficient for roofs, downward heat flow (NOM)
hi_flow True Roofs (tilt < 60°) pick hi_up/hi_down per time step; False → fixed hi everywhere
dt 10 s Fixed; assignments are ignored (why)
AIR_DENSITY 1.18 kg/m³ Indoor/cavity air density
AIR_HEAT_CAPACITY 1005 J/(kg·K) Indoor/cavity air specific heat

Methods: info(), to_dict(), reset() (restores the numeric defaults; file/materials are user-provided and are kept), materials_list(), materials_dict(); config.materials is a read-only dict of material properties (.k, .rho, .c).

Setters validate, raising ValueError: La, ho, hi, hi_up, hi_down and the air properties must be > 0; Nx must be an integer ≥ 3; materials must have k, rho, c > 0 (checked when the .ini is loaded; on failure the previous materials are kept). Layer thicknesses must be > 0 (checked at solve time).

config2d (2D)

Attribute Default Description
nx 80 Nodes across the cell width (adiabatic sides)
ny 160 Nodes through the thickness (outside → inside)
tol_inner 1e-8 °C Inner-step tolerance: max node update and max scaled equation residual must both fall below it
tol_day 5e-4 °C Day-to-day convergence tolerance
max_days 60 Cap on day-to-day iterations
max_inner 10000 Cap on inner sweeps per time step; if hit, the solve is flagged converged = False

Methods: info(), to_dict(), reset(), validate().

The attributes are plain (assignment is not checked); validate() runs at the start of every System2D solve and raises ValueError if nx/ny are not integers ≥ 3, a tolerance is ≤ 0, or max_days/max_inner are not integers ≥ 1.