Same Wall, Same Insulation, One Swap: The Glaser Condensation Check in 80 Lines of Python
Build a U-value and Glaser condensation check in plain Python: same wall, same insulation, one swap, and 0 vs 4.44 kg/m² of winter water. Code included.
A wall is two slopes drawn on one axis. Heat runs downhill from 20 °C inside to −5 °C outside. Water vapour runs downhill too, from the humid room to the dry winter air. Draw both against the summed vapour resistance sd, and water appears wherever the vapour line would have to climb above the saturation curve. The section render does not get a vote.
This piece is evergreen. The only hook is the heating season. Late October is when interior-insulation retrofits get specified, and behind protected street facades in Zürich, Basel and Bern the insulation often has to go inside. The arc is U-value, then the temperature at every face, then saturation pressure, then the Glaser “string pulled tight”, then three runs on the same four materials. All three walls have the same U-value, 0.206 against 0.205 W/m²K. Their moisture results are 0, 4.44 and 0.05 kg/m².
The Tool: plain Python 3 and its standard math module. Guido van Rossum started Python at CWI in Amsterdam. Today it is stewarded by the Python Software Foundation and by thousands of maintainers you mostly never see. There is nothing to clone and nothing to install, and the program pastes unchanged into the Rhino 8 ScriptEditor, McNeel’s native CPython 3 host. It is the hand-check underneath every commercial building-physics tool. Build it once and you know what those tools are doing.
Setup:
# Python 3.10 or newer (the run below used 3.10.11). No packages needed.
python --version
mkdir wall-check
cd wall-check
# paste the program from "The code, explained" into wall_check.py, then:
python wall_check.py
# The first two lines printed must read 1168 Pa and 321 Pa.First steps:
- Save the program as
wall_check.pyand run it. The top two lines are a self-check: 1,168 Pa and 321 Pa. Both match Baunetzwissen’s boundary values to the pascal. If yours differ, a constant was mistyped. - Read run A, insulation outside. The last line says the wall stays dry.
- Read run B. The same wool has moved to the warm side, and the program reports 4.44 kg/m² of water at its cold face.
- Read run C. One foil goes in, and the result drops to 0.05 kg/m².
←TODAY: Two walls with U = 0.206 and 0.205 W/m²K differ by 4.44 kg/m² of winter water, and only the layer order changed.
→3012: The buildings that last are the ones whose moisture logic still fits in a plain text file anyone can open.
Fulcrum: The U-value tells you how much heat leaves. Only the layer order tells you where the water stays.
The code, explained: this is the whole program, exactly as the editor ran it on 27 October 2026. The material values are example inputs, not norm or product values. Swap in the λ and μ from your own product sheets.
# wall_check.py - U-value and Glaser condensation check for a wall build-up
# Standard library only. Layers are listed from INSIDE to OUTSIDE.
import math
RSI, RSE = 0.13, 0.04 # surface resistances, m2K/W (horizontal heat flow)
TI, PHI_I = 20.0, 0.50 # inside: 20 C, 50 % RH
TE, PHI_E = -5.0, 0.80 # outside: -5 C, 80 % RH (winter period)
HOURS = 90 * 24 # December to February
DELTA0 = 2.0e-10 # vapour permeability of still air, kg/(m s Pa)
def psat(t):
"""Saturation vapour pressure in Pa (Magnus-type fit, water / ice)."""
if t >= 0:
return 610.5 * math.exp(17.269 * t / (237.3 + t))
return 610.5 * math.exp(21.875 * t / (265.5 + t))
def lower_hull(pts):
"""Lower convex hull of (x, y) points sorted by x - the Glaser 'string'."""
hull = []
for p in pts:
while len(hull) >= 2:
(x1, y1), (x2, y2) = hull[-2], hull[-1]
if (x2 - x1) * (p[1] - y1) - (y2 - y1) * (p[0] - x1) <= 0:
hull.pop()
else:
break
hull.append(p)
return hull
def check(name, layers):
# layers: (label, thickness m, lambda W/mK, mu) or (label, 0, 0, sd_m) for a foil
r_total = RSI + RSE + sum(d / lam for _, d, lam, _ in layers if d > 0)
u = 1 / r_total
q = u * (TI - TE) # heat flux, W/m2
pi, pe = PHI_I * psat(TI), PHI_E * psat(TE)
print(f"\n=== {name} ===")
print(f"U = {u:.3f} W/m2K heat flux = {q:.1f} W/m2")
t, sd, r = TI - q * RSI, 0.0, RSI
pts = [(0.0, pi)]
labels = ["inside air"]
print(f" {'interface':<30}{'T [C]':>7}{'sd [m]':>8}{'psat [Pa]':>11}")
print(f" {'inside surface':<30}{t:7.1f}{sd:8.2f}{psat(t):11.0f}")
for label, d, lam, mu in layers:
if d > 0:
t -= q * d / lam
sd += mu * d
else:
sd += mu # foil: mu column holds its sd
pts.append((sd, psat(t)))
labels.append("after " + label)
print(f" {'after ' + label:<30}{t:7.1f}{sd:8.2f}{psat(t):11.0f}")
pts.append((sd, pe))
labels.append("outside air")
hull = lower_hull(pts)
planes = hull[1:-1]
if not planes:
print(" No interstitial condensation: the vapour line stays below psat.")
return
for k in range(1, len(hull) - 1):
(x0, y0), (x1, y1), (x2, y2) = hull[k - 1], hull[k], hull[k + 1]
g = DELTA0 * ((y0 - y1) / (x1 - x0) - (y1 - y2) / (x2 - x1)) # kg/(m2 s)
where = labels[pts.index(hull[k])]
mc = g * HOURS * 3600
verdict = "OK" if mc <= 0.5 else ("capillary layers only" if mc <= 1.0 else "TOO MUCH")
print(f" Condensation at '{where}': {g * 3600 * 1000:.2f} g/m2 per hour,"
f" {mc:.2f} kg/m2 over 90 days -> {verdict}")
print(f"Check: inside vapour pressure {PHI_I * psat(TI):.0f} Pa")
print(f"Check: outside vapour pressure {PHI_E * psat(TE):.0f} Pa")
plaster = ("gypsum plaster 15 mm", 0.015, 0.70, 10)
concrete = ("concrete 180 mm", 0.180, 2.30, 80)
wool = ("mineral wool 160 mm", 0.160, 0.035, 1)
render = ("render 10 mm", 0.010, 0.87, 15)
board = ("plasterboard 12.5 mm", 0.0125, 0.25, 8)
foil = ("vapour retarder sd 20 m", 0, 0, 20.0)
check("A insulation outside (ETICS)", [plaster, concrete, wool, render])
check("B same insulation, inside", [board, wool, concrete, render])
check("C B plus vapour retarder", [board, foil, wool, concrete, render])Lines 1–9: the boundary conditions. These are Baunetzwissen’s winter conditions for the normative method: 90 days from December to February, −5 °C and 80 % outside, 20 °C and 50 % inside. Rsi = 0.13 and Rse = 0.04 m²K/W cover horizontal heat flow through a vertical wall. DELTA0 is a common value for still air. Capitals at the top tell the next reader to change these on purpose.
Lines 11–15: the curve the water must stay under. psat(t) gives the saturation vapour pressure, which is the most vapour air can hold at temperature t before it turns liquid. It uses the usual Magnus-type fit, with one branch over water and one over ice. A function is a named recipe: a temperature goes in, a pressure comes out, and the formula is written only once.
Lines 17–28: the string pulled tight. The core of the Glaser method is pure geometry. Plot each layer face as a point (sd, psat), with the room’s vapour pressure at the left end and the outdoor pressure at the right end. Then pull a string tight underneath all the points. That string is the lower convex hull, the lowest path that never bends upward. The if line is a cross product that asks whether the last point turns left or right, and any point that would make the string sag is popped. If the hull touches only its two ends, the wall is dry. Every corner in between is a condensation plane, which is Baunetzwissen’s “one plane, two planes or a zone”.
Lines 30–42: U-value and the first face. Rtotal = Rsi + Σ d/λ + Rse, and U = 1/Rtotal. Multiply U by 25 K and you get the heat flux q, about 5.2 W/m². That flux is the same through every layer, so each layer drops q × d/λ. The if d > 0 filter stops a zero-thickness foil from dividing by zero.
Lines 43–58: walk the wall. The loop runs from inside to outside. For each layer it subtracts the temperature drop, adds μ × d to sd, records the point, and prints a row. A foil enters with thickness 0 and its sd in the μ column. The beginner mistake is listing the layers from outside to inside. The program still runs, but it quietly flips your insulation to the other side. Check that the first row after “inside surface” is the material the plasterer touches.
Lines 59–66: how much water. At each hull corner, vapour arrives down the left slope and leaves down the right slope. The difference, times δ0, is the condensation rate. You can check run B by hand. (1168 − 425)/0.26 = 2,858 Pa/m flows in and (425 − 321)/14.55 = 7 Pa/m flows out. The difference times 2.0e-10 is 5.7e-7 kg/m²s, which is the printed 2.05 g/m² per hour, or 4.44 kg/m² over 90 days. The verdict uses Baunetzwissen’s limits: 1.0 kg/m² in general and 0.5 kg/m² where layers cannot take up water by capillarity. The same page recommends 0.25 kg/m² for timber.
=== A insulation outside (ETICS) ===
U = 0.206 W/m2K heat flux = 5.2 W/m2
No interstitial condensation: the vapour line stays below psat.
=== B same insulation, inside ===
U = 0.205 W/m2K heat flux = 5.1 W/m2
after mineral wool 160 mm -4.3 0.26 425
Condensation at 'after mineral wool 160 mm': 2.05 g/m2 per hour, 4.44 kg/m2 over 90 days -> TOO MUCH
=== C B plus vapour retarder ===
U = 0.205 W/m2K heat flux = 5.1 W/m2
Condensation at 'after mineral wool 160 mm': 0.02 g/m2 per hour, 0.05 kg/m2 over 90 days -> OKIn run B, the wool’s cold face sits at −4.3 °C, where saturation is only 425 Pa. Only 0.26 m of sd separates it from a 1,168 Pa room, while the concrete’s 14.4 m of sd holds the vapour in. Concrete is patient. Mould is more patient. In run C, the retarder adds 20 m of sd, and the inflow collapses to about 37 Pa/m.
The limits come from people who know the method well. Baunetzwissen notes that it uses stationary conditions and “does not reflect reality”. H. Künzel (DBMC 2020) records that “calculations according to Glaser are the sole means for assessing moisture control design” in many countries. He adds that hygrothermal simulation lets designers “use more innovative renewable and recycled materials that could not adequately be judged by the Glaser method”. Tom Carruthers, Technical Manager at Mike Wye, notes that ISO 13788 “does not account for material properties that vary with moisture, capillary suction or liquid transport”. The trade-off is plain. This script is fast and transparent, but it cannot see the capillary-active, bio-based materials that interior retrofits increasingly use. Treat it as a design check, never a compliance proof. When lime, clay or wood fibre are in the build-up, hand it to a hygrothermal simulation.
Atelier: A small Büro detailing interior insulation behind an old-town facade usually makes the call on a section drawing and a product sheet. An office starting to use AI-generated details adds one more risk: a plausible build-up with the layers in the wrong order. A script that prints the moisture result catches that before the detail reaches the tender. Monday move: put wall_check.py in the office template folder with your three standard build-ups, and make “run it” a line on the retrofit detail checklist.
Hack: Find out how much retarder this wall really needs, and predict the answer first. The inflow to the wool’s cold face is (1168 − 425)/(sd + 0.26), and the outflow stays near 7 Pa/m. With sd = 5 m, about 134 Pa/m remains, which is roughly 0.2 kg/m² over the winter. That is still OK, but no longer 0.05. Add these lines at the bottom and check your prediction:
print((1168 - 425) / 5.26 - (425 - 321) / 14.55) # predicted Pa/m left in the plane
foil5 = ("vapour retarder sd 5 m", 0, 0, 5.0)
check("D B with a lighter retarder", [board, foil5, wool, concrete, render])Then try sd = 2 m and watch the result reach the 0.5 kg/m² line.
From where I sit, decades on, the walls that survived were the repairable ones. The same was true of the files that described them. A .py file is plain text: when every vendor is gone, a 25-year-old can still open it and see why the foil is there. The next steps are the summer evaporation period (1,200 Pa on both sides and 1,700 Pa saturation at the plane, per Baunetzwissen) and slicing thick layers into sub-layers to find zones. After that comes a Grasshopper Python component that reads the layers from Rhino layer names, which is the kind of bridge PAZ Academy teaches in its Rhino and Grasshopper courses.
Learn-it:
- Baunetzwissen: Tauwasser und Glaser-Verfahren
- Baunetzwissen: Normativer Rechenweg, Randbedingungen
- H. Künzel, Preventing Damage by Updating Moisture Control Standards (DBMC 2020)
- Tom Carruthers, Mike Wye: Glaser or WUFI?
- Real Python: Defining Your Own Python Function, if
defandreturnare new to you.
Run all three walls, then put your office’s real build-up in as run D before the next retrofit detail leaves the office.
PAZ Kaffi · multidisciplinary editorial, led by PAZ Academy