psychrometrics#

Moist-air thermodynamics behind the Mollier h,x-diagram: saturation pressure, dew point, wet-bulb temperature, enthalpy and density, the state() constructor for a complete moist-air state, and air-handling processes (heating, cooling, isothermal and adiabatic humidification, mixing, heat recovery) that can be chained and summarised.

The public module is pyedautils.psychrometrics:

from pyedautils.psychrometrics import state, heat, mix, pressure_from_altitude

p = pressure_from_altitude(450)   # ISA barometric formula: 96 035 Pa at 450 m

pressure_from_altitude is also what plot_mollier_hx(), plot_mollier_hx_fast() and state(altitude=...) use when only an altitude is given, so passing altitude=450 is enough there.

Note

The implementation lives in the private module pyedautils._mollier. Imports from there keep working, but only the names exported by pyedautils.psychrometrics are public API.

See Mollier h,x-Diagram for usage examples.

Tip

New to the h,x diagram? The h,x diagram in five minutes explains how to read it correctly (relative vs. absolute humidity, pressure, mould at cold surfaces), comfort and saving energy, with worked examples computed with these functions.

API Reference#

Moist-air thermodynamics (psychrometrics) behind the Mollier h,x-diagram.

Public entry point for the psychrometric functions: saturation pressure, dew point, wet-bulb temperature, enthalpy and density, pressure from altitude, the state() constructor for a complete moist-air state, and air-handling processes (heating, cooling, isothermal and adiabatic humidification, mixing, heat recovery) that can be chained and summarised:

from pyedautils.psychrometrics import pressure_from_altitude, state, heat

p = pressure_from_altitude(450)        # 96 035 Pa (ISA)
room = state(t=21, phi=0.5, p=p)

The implementation lives in the private module pyedautils._mollier; importing from there keeps working, but only the names re-exported here are public API.

pyedautils.psychrometrics.pressure_from_altitude(altitude_m) → float[source]#

Atmospheric pressure [Pa] at given altitude [m above sea level].

International Standard Atmosphere (ISA) barometric formula, valid in the troposphere (0–11 km). Reference state: p₀ = 101325 Pa, T₀ = 288.15 K, L = 0.0065 K/m.

Public import: from pyedautils.psychrometrics import pressure_from_altitude. The Mollier plots (pyedautils.plots.plot_mollier_hx(), plot_mollier_hx_fast) and state() apply it when only altitude is given.

Parameters:

altitude_m – Altitude in metres above sea level (scalar or array).

Returns:

Pressure in Pa, e.g. 101325 at 0 m and about 96 035 at 450 m.

Return type:

float

pyedautils.psychrometrics.p_sat(t)[source]#

Saturation vapour pressure [Pa] for temperature t [°C].

Works with scalars and numpy arrays. Convention-independent.

pyedautils.psychrometrics.temperature_p_sat(p_s) → float[source]#

Inverse of p_sat: temperature [°C] from saturation pressure [Pa].

Convention-independent. Uses Newton’s method on log(p_sat) for better convergence.

pyedautils.psychrometrics.vapor_pressure(x, p) → float[source]#

Water-vapour partial pressure [Pa] from absolute humidity and pressure.

Parameters:
  • x – Absolute humidity [kg/kg dry air].

  • p – Total pressure [Pa].

pyedautils.psychrometrics.dew_point(x, p) → float[source]#

Dew-point temperature [°C] from absolute humidity and pressure.

The dew point is the temperature at which the current water-vapour partial pressure equals the saturation pressure (φ = 1). Inverts temperature_p_sat for the vapour-pressure of the given (x, p).

pyedautils.psychrometrics.specific_volume(t, x, p) → float[source]#

Specific volume [m³/kg dry air] from temperature, abs. humidity, pressure.

Parameters:
  • t – Dry-bulb temperature [°C].

  • x – Absolute humidity [kg/kg dry air].

  • p – Total pressure [Pa].

pyedautils.psychrometrics.wet_bulb(t, x, p) → float[source]#

Wet-bulb temperature [°C] from dry-bulb T, abs. humidity x, pressure p.

Solves the adiabatic-saturation energy balance:

h(T_db, x) = h(T_wb, x_sat(T_wb)) − (x_sat(T_wb) − x)·c_W_fl·T_wb

iteratively (Newton with numerical derivative). Returns T_wb such that air at T_db, x is in equilibrium with water adiabatically evaporated to saturation at T_wb.

Notes

  • At saturation (x = x_sat(T_db)), T_wb = T_db.

  • Convention-independent (T_wb is a physical thermodynamic quantity).

pyedautils.psychrometrics.enthalpy(x, y) → float[source]#

Enthalpy [kJ/kg] from diagram coordinates (x, y).

Convention-independent: h = R_0*x + C_PL*y holds by the y-axis definition in both conventions.

pyedautils.psychrometrics.x_hy(h, y) → float[source]#

Absolute humidity from enthalpy [kJ/kg] and y-coordinate.

Convention-independent.

pyedautils.psychrometrics.y_hx(h, x) → float[source]#

y-coordinate from enthalpy [kJ/kg] and absolute humidity.

Convention-independent.

pyedautils.psychrometrics.temperature(x, y, convention=None) → float[source]#

Temperature [°C] from diagram coordinates (x, y).

pyedautils.psychrometrics.rel_humidity(x, y, p, convention=None) → float[source]#

Relative humidity [0–1] from diagram coordinates and pressure [Pa].

pyedautils.psychrometrics.density(x, y, p, convention=None) → float[source]#

Air density [kg/m³] from diagram coordinates and pressure [Pa].

pyedautils.psychrometrics.get_x_y(t, phi, p, convention=None)[source]#

(x, y) from temperature [°C], relative humidity [0–1], pressure [Pa].

Works with scalars and numpy arrays.

pyedautils.psychrometrics.get_x_y_tx(t, x, p, convention=None)[source]#

(x, y) from temperature [°C] and absolute humidity [kg/kg].

pyedautils.psychrometrics.y_phix(phi, x, p, convention=None) → float[source]#

y-coordinate from relative humidity, absolute humidity, pressure.

pyedautils.psychrometrics.x_phiy(phi, y, p, convention=None) → float[source]#

Absolute humidity from relative humidity and y-coordinate (Newton).

pyedautils.psychrometrics.y_rhox(rho, x, p, convention=None) → float[source]#

y-coordinate from density [kg/m³], absolute humidity, pressure [Pa].

pyedautils.psychrometrics.create_comfort(range_t, range_phi, range_x, p, convention=None)[source]#

Create the comfort-zone polygon as a list of (x, y) tuples.

Parameters:
  • range_t – (min, max) temperature [°C].

  • range_phi – (min, max) relative humidity [0–1].

  • range_x – (min, max) absolute humidity [kg/kg].

  • p – Pressure [Pa].

  • convention – Mollier convention (‘classical’ default, or ‘glueck’).

Returns:

List of (x, y) coordinate pairs forming a closed polygon.

class pyedautils.psychrometrics.MoistAirState(t: float, phi: float, x: float, h: float, t_wb: float, t_dp: float, p_v: float, rho: float, v: float, y: float, m_dot_dry: float | None = None, volume_flow: float | None = None, p: float = 101325.0, convention: str = 'classical')[source]#

Bases: object

Immutable moist-air state with all derived psychrometric properties.

Construct via state(), never call this constructor directly.

Intensive properties (always populated):

t: dry-bulb temperature [°C] phi: relative humidity [0..1] x: absolute humidity [kg/kg dry air] h: specific enthalpy [kJ/kg dry air] t_wb: wet-bulb temperature [°C] t_dp: dew-point temperature [°C] p_v: water-vapour partial pressure [Pa] rho: moist-air density [kg/m³] v: specific volume [m³/kg dry air] y: diagram y-coordinate (depends on convention)

Extensive properties (optional, None when not specified):

m_dot_dry: dry-air mass flow [kg/s] volume_flow: volumetric flow rate [m³/h], evaluated at v

Context:

p: total pressure [Pa] convention: Mollier coordinate convention (‘classical’ | ‘glueck’)

t: float#
phi: float#
x: float#
h: float#
t_wb: float#
t_dp: float#
p_v: float#
rho: float#
v: float#
y: float#
m_dot_dry: float | None = None#
volume_flow: float | None = None#
p: float = 101325.0#
convention: str = 'classical'#
pyedautils.psychrometrics.state(*, t=None, t_wb=None, t_dp=None, phi=None, x=None, h=None, p=None, altitude=None, m_dot_dry=None, volume_flow=None, convention=None) → MoistAirState[source]#

Construct a MoistAirState from any two psychrometric properties.

Exactly two of {t, t_wb, t_dp, phi, x, h} must be given. All other properties are computed and returned.

Parameters:
  • t – dry-bulb, wet-bulb, dew-point temperatures [°C]

  • t_wb – dry-bulb, wet-bulb, dew-point temperatures [°C]

  • t_dp – dry-bulb, wet-bulb, dew-point temperatures [°C]

  • phi – relative humidity [0..1]

  • x – absolute humidity [kg/kg dry air]

  • h – enthalpy [kJ/kg dry air]

  • p – total pressure [Pa] (defaults to sea-level standard when neither p nor altitude is given)

  • altitude – altitude in metres a.s.l.; mutually exclusive with p

  • m_dot_dry – dry-air mass flow [kg/s] (mutually exclusive with volume_flow)

  • volume_flow – volumetric flow [m³/h] at the state’s specific volume; converted internally to m_dot_dry

  • convention – ‘classical’ (default) or ‘glueck’

Raises:

ValueError – on invalid argument combinations (e.g. wrong number of psychrometric properties, both p and altitude, degenerate pair like (x, t_dp)).

class pyedautils.psychrometrics.ProcessBalance(name: str, dh: float, dx: float, power_kw: float | None = None, condensate_kgh: float | None = None, water_kgh: float | None = None, epsilon: float | None = None)[source]#

Bases: object

Energy and water balance for one psychrometric process step.

name#

process identifier (‘heat’, ‘cool’, …)

Type:

str

dh#

specific enthalpy change [kJ/kg dry air] (positive = heating, negative = cooling). 0 by convention for mix.

Type:

float

dx#

humidity-ratio change [kg/kg dry air] (positive = humidification, negative = drying). 0 by convention for mix.

Type:

float

power_kw#

m_dot_dry × dh [kW]; None when m_dot_dry unset

Type:

float | None

condensate_kgh#

m_dot_dry × |dx| × 3600 [kg/h] for cooling that crosses the dew point; None otherwise

Type:

float | None

water_kgh#

m_dot_dry × dx × 3600 [kg/h] for humidification; None otherwise

Type:

float | None

epsilon#

efficiency for heat_recovery; None otherwise

Type:

float | None

name: str#
dh: float#
dx: float#
power_kw: float | None = None#
condensate_kgh: float | None = None#
water_kgh: float | None = None#
epsilon: float | None = None#
pyedautils.psychrometrics.heat(s, t_out=None, dt=None, power_kw=None)[source]#

Sensible heating at constant absolute humidity x.

Parameters:
  • s – input MoistAirState

  • t_out – target dry-bulb temperature [°C]

  • dt – temperature change [K]; positive = heating

  • power_kw – heating power [kW]; requires s.m_dot_dry to be set

Returns:

(new_state, ProcessBalance)

pyedautils.psychrometrics.cool(s, t_out=None, dt=None)[source]#

Cooling. Sensible while T_out ≥ dew point; latent below (output state saturated at the coil temperature, BF = 0).

Parameters:
  • s – input MoistAirState

  • t_out – target dry-bulb temperature [°C] (= coil temperature in the latent regime)

  • dt – temperature change [K]; should be negative

Returns:

(new_state, ProcessBalance)

pyedautils.psychrometrics.humidify_isothermal(s, x_out=None, phi_out=None)[source]#

Steam humidifier: T stays constant, water vapour added.

Parameters:
  • s – input MoistAirState

  • x_out – target humidity ratio [kg/kg]

  • phi_out – target relative humidity [0..1]

Returns:

(new_state, ProcessBalance)

pyedautils.psychrometrics.humidify_adiabatic(s, x_out=None, phi_out=None)[source]#

Adiabatic humidifier (evaporative): h stays constant, water added, T drops.

Parameters:
  • s – input MoistAirState

  • x_out – target humidity ratio [kg/kg]

  • phi_out – target relative humidity [0..1]

Returns:

(new_state, ProcessBalance)

pyedautils.psychrometrics.mix(s_a, s_b)[source]#

Adiabatic mixing of two moist-air streams.

Both inputs must have m_dot_dry set and matching pressure. The output has m_dot_dry = s_a.m_dot_dry + s_b.m_dot_dry and the mass-weighted x and h.

Returns:

(mixed_state, ProcessBalance with dh=dx=0)

pyedautils.psychrometrics.heat_recovery(supply, extract, eps_sensible, eps_latent=0.0)[source]#

Two-stream heat (and optionally moisture) recovery exchanger.

Returns the new state of the SUPPLY side after recovery. The extract side is treated as an input boundary condition; compute it separately if you need the exhaust state.

Parameters:
  • supply – inlet state of the supply stream

  • extract – inlet state of the extract (return) stream

  • eps_sensible – temperature effectiveness [0..1]

  • eps_latent – humidity effectiveness [0..1]; default 0 (sensible-only HX)

Returns:

(new_supply_state, ProcessBalance)

pyedautils.psychrometrics.chain_summary(steps)[source]#

Tabulate a sequence of (label, ProcessBalance) tuples as a DataFrame.

Parameters:

steps – iterable of (label, ProcessBalance) pairs.

Returns:

pandas.DataFrame indexed by label with columns [name, dh, dx, power_kw, condensate_kgh, water_kgh].