diff --git a/README.md b/README.md index 8df7dd6..ea3f53d 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,11 @@ I'm using this code to explore human survivability given global warming. The fol 2. [The 'everyone dies' event comes home](https://www.journeyman.cc/blog/posts-output/2026-07-15-Everyone-dies-UK/) 3. [Extreme Heat](https://www.journeyman.cc/blog/posts-output/2026-07-31-Extreme-Heat/) 4. [Analysis of 2026 heatwaves UK](https://www.journeyman.cc/blog/posts-output/2026-08-04-Analysis-of-2026-heatwaves-UK/) +5. [Exploration of what happened on 26th June this year](https://www.journeyman.cc/blog/posts-output/2026-08-13-Exploration-of-what-happened-on-26th-June-this-year/) -## Status +## Disclaimer -Alpha-quality code. It more or less works but it isn't at all polished. +This library is supplied in the hope that it may be useful, but results computed with it should not be taken as authoritative but should be checked against other sources. I do not have enough expertise to guarantee its results are correct. ## General architecture @@ -29,13 +30,13 @@ for some of the following keywords: 1. `:dew-point-celsius` 2. `:pressure-pascals` 3. `:pressure-millibars` -4. `:saturation-vapour-pressure` *Pascals* -5. `:relative-humidity` *percentage, 0...100.* +4. `:relative-humidity` *percentage, 0...100.* +5. `:saturation-vapour-pressure` *Pascals* 6. `:temperature` *assumed to be ° Celsius* 7. `:temperature-celsius` 8. `:temperature-kelvin` 9. `:volume` *in metres3, relative to 1 at standard temperature and pressure.* -10. `:wet-bulb-temperature` *in ° Celsius.* +10. `:wet-bulb-temperature-celsius` *in ° Celsius.* 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 @@ -52,9 +53,58 @@ humidity.core=> (resolution :relative-humidity 100 :temperature-celsius 35) {:relative-humidity 100, :temperature-celsius 35, :pressure-pascals 101325, :pressure-millibars 4053/4, :saturation-vapour-pressure 5622.488734516426, :temperature-kelvin 308.15, :absolute-humidity 3953.618101887827} ``` -At present there are no checks to ensure that the packet makes sense. +### Naming -Associated with each variable there is a namespace which contains a function that takes a packet, and, provided that packet contains enough information to compute that variable, returns a numeric value, else `nil`. +Associated with each variable there is a namespace of the same name which contains a function, whose name is the name of the variable with `-fn` appended, that takes a packet, and, provided that packet contains enough information to compute that variable, returns a numeric value, else `nil`. + +### Validation + +A packet may be validated by calling the function `validate`. This recalculates all values in the packet, and, by default, tolerates discrepancies of up to 1%. This tolerance can be varied by binding the dynamic variable `*validation-limit*`. Validation merely checks that the values within the packet are consistent with one another, and does not properly account for pressure, so will not be valid at altitude. + +```clojure +humidity.core=> (validate {:absolute-humidity 3953.618101887827, + #_=> :actual-vapour-pressure 4765.10094206357, + #_=> :dew-point-celsius 35.00000000000001, + #_=> :pressure-millibars 1013.25, + #_=> :pressure-pascals 101325, + #_=> :relative-humidity 100, + #_=> :saturation-vapour-pressure 5622.488734516426, + #_=> :temperature-celsius 35, + #_=> :temperature-kelvin 308.15, + #_=> :volume 1.1281347245103424, + #_=> :wet-bulb-temperature-celsius 35.11625928298915} + #_=> ) +true + +humidity.core=> (validate {:absolute-humidity 3953.618101887827, + #_=> :actual-vapour-pressure 4765.10094206357, + #_=> :dew-point-celsius 35.00000000000001, + #_=> :pressure-millibars 1013.25, + #_=> :pressure-pascals 101325, + #_=> :relative-humidity 95, ;; <-- intentionally wrong + #_=> :saturation-vapour-pressure 5622.488734516426, + #_=> :temperature-celsius 35, + #_=> :temperature-kelvin 308.15, + #_=> :volume 1.1281347245103424, + #_=> :wet-bulb-temperature-celsius 35.11625928298915} + #_=> ) +ERROR: key: :dew-point-celsius; a: 35.00000000000001; b 34.07737599732603; errror: 2.857142925262451%. +false + +humidity.core=> (binding [*validation-limit* 5] + #_=> (validate {:absolute-humidity 3953.618101887827, + #_=> :actual-vapour-pressure 4765.10094206357, + #_=> :dew-point-celsius 35.00000000000001, + #_=> :pressure-millibars 1013.25, + #_=> :pressure-pascals 101325, + #_=> :relative-humidity 95, ;; <-- intentionally wrong + #_=> :saturation-vapour-pressure 5622.488734516426, + #_=> :temperature-celsius 35, + #_=> :temperature-kelvin 308.15, + #_=> :volume 1.1281347245103424, + #_=> :wet-bulb-temperature-celsius 35.11625928298915})) +true +``` ## Developing @@ -66,6 +116,8 @@ lein repl and hook in your preferred editor as appropriate for that editor. +Full documentation is available [here](https://www.journeyman.cc/humidity). + ## Testing Tests are reasonably complets but all could do with more examples, especially around the crucial 35° C wet bulb area and towards the extremities of normal weather range. If you are contributing additional code, I would appreciate it being accompanied by tests. diff --git a/doc/intro.md b/doc/intro.md new file mode 100644 index 0000000..db24108 --- /dev/null +++ b/doc/intro.md @@ -0,0 +1,128 @@ +# Introduction + +## Disclaimer + +This library is supplied in the hope that it may be useful, but results computed with it should not be taken as authoritative but should be checked against other sources. I do not have enough expertise to guarantee its results are correct. + +## 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. `:absolute-humidity` *grammes per metre3.* +2. `:actual-vapour-pressure` *Pascals* +1. `:dew-point-celsius` +2. `:pressure-pascals` +3. `:pressure-millibars` +4. `:relative-humidity` *percentage, 0...100.* +5. `:saturation-vapour-pressure` *Pascals* +6. `:temperature` *assumed to be ° Celsius* +7. `:temperature-celsius` +8. `:temperature-kelvin` +9. `:volume` *in metres3, relative to 1 at standard temperature and pressure.* +10. `:wet-bulb-temperature-celsius` *in ° Celsius.* + +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 tangle 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 packet and repeatedly tries to compute the values for more variables until it can compute no more, and then returns the enhanced packet. + +Thus: + +```clojure +humidity.core=> (use 'humidity.core :reload) +nil +humidity.core=> (resolution :relative-humidity 100 :temperature-celsius 35) +{:relative-humidity 100, :temperature-celsius 35, :pressure-pascals 101325, :pressure-millibars 4053/4, :saturation-vapour-pressure 5622.488734516426, :temperature-kelvin 308.15, :absolute-humidity 3953.618101887827} +``` + +### Naming + +Associated with each variable there is a namespace of the same name which contains a function, whose name is the name of the variable with `-fn` appended, that takes a packet, and, provided that packet contains enough information to compute that variable, returns a numeric value, else `nil`. + +### Validation + +A packet may be validated by calling the function `validate`. This recalculates all values in the packet, and, by default, tolerates discrepancies of up to 1%. This tolerance can be varied by binding the dynamic variable `*validation-limit*`. Validation merely checks that the values within the packet are consistent with one another, and does not properly account for pressure, so will not be valid at altitude. + +```clojure +humidity.core=> (validate {:absolute-humidity 3953.618101887827, + #_=> :actual-vapour-pressure 4765.10094206357, + #_=> :dew-point-celsius 35.00000000000001, + #_=> :pressure-millibars 1013.25, + #_=> :pressure-pascals 101325, + #_=> :relative-humidity 100, + #_=> :saturation-vapour-pressure 5622.488734516426, + #_=> :temperature-celsius 35, + #_=> :temperature-kelvin 308.15, + #_=> :volume 1.1281347245103424, + #_=> :wet-bulb-temperature-celsius 35.11625928298915} + #_=> ) +true + +humidity.core=> (validate {:absolute-humidity 3953.618101887827, + #_=> :actual-vapour-pressure 4765.10094206357, + #_=> :dew-point-celsius 35.00000000000001, + #_=> :pressure-millibars 1013.25, + #_=> :pressure-pascals 101325, + #_=> :relative-humidity 95, ;; <-- intentionally wrong + #_=> :saturation-vapour-pressure 5622.488734516426, + #_=> :temperature-celsius 35, + #_=> :temperature-kelvin 308.15, + #_=> :volume 1.1281347245103424, + #_=> :wet-bulb-temperature-celsius 35.11625928298915} + #_=> ) +ERROR: key: :dew-point-celsius; a: 35.00000000000001; b 34.07737599732603; errror: 2.857142925262451%. +false + +humidity.core=> (binding [*validation-limit* 5] + #_=> (validate {:absolute-humidity 3953.618101887827, + #_=> :actual-vapour-pressure 4765.10094206357, + #_=> :dew-point-celsius 35.00000000000001, + #_=> :pressure-millibars 1013.25, + #_=> :pressure-pascals 101325, + #_=> :relative-humidity 95, ;; <-- intentionally wrong + #_=> :saturation-vapour-pressure 5622.488734516426, + #_=> :temperature-celsius 35, + #_=> :temperature-kelvin 308.15, + #_=> :volume 1.1281347245103424, + #_=> :wet-bulb-temperature-celsius 35.11625928298915})) +true +``` + +## Developing + +This is a fairly standard [leiningen](https://leiningen.org/) project. I anticipate that most Clojure developers will have leininen installed, even if they no longer use it much. Start a repl with + +``` +lein repl +``` + +and hook in your preferred editor as appropriate for that editor. + +Full documentation is available [here](https://www.journeyman.cc/humidity). + +## Testing + +Tests are reasonably complets but all could do with more examples, especially around the crucial 35° C wet bulb area and towards the extremities of normal weather range. If you are contributing additional code, I would appreciate it being accompanied by tests. + +I suggest running tests using + +``` +lein cloverage +``` + +As this gives not only pass/fail output, but also test coverage output. + +Current test results are [here](cloverage/index.html), for comparison. + +## Contributing + +Pull requests are welcomed, provided they do not contain AI generated code. I'm not going to waste time debugging Claude's mess, my own is bad enough. + +## License + +Copyright © 2026 Simon Brooke. Licensed under the GNU General Public License, +version 2.0 or (at your option) any later version. diff --git a/project.clj b/project.clj index 268d971..430e3a3 100644 --- a/project.clj +++ b/project.clj @@ -1,10 +1,10 @@ (defproject humidity "0.1.1-SNAPSHOT" - :cloverage {:output "docs/cloverage" + :cloverage {:output "docs/humidity/cloverage" :ns-exclude-regex [#"beowulf\.gendoc" #"beowulf\.scratch"]} :codox {:metadata {:doc "**TODO**: write docs" :doc/format :markdown} - :output-path "docs/codox" - :source-uri "https://github.com/simon-brooke/humidity/blob/master/{filepath}#L{line}"} + :output-path "docs/humidity/" + :source-uri "https://git.journeyman.cc/simon/humidity/src/branch/main/{filepath}#L{line}"} :dependencies [[org.clojure/clojure "1.12.5"] [io.github.nextjournal/clerk "0.18.1158"]] :deploy-repositories [["releases" :clojars] @@ -18,4 +18,4 @@ :plugins [[lein-ancient "1.0.0"] [lein-cloverage "1.2.2"] [lein-codox "0.10.8"]] - :url "https://git.journeyman.cc/simon/humidity") + :url "https://www.journeyman.cc/humidity/") diff --git a/test/humidity/core_test.clj b/test/humidity/core_test.clj index fca45ee..d76cf10 100644 --- a/test/humidity/core_test.clj +++ b/test/humidity/core_test.clj @@ -25,21 +25,36 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (deftest resolution-test - (testing "resolution") - (let [expected {:absolute-humidity 3953.618101887827, - :actual-vapour-pressure 4765.10094206357, - :dew-point-celsius 35.00000000000001, - :pressure-millibars 1013.25, - :pressure-pascals 101325, - :relative-humidity 100, - :saturation-vapour-pressure 5622.488734516426, - :temperature-celsius 35, - :temperature-kelvin 308.15, - :volume 1.1281347245103424, - :wet-bulb-temperature-celsius 35.11625928298915} - packet {:relative-humidity 100 :temperature-celsius 35} - actual (resolution packet)] - (is (= actual expected)))) + (testing "resolution" + (let [expected {:absolute-humidity 3953.618101887827, + :actual-vapour-pressure 4765.10094206357, + :dew-point-celsius 35.00000000000001, + :pressure-millibars 1013.25, + :pressure-pascals 101325, + :relative-humidity 100, + :saturation-vapour-pressure 5622.488734516426, + :temperature-celsius 35, + :temperature-kelvin 308.15, + :volume 1.1281347245103424, + :wet-bulb-temperature-celsius 35.11625928298915}] + (let [packet {:relative-humidity 100 :temperature-celsius 35} + actual (resolution packet)] + (is (= actual expected))) + (let [packet {:relative-humidity 100 :temperature-kelvin 308.15} + actual (resolution packet)] + (is (= actual expected))) + (let [packet {:relative-humidity 100 :temperature 35} + actual (resolution packet)] + (is (= actual expected))) + (let [packet {:relative-humidity 100 :pressure-pascals 101325 :temperature 35} + actual (resolution packet)] + (is (= actual expected))) + (let [packet {:relative-humidity 100 :pressure-millibars 1013.25 :temperature 35} + actual (resolution packet)] + (is (= actual expected))) + (let [packet {:absolute-humidity 3953.618101887827 :temperature-celsius 35} + actual (resolution packet)] + (is (= actual expected)))))) (deftest validation-test (testing "validation" diff --git a/test/humidity/saturation_vp_test.clj b/test/humidity/saturation_vp_test.clj index b66f30b..c19a4ee 100644 --- a/test/humidity/saturation_vp_test.clj +++ b/test/humidity/saturation_vp_test.clj @@ -1,5 +1,6 @@ (ns humidity.saturation-vp-test (:require [clojure.test :refer [deftest is testing]] + [humidity.constants :refer [celsius-offset]] [humidity.saturation-vapour-pressure :refer [saturation-vapour-pressure-fn]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -28,4 +29,8 @@ (let [min-expected 5622 max-expected 5629 actual (saturation-vapour-pressure-fn {:temperature-celsius 35})] - (is (< min-expected actual max-expected))))) + (is (< min-expected actual max-expected))) + (let [min-expected 5622 + max-expected 5629 + actual (saturation-vapour-pressure-fn {:temperature-kelvin (+ celsius-offset 35)})] + (is (< min-expected actual max-expected)))))