diff --git a/README.md b/README.md index 01b8bd5..8df7dd6 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ The 'packet', in the terminology of this library, is a map which has bindings for some of the following keywords: 1. `:absolute-humidity` *grammes per metre3.* -2. `:actual-vapour-pressure` *not yet implemented* -1. `:dew-point` *not yet implemented* +2. `:actual-vapour-pressure` *Pascals* +1. `:dew-point-celsius` 2. `:pressure-pascals` 3. `:pressure-millibars` 4. `:saturation-vapour-pressure` *Pascals* diff --git a/src/humidity/actual_vp.clj b/src/humidity/actual_vp.clj index 0c2ff87..e04fc27 100644 --- a/src/humidity/actual_vp.clj +++ b/src/humidity/actual_vp.clj @@ -1,7 +1,8 @@ (ns ^{:author "simon" - :authority "https://www.weather.gov/media/epz/wxcalc/vaporPressure.pdf" - :doc "Compute actual vapour pressure."} humidity.actual-vp) - + :authority "https://www.weather.gov/media/epz/wxcalc/vaporPressure.pdf" + :doc "Compute actual vapour pressure."} + humidity.actual-vp + (:require [humidity.utils :refer [expt]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; @@ -23,3 +24,21 @@ ;;; USA. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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." + ;; 1000 because are units are Pascals + [0 6.11 1000 7.5 237.3]) + +(def C magic-numbers) + +(defn actual-vapour-pressure-fn + "Compute actual vapour pressure in Pascals given dew point in ° Celsius." + [& {:keys [dew-point-celsius]}] + (when dew-point-celsius + (* (C 1) (expt (C 2) + (/ (* (C 3) dew-point-celsius) + (+ (C 4) dew-point-celsius)))))) diff --git a/src/humidity/constants.clj b/src/humidity/constants.clj index 2173cc6..eaaa48d 100644 --- a/src/humidity/constants.clj +++ b/src/humidity/constants.clj @@ -1,4 +1,5 @@ (ns ^{:doc "Constants used in humidity calculations." + :authority ["https://journals.ametsoc.org/view/journals/apme/35/4/1520-0450_1996_035_0601_imfaos_2_0_co_2.xml"] :author "simon"} humidity.constants) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -30,6 +31,10 @@ "Base of natural logarithms" 2.71828) +(def ^:const magnus-coefficients + "See https://www.omnicalculator.com/physics/dew-point#how-to-calculate-dew-point-how-to-calculate-relative-humidity" + {:a 17.625 :b 243.04}) + (def ^:const Pa "Standard atmospheric pressure, in Pascals. I don't know why this isn't 100,000, but it isn't. diff --git a/src/humidity/core.clj b/src/humidity/core.clj index aaaa6d3..e9d94a0 100644 --- a/src/humidity/core.clj +++ b/src/humidity/core.clj @@ -2,7 +2,9 @@ :author "simon"} humidity.core (:require [humidity.absolute :refer [abs-humidity]] + [humidity.actual-vp :refer [actual-vapour-pressure-fn]] [humidity.constants :refer [celsius-offset Pa]] + [humidity.dew-point :refer [dew-point-celsius-fn]] [humidity.relative :refer [rel-humidity]] [humidity.saturation-vp :refer [saturation-vp]] [humidity.utils :refer [?assoc]] @@ -32,7 +34,8 @@ (defn resolve-humidity "Resolve humidity equations given the data in this packet." - [& {:keys [absolute-humidity pressure-pascals pressure-millibars + [& {:keys [absolute-humidity actual-vapour-pressure dew-point-celsius + pressure-pascals pressure-millibars relative-humidity saturation-vapour-pressure temperature temperature-celsius temperature-kelvin volume wet-bulb-temperature] :as packet}] @@ -43,13 +46,17 @@ (?assoc packet :absolute-humidity (when-not absolute-humidity (abs-humidity packet)) + :actual-vapour-pressure (when-not actual-vapour-pressure + (actual-vapour-pressure-fn packet)) + :dew-point-celsius (when-not dew-point-celsius + (dew-point-celsius-fn packet)) :pressure-pascals (when-not pressure-pascals (if pressure-millibars (* pressure-millibars 100) Pa)) :pressure-millibars (when-not pressure-millibars (if pressure-pascals - (float (/ pressure-pascals 100)) - (float (/ Pa 100)))) + (float (/ pressure-pascals 100)) + (float (/ Pa 100)))) :relative-humidity (when-not relative-humidity (rel-humidity packet)) :saturation-vapour-pressure (when-not saturation-vapour-pressure diff --git a/src/humidity/dew_point.clj b/src/humidity/dew_point.clj new file mode 100644 index 0000000..ac97e67 --- /dev/null +++ b/src/humidity/dew_point.clj @@ -0,0 +1,34 @@ +(ns ^{:author "simon" + :authority ["https://www.omnicalculator.com/physics/dew-point#what-is-dew-point-dew-point-definition" + "https://en.wikipedia.org/wiki/Dew_point#Calculating_the_dew_point"] + :doc "Function to compute the dew point, in ° Celsius, given temperature + in ° Celsius and relative humidity percentage."} humidity.dew-point +(:require [clojure.math :refer [log]] + [humidity.constants :refer [magnus-coefficients]])) + +(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 6.11 10 7.5 237.3]) + +(def C magic-numbers) + +(defn- alpha + "See https://www.omnicalculator.com/physics/dew-point#how-to-calculate-dew-point-how-to-calculate-relative-humidity" + [T RH] + (let [a (:a magnus-coefficients) + b (:b magnus-coefficients)] + (+ (log (/ RH 100)) (/ (* a T) (+ b T))))) + +;; Ts = (b × α(T,RH)) / (a - α(T,RH)) +(defn dew-point-celsius-fn + "Compute and return the dew point, in ° Celsius, given temperature + in ° Celsius and relative humidity percentage; or `nil` if this data is + not available." + [&{:keys [temperature-celsius relative-humidity]}] + (when (and temperature-celsius relative-humidity) + (let [a' (alpha temperature-celsius relative-humidity)] + (/ (* (:b magnus-coefficients) a') + (- (:a magnus-coefficients) a'))))) diff --git a/src/humidity/volume.clj b/src/humidity/volume.clj index 185365b..bfc827b 100644 --- a/src/humidity/volume.clj +++ b/src/humidity/volume.clj @@ -2,7 +2,7 @@ :authority "https://en.wikipedia.org/wiki/Gas_laws" :doc "In computing the relative humidity of air after it has warmed, I need to account for the change in volume. By Charles' Law, given stable - pressure, volume is proportinal to absolute temperature (i.e. V=Tk, where K + pressure, volume is proportional to absolute temperature (i.e. V=Tk, where K is a constant). However, the sources I have read do not give that constant, for air. But seeing what I'm interested in is that amount of air which is contained in a cubic metre at standard temperature and pressure, then *I @@ -14,7 +14,7 @@ (:require [humidity.constants :refer [celsius-offset Pa]])) (defn volume-fn - "Compute the volume, in m3 of a quantity of air which would be + "Compute the volume, in m3, of a quantity of air which would be 1m3 at standard temperature and pressure, given temperature in ° Kelvin and pressure in Pascals." [& {:keys [temperature-kelvin pressure-pascals]}] diff --git a/test/humidity/core_test.clj b/test/humidity/core_test.clj index e029375..34e24b9 100644 --- a/test/humidity/core_test.clj +++ b/test/humidity/core_test.clj @@ -26,14 +26,16 @@ (deftest resolution-test (testing "resolution") - (let [expected {:relative-humidity 100, - :temperature-celsius 35, + (let [expected {:absolute-humidity 3953.618101887827, :pressure-pascals 101325, + :temperature-celsius 35, + :dew-point-celsius 35.00000000000001, :pressure-millibars 1013.25, - :saturation-vapour-pressure 5622.488734516426, + :volume 1.1281347245103424, :temperature-kelvin 308.15, - :absolute-humidity 3953.618101887827, - :volume 1.1281347245103424} + :relative-humidity 100, + :saturation-vapour-pressure 5622.488734516426, + :actual-vapour-pressure 4765.10094206357} packet {:relative-humidity 100 :temperature-celsius 35} actual (resolution packet)] (is (= actual expected)))) diff --git a/test/humidity/dew_point_test.clj b/test/humidity/dew_point_test.clj index 4bfbe4e..84e9e9c 100644 --- a/test/humidity/dew_point_test.clj +++ b/test/humidity/dew_point_test.clj @@ -1 +1,36 @@ -(ns humidity.dew-point-test) +(ns humidity.dew-point-test + (:require [clojure.test :refer [deftest is testing]] + [humidity.constants :refer [celsius-offset]] + [humidity.dew-point :refer [dew-point-celsius-fn]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2026 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;; USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(deftest dew-point-test + (testing "definitional" + (let [min-expected 34.9 + max-expected 35.1 + actual (dew-point-celsius-fn :relative-humidity 100 + :temperature-celsius 35)] + (is (< min-expected actual max-expected)))) + (testing "missing data" + (let [expected nil + actual (dew-point-celsius-fn :relative-humidity 100)] + (is (= expected actual)))))