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 Functions to compute relative humidity, absolute humidity, wet bulb
temperature, and other values related to humidity of air, given 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]}] (defn abs-humidity [& {:keys [relative-humidity saturation-pressure temperature-kelvin]}]
(when (and 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" "Base of natural logarithms"
2.71828) 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 (def ^:const Pc
"Critical pressure for water, in MPa (Megapascal)" "Critical pressure for water, in Pascals"
22.064) 22064)
(def ^:const Rw (def ^:const Rw
"Specific gas constant for water vapour (J/(kg⋅K))" "Specific gas constant for water vapour (J/(kg⋅K))"

View file

@ -1,6 +1,6 @@
(ns humidity.core (ns humidity.core
(:require [humidity.absolute :refer [abs-humidity]] (:require [humidity.absolute :refer [abs-humidity]]
[humidity.constants :refer [celsius-offset]] [humidity.constants :refer [celsius-offset Pa]]
[humidity.saturation-vp :as saturation-vp] [humidity.saturation-vp :as saturation-vp]
[humidity.utils :refer [?assoc]] [humidity.utils :refer [?assoc]]
[humidity.wet-bulb :refer [wet-bulb-temperature]])) [humidity.wet-bulb :refer [wet-bulb-temperature]]))
@ -8,7 +8,7 @@
(defn resolve-humidity (defn resolve-humidity
"Resolve humidity equations given the data in this packet." "Resolve humidity equations given the data in this packet."
[& {:keys [temperature temperature-celsius temperature-kelvin relative-humidity [& {: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) (println packet)
(if temperature (resolve-humidity (assoc (dissoc packet :temperature) :temperature-celsius temperature)) (if temperature (resolve-humidity (assoc (dissoc packet :temperature) :temperature-celsius temperature))
(?assoc packet (?assoc packet
@ -22,13 +22,13 @@
(and temperature-celsius relative-humidity) (and temperature-celsius relative-humidity)
(wet-bulb-temperature temperature-celsius relative-humidity)) (wet-bulb-temperature temperature-celsius relative-humidity))
:absolute-humidity (abs-humidity :relative-humidity relative-humidity :absolute-humidity (abs-humidity :relative-humidity relative-humidity
:saturation-pressure saturation-pressure :saturation-vapour-pressure saturation-vapour-pressure
:temperature-kelvin temperature-kelvin) :temperature-kelvin temperature-kelvin)
:pressure-kilopascals (when-not pressure-kilopascals :pressure-pascals (when-not pressure-pascals
(if pressure-millibars (/ pressure-millibars 10) 100)) (if pressure-millibars (* pressure-millibars 100) Pa))
:pressure-millibars (when-not pressure-millibars :pressure-millibars (when-not pressure-millibars
(if pressure-kilopascals (* pressure-kilopascals 10) 1000)) (if pressure-pascals (/ pressure-pascals 100) (/ Pa 100)))
:saturation-pressure (saturation-vp/saturation-vp packet)))) :saturation-vapour-pressure (saturation-vp/saturation-vp packet))))
(defn resolution (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]] (:require [clojure.math :refer [pow]]
[humidity.constants :refer [celsius-offset e]])) [humidity.constants :refer [celsius-offset e]]))
(defn saturation-vp-tetens (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." formula, given temerature in ° Celsius."
[temperature-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 b 17.27
c 237.3] c 237.3]
;; constant 100 to convert to Pascals
(* a (pow e (/ (* b temperature-celsius) (+ c temperature-celsius)))))) (* a (pow e (/ (* b temperature-celsius) (+ c temperature-celsius))))))
(defn saturation-vp (defn saturation-vp

View file

@ -1,5 +1,6 @@
(ns ^{:doc "Function to calculate wet bulb temperature (in ° C) given temperature in (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 humidity.wet-bulb
(:require [clojure.math :refer [atan]] (:require [clojure.math :refer [atan]]
[humidity.utils :refer [expt]])) [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)))))