Most HVAC and building simulation software still relies on archaic Fortran or compiled C dynamic libraries to evaluate moist air thermodynamic properties. If you inspect typical web calculators, you will usually find crude quadratic curve fits or linear approximations that deviate by more than 8% at non-standard barometric pressures or elevated humidity ratios.
When building HVACLogic, our goal was to run all thermodynamic and building science calculations 100% client-side in the browser with zero external dependencies, zero backend roundtrips, and zero database tracking.
To calculate thermodynamic air properties with laboratory-grade precision, we implemented the governing ASHRAE Fundamentals (Chapter 1) Hyland-Wexler formulations in pure TypeScript.
Here is an architectural walkthrough of how these thermodynamic equations work and how to solve moist air state points deterministically in modern JavaScript engines.
1. The Physics of Moist Air: Dalton's Law & Hyland-Wexler
Atmospheric air is a binary mixture of dry air and water vapor. Under Dalton's Law of Partial Pressures, total barometric pressure P_atm is the sum of partial pressure exerted by dry air P_da and partial pressure of water vapor P_w:
P_atm = P_da + P_w
At sea level, standard atmospheric pressure is 14.696 psia (101.325 kPa). When evaluating high-altitude structures (such as Denver at 5,280 ft), barometric pressure drops to 12.15 psia. Calculating moist air without adjusting for altitude produces severe sizing errors.
The barometric pressure as a function of elevation h (in feet) is governed by the ASHRAE Standard Atmosphere:
export function getBarometricPressurePsia(altitudeFeet: number = 0): number {
const h = Math.max(-1000, Math.min(15000, altitudeFeet));
return 14.696 * Math.pow(1 - 6.8754e-6 * h, 5.2559);
}
2. Saturation Vapor Pressure: The Hyland-Wexler Formulation
The core thermodynamic anchor of psychrometrics is the saturation vapor pressure P_ws, which defines the maximum partial pressure water vapor can exert at a given temperature before condensing into liquid or sublimating into frost.
ASHRAE endorses the Hyland-Wexler equations. These polynomials evaluate saturation pressure over absolute temperature T in degrees Rankine (°R = °F + 459.67).
Crucially, the physics of phase change require two distinct formulations depending on whether the temperature is above or below the triple point of water (32°F / 0°C):
Over Liquid Water (32°F <= T <= 392°F):
ln(P_ws) = C8/T + C9 + C10*T + C11*T^2 + C12*T^3 + C13*ln(T)
Over Ice (-148°F <= T < 32°F):
ln(P_ws) = C1/T + C2 + C3*T + C4*T^2 + C5*T^3 + C6*T^4 + C7*ln(T)
Here is the deterministic TypeScript implementation:
export function getSaturationVaporPressurePsia(tempF: number): number {
const tRankine = tempF + 459.67;
if (tempF >= 32) {
// Coefficients over liquid water (ASHRAE Fundamentals)
const c8 = -1.0440397e4;
const c9 = -1.129465e1;
const c10 = -2.7022355e-2;
const c11 = 1.289036e-5;
const c12 = -2.4780681e-9;
const c13 = 6.5459673;
const lnPws =
c8 / tRankine +
c9 +
c10 * tRankine +
c11 * Math.pow(tRankine, 2) +
c12 * Math.pow(tRankine, 3) +
c13 * Math.log(tRankine);
return Math.exp(lnPws);
} else {
// Coefficients over ice
const c1 = -1.0214165e4;
const c2 = -4.8932428;
const c3 = -5.3765794e-3;
const c4 = 1.9202377e-7;
const c5 = 3.5575832e-10;
const c6 = -9.0344688e-14;
const c7 = 4.1635019;
const lnPws =
c1 / tRankine +
c2 +
c3 * tRankine +
c4 * Math.pow(tRankine, 2) +
c5 * Math.pow(tRankine, 3) +
c6 * Math.pow(tRankine, 4) +
c7 * Math.log(tRankine);
return Math.exp(lnPws);
}
}
3. Humidity Ratio and Grains of Moisture
Once we know the actual partial vapor pressure P_w, we can calculate the humidity ratio W, defined as the mass of water vapor per unit mass of dry air:
W = 0.621945 * [ P_w / (P_atm - P_w) ]
The constant 0.621945 represents the ratio of the molecular weight of water (18.01528 g/mol) to the molecular weight of dry air (28.966 g/mol).
In residential building science and dehumidification equipment sizing, humidity ratio in pounds of water per pound of dry air is an unwieldy decimal (e.g. 0.00928 lb/lb). The trade uses grains of moisture per pound of dry air, where 1 pound contains exactly 7,000 grains:
const humidityRatioLbPerLb = (0.621945 * pw) / (patm - pw);
const humidityRatioGrainsPerLb = humidityRatioLbPerLb * 7000;
For instance, at standard indoor design conditions (75°F dry bulb, 50% relative humidity), air contains approximately 65 grains per pound (gr/lb). If indoor air exceeds 70 gr/lb, mold spore germination acceleration occurs regardless of dry-bulb thermostat settings.
4. Dew Point Inversion via ASHRAE Logarithmic Polynomials
The dew point temperature T_dp is the temperature at which moist air must be cooled at constant barometric pressure and moisture content to reach 100% saturation.
Rather than running an iterative search on getSaturationVaporPressurePsia, ASHRAE provides an explicit inverse logarithmic polynomial using alpha = ln(P_w):
export function getDewPointTempF(vaporPressurePsia: number): number {
const p = Math.max(0.0001, vaporPressurePsia);
const alpha = Math.log(p);
if (p >= 0.08865) {
// Tdp >= 32°F
return (
100.45 +
33.193 * alpha +
2.319 * Math.pow(alpha, 2) +
0.17074 * Math.pow(alpha, 3) +
1.2063 * Math.pow(p, 0.1984)
);
} else {
// Tdp < 32°F
return 90.12 + 26.142 * alpha + 0.8927 * Math.pow(alpha, 2);
}
}
5. Solving Wet Bulb Temperature via Numerical Energy Balance
Unlike dew point, wet bulb temperature T_wb does not possess a direct, closed-form algebraic inverse across broad atmospheric ranges.
Wet bulb temperature reflects dynamic thermodynamic equilibrium reached when evaporative cooling of a wetted wick balances convective heat transfer from passing air.
The governing ASHRAE energy balance equation is:
W = [ (1093 - 0.556 * T_wb) * W_s_wb - 0.24 * (T_db - T_wb) ] /
[ 1093 + 0.444 * T_db - T_wb ]
Where:
-
T_dbis the dry bulb temperature in °F. -
T_wbis the wet bulb temperature to solve for. -
W_s_wbis the saturation humidity ratio evaluated strictly atT_wb. -
Wis the known humidity ratio of the state point.
Because T_wb is strictly bounded between -20°F and T_db, we can use a bisection solver (or 1D Newton solver) that converges to within 0.01°F in under 25 iterations:
export function getWetBulbTempF(
dryBulbF: number,
humidityRatio: number,
patm: number
): number {
let low = -20;
let high = dryBulbF;
let wetBulb = dryBulbF;
for (let i = 0; i < 30; i++) {
wetBulb = (low + high) / 2;
const pwsWb = getSaturationVaporPressurePsia(wetBulb);
const wsWb = 0.621945 * (pwsWb / (patm - pwsWb));
// ASHRAE psychrometric wet bulb energy balance
const calcW =
((1093 - 0.556 * wetBulb) * wsWb - 0.24 * (dryBulbF - wetBulb)) /
(1093 + 0.444 * dryBulbF - wetBulb);
if (calcW < humidityRatio) {
low = wetBulb;
} else {
high = wetBulb;
}
}
return Math.round(wetBulb * 10) / 10;
}
6. Evaluating Specific Enthalpy, Volume, and Density
With the state point fully resolved, we evaluate the thermodynamic energy content and volumetric properties:
Specific Enthalpy (h, in BTU/lb dry air)
Total enthalpy represents sensible heat of dry air plus latent heat of water vapor:
const specificEnthalpyBtuPerLb =
0.24 * tdb + humidityRatioLbPerLb * (1061 + 0.444 * tdb);
Specific Volume (v, in cu ft/lb dry air)
Accounting for molecular expansion:
const tRankine = tdb + 459.67;
const specificVolumeCuFtPerLb =
(53.352 * tRankine * (1 + 1.607858 * humidityRatioLbPerLb)) / (patm * 144);
Air Density (rho, in lb/cu ft)
const airDensityLbPerCuFt = (1 + humidityRatioLbPerLb) / specificVolumeCuFtPerLb;
7. Interactive State Verification: The State Point Matrix
Here is how our TypeScript engine solves standard atmospheric air across common operational states:
| State Condition | Dry Bulb (°F) |
Relative Humidity | Wet Bulb (°F) |
Dew Point (°F) |
Humidity Ratio (gr/lb) |
Enthalpy (BTU/lb) |
|---|---|---|---|---|---|---|
| Standard Indoor (Comfort) | 75.0°F | 50.0% | 62.5°F | 55.1°F | 64.9 gr/lb | 28.14 BTU/lb |
| Cooling Coil Discharge | 55.0°F | 90.0% | 53.6°F | 52.2°F | 58.1 gr/lb | 22.42 BTU/lb |
| Summer Peak Design | 95.0°F | 40.0% | 75.2°F | 67.2°F | 98.4 gr/lb | 38.61 BTU/lb |
| Dry High-Altitude (Denver) | 85.0°F | 20.0% | 58.4°F | 38.8°F | 40.6 gr/lb | 26.83 BTU/lb |
You can test these state transitions interactively in our open-access Psychrometric State Point Calculator, which runs these exact TypeScript functions with live reactive SVG state plotting.
For a deeper dive into how moist air enthalpy governs sensible-to-latent load splitting and building envelope vapor barriers, read our complete Building Science & Psychrometrics Engineering Guide.
Conclusion: Zero-DB Deterministic Engineering
Building thermodynamic simulation software in TypeScript does not require sacrificing mathematical rigor for performance.
By implementing exact ASHRAE Hyland-Wexler formulations in pure functions:
- Zero Latency: Calculations execute in microseconds directly in the browser.
- Offline Resilience: Field technicians on job sites can diagnose evaporator coils and moisture problems without cell coverage.
- 100% Privacy: Client psychrometric submittals and facility environmental parameters never leave the user's browser.
If you are developing building science tools, HVAC modeling engines, or smart thermostat algorithms, treating moist air as a non-ideal mixture governed by rigorous thermodynamic equations eliminates hidden 5% to 10% errors in sensible and latent load calculations.
Top comments (0)