diff --git a/README.md b/README.md index 16ee733..e49136e 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/src/humidity/absolute.clj b/src/humidity/absolute.clj index 4bd1367..38a93a8 100644 --- a/src/humidity/absolute.clj +++ b/src/humidity/absolute.clj @@ -1,8 +1,8 @@ -(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). - + See [authority](https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/)"} - humidity.absolute + humidity.absolute (:require [humidity.constants :refer [Rw]])) @@ -11,15 +11,15 @@ ;; (273.15+T) (def ^:const magic-numbers - "The magic number constants from the derived expression. It would give me - more comfort if there were some agreement between these magic numbers and + "The magic number constants from the derived expression. It would give me + more comfort if there were some agreement between these magic numbers and those used in the wet bulb function, but there is not." [0 6.112 17.67 243.5 2.1674 273.15]) (def C magic-numbers) ;; (defn abs-humidity -;; "Returns absolute humidity in grammes per cubic metre given this +;; "Returns absolute humidity in grammes per cubic metre given this ;; `temperature` in ° C and `relative-hunidity` percentage (0...100). ;; ![function for absolute humidity given temperature and relative humidity](https://carnotcycle.wordpress.com/wp-content/uploads/2014/09/ah3.gif)" ;; [temperature relative-humidity] @@ -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)) ) - diff --git a/src/humidity/constants.clj b/src/humidity/constants.clj index 94bcead..29ac6d1 100644 --- a/src/humidity/constants.clj +++ b/src/humidity/constants.clj @@ -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))" @@ -19,4 +25,4 @@ (def ^:const Tc "Critical temperature for water, ° Kelvin" - 647.096) \ No newline at end of file + 647.096) diff --git a/src/humidity/core.clj b/src/humidity/core.clj index 2f23add..16f5054 100644 --- a/src/humidity/core.clj +++ b/src/humidity/core.clj @@ -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 diff --git a/src/humidity/relative.clj b/src/humidity/relative.clj index b45c850..247f7e3 100644 --- a/src/humidity/relative.clj +++ b/src/humidity/relative.clj @@ -1 +1,34 @@ -(ns humidity.relative) \ No newline at end of file +(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) +;; +;; => / +;; diff --git a/src/humidity/saturation_vp.clj b/src/humidity/saturation_vp.clj index b7dd8d4..b48aa47 100644 --- a/src/humidity/saturation_vp.clj +++ b/src/humidity/saturation_vp.clj @@ -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 diff --git a/src/humidity/wet_bulb.clj b/src/humidity/wet_bulb.clj index 5443d0a..ffed146 100644 --- a/src/humidity/wet_bulb.clj +++ b/src/humidity/wet_bulb.clj @@ -1,5 +1,6 @@ -(ns ^{:doc "Function to calculate wet bulb temperature (in ° C) given temperature in - ° C and relative-humidity percentage (0...100)"} +(ns ^{:doc "Function to calculate wet bulb temperature (in ° C) given temperature in + ° C and relative-humidity percentage (0...100)" + :author "simon"} humidity.wet-bulb (:require [clojure.math :refer [atan]] [humidity.utils :refer [expt]])) @@ -14,7 +15,7 @@ (def C magic-numbers) (defn wet-bulb-temperature - "Returns wet bulb temperature (in ° C) given this `temperature` in + "Returns wet bulb temperature (in ° C) given this `temperature` in ° C and `relative-humidity` percentage (0...100)" [temperature relative-humidity] (let [T temperature diff --git a/test/humidity/absolute_test.clj b/test/humidity/absolute_test.clj new file mode 100644 index 0000000..b983e1f --- /dev/null +++ b/test/humidity/absolute_test.clj @@ -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))))) diff --git a/test/humidity/dew_point_test.clj b/test/humidity/dew_point_test.clj new file mode 100644 index 0000000..4bfbe4e --- /dev/null +++ b/test/humidity/dew_point_test.clj @@ -0,0 +1 @@ +(ns humidity.dew-point-test) diff --git a/test/humidity/relative_test.clj b/test/humidity/relative_test.clj new file mode 100644 index 0000000..3a9d52b --- /dev/null +++ b/test/humidity/relative_test.clj @@ -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))))) diff --git a/test/humidity/saturation_vp_test.clj b/test/humidity/saturation_vp_test.clj new file mode 100644 index 0000000..84db41b --- /dev/null +++ b/test/humidity/saturation_vp_test.clj @@ -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)))))