More progress... but more things I need to calculate that I don't yet know how.

This commit is contained in:
Simon Brooke 2026-08-11 13:59:22 +01:00
parent ccfc47eb79
commit d679bcf781
11 changed files with 137 additions and 26 deletions

View file

@ -2,4 +2,38 @@
Functions to compute relative humidity, absolute humidity, wet bulb
temperature, and other values related to humidity of air, given
partial data
partial data.
## General architecture
### The packet
The 'packet', in the terminology of this library, is a map which has bindings
for some of the following keywords:
1. `:actual-humidity`
2. `:actual-vapour-pressure
1. `:dew-point`
2. `:pressure-pascals`
3. `:pressure-millibars`
4. `:saturation-pressure`
5. `:relative-humidity`
6. `:temperature`
7. `:temperature-celsius`
8. `:temperature-kelvin`
These keywords represent variables in the pressure equations, and they are all
to a degree inter-related; so if you have (or can assume) some of them, you can
compute others.
Rather than having a mess of functions all of which call one another, which
seemed likely to result in horrible messes and potential infinite recursion,
there is one central function, `humidity.core/resolution`, which takes a
partial package and repeatedly tries to compute the values for more variables
until it can compute no more, and then returns the enhanced packet.
At present there are no checks to ensure that the packet makes sense.
Associated with each variable there is a namespace which takes a packet, and,
if that packet contains enough information to compute that variable, returns
a numeric value, else `nil`.

View file

@ -33,6 +33,6 @@
(defn abs-humidity [& {:keys [relative-humidity saturation-pressure temperature-kelvin]}]
(when (and relative-humidity saturation-pressure temperature-kelvin)
(/ (* relative-humidity saturation-pressure) (* Rw temperature-kelvin 100)))
;; I don't know why I'm having to multiply by a thousand here...
(* (/ (* relative-humidity saturation-pressure) Rw temperature-kelvin) 1000))
)

View file

@ -9,9 +9,15 @@
"Base of natural logarithms"
2.71828)
(def ^:const Pa
"Standard atmospheric pressure, in Pascals. I don't know why this isn't
100,000, but it isn't.
See https://en.wikipedia.org/wiki/Standard_atmosphere_(unit)"
101325)
(def ^:const Pc
"Critical pressure for water, in MPa (Megapascal)"
22.064)
"Critical pressure for water, in Pascals"
22064)
(def ^:const Rw
"Specific gas constant for water vapour (J/(kg⋅K))"

View file

@ -1,6 +1,6 @@
(ns humidity.core
(:require [humidity.absolute :refer [abs-humidity]]
[humidity.constants :refer [celsius-offset]]
[humidity.constants :refer [celsius-offset Pa]]
[humidity.saturation-vp :as saturation-vp]
[humidity.utils :refer [?assoc]]
[humidity.wet-bulb :refer [wet-bulb-temperature]]))
@ -8,7 +8,7 @@
(defn resolve-humidity
"Resolve humidity equations given the data in this packet."
[& {:keys [temperature temperature-celsius temperature-kelvin relative-humidity
pressure-kilopascals pressure-millibars saturation-pressure] :as packet}]
pressure-pascals pressure-millibars saturation-vapour-pressure] :as packet}]
(println packet)
(if temperature (resolve-humidity (assoc (dissoc packet :temperature) :temperature-celsius temperature))
(?assoc packet
@ -22,13 +22,13 @@
(and temperature-celsius relative-humidity)
(wet-bulb-temperature temperature-celsius relative-humidity))
:absolute-humidity (abs-humidity :relative-humidity relative-humidity
:saturation-pressure saturation-pressure
:saturation-vapour-pressure saturation-vapour-pressure
:temperature-kelvin temperature-kelvin)
:pressure-kilopascals (when-not pressure-kilopascals
(if pressure-millibars (/ pressure-millibars 10) 100))
:pressure-pascals (when-not pressure-pascals
(if pressure-millibars (* pressure-millibars 100) Pa))
:pressure-millibars (when-not pressure-millibars
(if pressure-kilopascals (* pressure-kilopascals 10) 1000))
:saturation-pressure (saturation-vp/saturation-vp packet))))
(if pressure-pascals (/ pressure-pascals 100) (/ Pa 100)))
:saturation-vapour-pressure (saturation-vp/saturation-vp packet))))
(defn resolution

View file

@ -1 +1,34 @@
(ns humidity.relative)
(ns ^{:authority "https://journals.ametsoc.org/view/journals/apme/35/4/1520-0450_1996_035_0601_imfaos_2_0_co_2.xml/"
:author "simon"
:doc "Function to return relative humidity."} humidity.relative
(:require [humidity.constants :refer [celsius-offset e]]
[humidity.utils :refer [expt]]))
(def ^:const magic-numbers
"The magic numbers (empirical constants) from the derived expression. It
bothers me that all these formulae depend on 'empirical constants', *and*
that there is no commonality between the empirical constants of different
formulae."
[0 17.625 243.04])
(def C magic-numbers)
(defn rel-humidity
"Returns relative humidity (as a percentage, 0...100) given values for dew
point and temperature in either Celsius or Kelvin."
[& {:keys [dew-point temperature-celsius temperature-kelvin]}]
(cond (and dew-point (or temperature-celsius temperature-kelvin))
(let [Tc (if temperature-celsius
temperature-celsius
(- temperature-kelvin celsius-offset))]
(* 100 (/ (expt e (/ (* (C 1) dew-point)
(+ (C 2) dew-point)))
(expt e (/ (* (C 1) Tc)
(+ (C 2) Tc))))))))
;; Absolute Humidity (grams/m3) = 6.112 × e^[(17.67 × T)/(T+243.5)] × rh × 2.1674
;; -----------------------------------------------
;; (273.15+T)
;;
;; => /
;;

View file

@ -5,14 +5,16 @@
(:require [clojure.math :refer [pow]]
[humidity.constants :refer [celsius-offset e]]))
(defn saturation-vp-tetens
"Calculate and return saturation vapour pressure in hPa using Tetens'
"Calculate and return saturation vapour pressure in Pa using Tetens'
formula, given temerature in ° Celsius."
[temperature-celsius]
(let [a 6.1078 ;; yet more magic numbers...
(let [a 610.78 ;; yet more magic numbers...
;; NOTE: 610.78 rather than 0.61078 in source because
;; we want answer in Pascals.
b 17.27
c 237.3]
;; constant 100 to convert to Pascals
(* a (pow e (/ (* b temperature-celsius) (+ c temperature-celsius))))))
(defn saturation-vp

View file

@ -1,5 +1,6 @@
(ns ^{:doc "Function to calculate wet bulb temperature (in ° C) given temperature in
° C and relative-humidity percentage (0...100)"}
° C and relative-humidity percentage (0...100)"
:author "simon"}
humidity.wet-bulb
(:require [clojure.math :refer [atan]]
[humidity.utils :refer [expt]]))

View file

@ -0,0 +1,13 @@
(ns humidity.absolute-test
(:require [clojure.test :refer [deftest is testing]]
[humidity.constants :refer [celsius-offset]]
[humidity.absolute :refer [abs-humidity]]))
(deftest absolute-humidity-test
(testing "definitional"
(let [min-expected 39.58
max-expected 39.59
actual (abs-humidity :relative-humidity 100
:saturation-pressure 56.29
:temperature-kelvin (+ 35 celsius-offset))]
(is (< min-expected actual max-expected)))))

View file

@ -0,0 +1 @@
(ns humidity.dew-point-test)

View file

@ -0,0 +1,11 @@
(ns humidity.relative-test
(:require [clojure.test :refer [deftest is testing]]
[humidity.constants :refer [celsius-offset]]
[humidity.relative :refer [rel-humidity]]))
(deftest relative-humidity-test
(testing "Online examples"
(let [min-expected 99
max-expected 101
actual (rel-humidity :dew-point 35 :temperature-celsius 35)]
(is (< min-expected actual max-expected)))))

View file

@ -0,0 +1,10 @@
(ns humidity.saturation-vp-test(:require [clojure.test :refer [deftest is testing]]
[humidity.constants :refer [celsius-offset]]
[humidity.saturation-vp :refer [saturation-vp saturation-vp-tetens]]))
(deftest saturation-vp-test
(testing "from online sources"
(let [min-expected 5622
max-expected 5629
actual (saturation-vp {:temperature-celsius 35})]
(is (< min-expected actual max-expected)))))