Merge branch 'develop'
This commit is contained in:
commit
f8572b5b03
18 changed files with 475 additions and 73 deletions
|
|
@ -12,4 +12,8 @@ Computes:
|
|||
4. :pressure-millibars
|
||||
5. :pressure-pascals
|
||||
6. :relative-humidity
|
||||
7. :saturation-vapour-pressure :temperature-celsius :temperature-kelvin :volume
|
||||
7. :saturation-vapour-pressure
|
||||
8. :temperature-celsius
|
||||
9. :temperature-kelvin
|
||||
10. :volume
|
||||
11. :wet-bulb-temperature
|
||||
|
|
|
|||
66
README.md
66
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 metres<sup>3</sup>, 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.
|
||||
|
|
|
|||
128
doc/intro.md
Normal file
128
doc/intro.md
Normal file
|
|
@ -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 metre<sup>3</sup>.*
|
||||
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 metres<sup>3</sup>, 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.
|
||||
124
notebooks/june_26th_exploration.clj
Normal file
124
notebooks/june_26th_exploration.clj
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
(ns june-26th-exploration
|
||||
(:require [humidity.core :refer [resolution]]))
|
||||
|
||||
;; # Exploration of what happened on 26th June this year
|
||||
;;
|
||||
;; On June 26th 2026, at the peak of the second heatwave, there is reported to have been sea mist coming ashore in southern England. What does that imply about inland humidity later in the day, and what happens if we project that foreward to the 2056 numbers?
|
||||
;;
|
||||
;; ## The Newquay → Bristol axis
|
||||
;;
|
||||
;; Wind in Newquay starts southerly around dawn and veers south westerly through the morning, freshening from 17 kph at dawn to 28 at 10:50 and then veering further west and decreasing through the afternoon.
|
||||
;;
|
||||
;; So that means air from Newquay will have been more or less over Bristol or Cardiff by 15:00.
|
||||
;;
|
||||
;; Sunrise at Newquay was 05:06, sunset 21:31.
|
||||
|
||||
;; [Data](https://www.wunderground.com/history/daily/gb/plymouth/EGHQ/date/2026-6-26)
|
||||
(def newquay-conditions-0650
|
||||
(resolution :relative-humidity 100
|
||||
:pressure-millibars 998
|
||||
:temperature-celsius 18))
|
||||
|
||||
;; [Data](https://www.wunderground.com/history/daily/gb/bristol/EGGD/date/2026-6-26)
|
||||
|
||||
(def bristol-conditions-1500
|
||||
(resolution :relative-humidity 65
|
||||
:pressure-millibars 993
|
||||
:temperature-celsius 26))
|
||||
|
||||
(def projected-conditions-bristol-1500
|
||||
(resolution :absolute-humidity (* (newquay-conditions-0650 :absolute-humidity)
|
||||
(/ (bristol-conditions-1500 :volume)
|
||||
(newquay-conditions-0650 :volume)))
|
||||
:pressure-millibars (bristol-conditions-1500 :pressure-millibars)
|
||||
:temperature-celsius (bristol-conditions-1500 :temperature-celsius)))
|
||||
|
||||
;; ### So what just happened?
|
||||
;;
|
||||
;; Firstly, the temperature of the air increased from 19° C to 26°C, an increase of 7° C.
|
||||
;;
|
||||
;; Second, the pressure actually fell slightly from 998 to 993 millibars (why?). So the change in volume of the air due to the changes in temperature and pressure was
|
||||
|
||||
(float (/ (bristol-conditions-1500 :volume)
|
||||
(newquay-conditions-0650 :volume)))
|
||||
|
||||
;; a very small change. Nevertheless, we've plugged that change into our projection. The actual relative humidity at Bristol at 15:00 as recorded by Wunderground was
|
||||
|
||||
(bristol-conditions-1500 :relative-humidity)
|
||||
|
||||
;; whereas our projection is
|
||||
|
||||
(projected-conditions-bristol-1500 :relative-humidity)
|
||||
|
||||
;; which is somewhat conservative. The air over Bristol is wetter than I would have expected if this is the 'same air' as came ashore at Newquay.
|
||||
;;
|
||||
;; ## The Weymouth → Oxford axis
|
||||
;;
|
||||
;; Bristol is sort-of coastal, and not where the highest temperatures are to be expected. By contrast, Oxford is more inland, and close to where the hottest temperatures [projected by the Met Office for 2056](https://www.metoffice.gov.uk/blog/2026/uk-could-see-45c-by-2056-scientists-reflect-on-1976-heatwave-anniversary) are to be found.
|
||||
;;
|
||||
;; So let's examine that axis. The interesting time is 09:00, when [air is coming ashore with a relative humidity of 97%](https://www.wunderground.com/history/daily/gb/weymouth/EGDP/date/2026-6-26). But note that very humid air was continuing to come ashore all through the day.
|
||||
;;
|
||||
;; The wind is west-south-west, so not blowing directly towards Oxford, but I'm going to assume it was since that's certainly feasible. It was blowing 26 kph at 09:00, increasing to 33 kph by 14:00. So it would travel the 150Km to Oxford in 6 hours.
|
||||
|
||||
(def weymouth-conditions-0900 (resolution :relative-humidity 97
|
||||
:pressure-millibars 1008.9
|
||||
:temperature-celsius 17 ))
|
||||
|
||||
;; [Data](https://www.wunderground.com/history/daily/gb/oxford/EGTK/date/2026-6-26)
|
||||
|
||||
(def oxford-conditions-1520 (resolution :relative-humidity 34
|
||||
:pressure-millibars 1004.18
|
||||
:temperature-celsius 33))
|
||||
|
||||
(def projected-conditions-oxford-1520
|
||||
(resolution :absolute-humidity (* (weymouth-conditions-0900 :absolute-humidity)
|
||||
(/ (oxford-conditions-1520 :volume)
|
||||
(weymouth-conditions-0900 :volume)))
|
||||
:pressure-millibars (oxford-conditions-1520 :pressure-millibars)
|
||||
:temperature-celsius (oxford-conditions-1520 :temperature-celsius)))
|
||||
|
||||
;; ### What happened here?
|
||||
|
||||
;; Here the air is substantially drier than expected. The absolute and relative humidity in the observed data is
|
||||
|
||||
[(oxford-conditions-1520 :absolute-humidity) (oxford-conditions-1520 :relative-humidity)]
|
||||
|
||||
;; whereas in the projected data it is
|
||||
|
||||
[(projected-conditions-oxford-1520 :absolute-humidity) (projected-conditions-oxford-1520 :relative-humidity)]
|
||||
|
||||
;; if this is 'the same air', then we've lost about 120 grammes of water per cubic metre of air Again there's
|
||||
;; a change in volume
|
||||
|
||||
(/ (oxford-conditions-1520 :volume)
|
||||
(weymouth-conditions-0900 :volume))
|
||||
|
||||
;; but, again, we've accounted for that.
|
||||
|
||||
;; So I don't know what's happening and, again, my understanding may be wrong. I would expect air over land in hot weather to show a slight increase in absolute humidity from evaporating dew, and from transpiration from green plants. Here, we're seeing a substantial decrease, and there's no precipitation in the record.
|
||||
;;
|
||||
;; I don't understand this.
|
||||
|
||||
;; ## The 2056 projection
|
||||
|
||||
;; In the video embedded in their blog, the Met Office show their projected minimum temperatures for their 2056 'hottest day' [at 46 seconds in](https://youtu.be/YXwk3Eqa8l8?si=Ypc_XpqOIDsNUv61&t=46). I'm going to assume this is at dawn.
|
||||
|
||||
;; Weymouth is shown as between 17° C and 20° C; and by interpolation I'm going to take 19° C.
|
||||
|
||||
(def weymouth-model-0600 (resolution :temperature-celsius 19
|
||||
:pressure-millibars (weymouth-conditions-0900 :pressure-millibars)
|
||||
:relative-humidity (weymouth-conditions-0900 :relative-humidity)))
|
||||
|
||||
;; Oxford is close to the peak temperature of 45° C shown on the Met Office's projected map. They don't give a time for this, but I'm going to assume mid-afternoon.
|
||||
|
||||
(def oxford_model-1500 (resolution :temperature-celsius 45
|
||||
:pressure-millibars (oxford-conditions-1520 :pressure-millibars)
|
||||
:absolute-humidity (weymouth-model-0600 :absolute-humidity)))
|
||||
|
||||
;; Note that, the conditions I've assumed here — that the absolute humidity will remain constant as the air moves from Weymouth to Oxford — are worse than what in the observed data, where the air mysteriously dried.
|
||||
|
||||
;; Nevertheless, despite the high absolute humidity, the relative humidity remains at a survivable level
|
||||
|
||||
(oxford_model-1500 :wet-bulb-temperature)
|
||||
|
||||
;; I think that this is the absolute 'worst case' for the Met Office projection, and it clearly does not imply a mass deaths event; so I was mistaken.
|
||||
15
project.clj
15
project.clj
|
|
@ -1,11 +1,14 @@
|
|||
(defproject humidity "0.1.0"
|
||||
:cloverage {:output "docs/cloverage"
|
||||
(defproject humidity "0.1.1-SNAPSHOT"
|
||||
: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}"}
|
||||
:dependencies [[org.clojure/clojure "1.12.5"]]
|
||||
: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]
|
||||
["snapshots" :clojars]]
|
||||
:description "Functions to compute relative humidity, absolute humidity, wet bulb
|
||||
temperature, and other values related to humidity of air, given
|
||||
partial data."
|
||||
|
|
@ -15,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/")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
in Pascals, and relative humidity as a percentage (0...100)."
|
||||
:authority "https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/"
|
||||
:author "simon"}
|
||||
humidity.absolute
|
||||
humidity.absolute-humidity
|
||||
(:require
|
||||
[humidity.constants :refer [Rw]]))
|
||||
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
(def C magic-numbers)
|
||||
|
||||
(defn abs-humidity
|
||||
(defn absolute-humidity-fn
|
||||
"calculate and return absolute humidity in grammes per metre<sup>3</sup>
|
||||
given temperature in ° K, saturation vapour pressure in Pascals, and
|
||||
relative humidity as a percentage (0...100)."
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
(ns ^{:author "simon"
|
||||
:authority "https://www.weather.gov/media/epz/wxcalc/vaporPressure.pdf"
|
||||
:doc "Compute actual vapour pressure."}
|
||||
humidity.actual-vp
|
||||
humidity.actual-vapour-pressure
|
||||
(:require [humidity.utils :refer [expt]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
(ns ^{:doc "Central resolver function for humidity calculations"
|
||||
:author "simon"}
|
||||
humidity.core
|
||||
(:require [humidity.absolute :refer [abs-humidity]]
|
||||
[humidity.actual-vp :refer [actual-vapour-pressure-fn]]
|
||||
(:require [humidity.absolute-humidity :refer [absolute-humidity-fn]]
|
||||
[humidity.actual-vapour-pressure :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.dew-point-celsius :refer [dew-point-celsius-fn]]
|
||||
[humidity.relative-humidity :refer [relative-humidity-fn]]
|
||||
[humidity.saturation-vapour-pressure :refer [saturation-vapour-pressure-fn]]
|
||||
[humidity.utils :refer [?assoc]]
|
||||
[humidity.volume :refer [volume-fn]]
|
||||
[humidity.wet-bulb :as wb]))
|
||||
[humidity.wet-bulb-temperature-celsius :refer [wet-bulb-temperature-celsius-fn]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;
|
||||
|
|
@ -38,14 +38,13 @@
|
|||
pressure-pascals pressure-millibars
|
||||
relative-humidity saturation-vapour-pressure temperature
|
||||
temperature-celsius temperature-kelvin volume
|
||||
wet-bulb-temperature] :as packet}]
|
||||
(println packet)
|
||||
wet-bulb-temperature-celsius] :as packet}]
|
||||
(if temperature (resolve-humidity
|
||||
(assoc (dissoc packet :temperature)
|
||||
:temperature-celsius temperature))
|
||||
(?assoc packet
|
||||
(?assoc (into (sorted-map) packet)
|
||||
:absolute-humidity (when-not absolute-humidity
|
||||
(abs-humidity packet))
|
||||
(absolute-humidity-fn packet))
|
||||
:actual-vapour-pressure (when-not actual-vapour-pressure
|
||||
(actual-vapour-pressure-fn packet))
|
||||
:dew-point-celsius (when-not dew-point-celsius
|
||||
|
|
@ -58,20 +57,21 @@
|
|||
(float (/ pressure-pascals 100))
|
||||
(float (/ Pa 100))))
|
||||
:relative-humidity (when-not relative-humidity
|
||||
(rel-humidity packet))
|
||||
(relative-humidity-fn packet))
|
||||
:saturation-vapour-pressure (when-not saturation-vapour-pressure
|
||||
(saturation-vp packet))
|
||||
:temperature-celsius (when
|
||||
(and temperature-kelvin
|
||||
(not temperature-celsius))
|
||||
(saturation-vapour-pressure-fn packet))
|
||||
:temperature-celsius (cond
|
||||
temperature temperature
|
||||
(and temperature-kelvin
|
||||
(not temperature-celsius))
|
||||
(- temperature-kelvin celsius-offset))
|
||||
:temperature-kelvin (when
|
||||
(and temperature-celsius
|
||||
(not temperature-kelvin))
|
||||
(+ temperature-celsius celsius-offset))
|
||||
:volume (when-not volume (volume-fn packet))
|
||||
:wet-bulb-temperature (when-not wet-bulb-temperature
|
||||
(wb/wet-bulb-temperature packet)))))
|
||||
:wet-bulb-temperature-celsius (when-not wet-bulb-temperature-celsius
|
||||
(wet-bulb-temperature-celsius-fn packet)))))
|
||||
|
||||
(defn resolution
|
||||
"Repeatedly run `resolve-humidity`, q.v., until we can infer no further
|
||||
|
|
@ -81,3 +81,45 @@
|
|||
(let [p' (resolve-humidity p)]
|
||||
(if (= (keys p) (keys p')) p
|
||||
(recur p')))))
|
||||
|
||||
(defn validation-error
|
||||
[key limit a b]
|
||||
(let [m (max a b)
|
||||
error (abs (float (/ (compare a b) (if (zero? m) 0.000001 m) (/ limit 100))))]
|
||||
(if (> error limit)
|
||||
(do
|
||||
(println (format "ERROR: key: %s; a: %s; b %s; errror: %s%%." key a b error))
|
||||
false)
|
||||
true)))
|
||||
|
||||
(def ^:dynamic
|
||||
*validation-limit*
|
||||
"Percentage limit for discrepancies in values during validation."
|
||||
1)
|
||||
|
||||
(defn validate
|
||||
"Validate a packet by recalculating each value. Discrepancies within
|
||||
`*validation-limit*`% will be ignored."
|
||||
[& {:keys [pressure-millibars pressure-pascals temperature-celsius
|
||||
temperature-kelvin] :as packet}]
|
||||
(let [v (?assoc (into (sorted-map) packet)
|
||||
:absolute-humidity (absolute-humidity-fn packet)
|
||||
:actual-vapour-pressure (actual-vapour-pressure-fn packet)
|
||||
:dew-point-celsius (dew-point-celsius-fn packet)
|
||||
:pressure-pascals (if pressure-millibars
|
||||
(* pressure-millibars 100) Pa)
|
||||
:pressure-millibars (if pressure-pascals
|
||||
(float (/ pressure-pascals 100))
|
||||
(float (/ Pa 100)))
|
||||
:relative-humidity (relative-humidity-fn packet)
|
||||
:saturation-vapour-pressure
|
||||
(saturation-vapour-pressure-fn packet)
|
||||
:temperature-celsius (- temperature-kelvin celsius-offset)
|
||||
:temperature-kelvin (+ temperature-celsius celsius-offset)
|
||||
:volume (volume-fn packet)
|
||||
:wet-bulb-temperature-celsius
|
||||
(wet-bulb-temperature-celsius-fn packet))]
|
||||
(empty?
|
||||
(remove true?
|
||||
(map #(validation-error % *validation-limit* (packet %) (v %))
|
||||
(keys packet))))))
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
: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
|
||||
in ° Celsius and relative humidity percentage."}
|
||||
humidity.dew-point-celsius
|
||||
(:require [clojure.math :refer [log]]
|
||||
[humidity.constants :refer [magnus-coefficients]]))
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
(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
|
||||
:doc "Function to return relative humidity."} humidity.relative-humidity
|
||||
(:require [humidity.constants :refer [e Rw]]
|
||||
[humidity.utils :refer [expt]]))
|
||||
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
;; relative-humidity = -------------------------------------------
|
||||
;; 1000 x saturation-pressure
|
||||
|
||||
(defn rel-humidity
|
||||
(defn relative-humidity-fn
|
||||
"Returns relative humidity (as a percentage, 0...100) given values for dew
|
||||
point and temperature in Celsius or for absolute humidity, saturation
|
||||
vapour pressure, temperature in Kelvin."
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
(ns ^{:doc "Saturation vapour pressure function"
|
||||
:authority "https://en.wikipedia.org/wiki/Tetens_equation"
|
||||
:author "simon"}
|
||||
humidity.saturation-vp
|
||||
humidity.saturation-vapour-pressure
|
||||
(:require [clojure.math :refer [pow]]
|
||||
[humidity.constants :refer [celsius-offset e]]))
|
||||
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
;; constant 100 to convert to Pascals
|
||||
(* a (pow e (/ (* b temperature-celsius) (+ c temperature-celsius))))))
|
||||
|
||||
(defn saturation-vp
|
||||
(defn saturation-vapour-pressure-fn
|
||||
"Calculate and return saturation vapour pressure, is possible,
|
||||
given the data in this packet."
|
||||
[packet]
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
(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
|
||||
humidity.wet-bulb-temperature-celsius
|
||||
(:require [clojure.math :refer [atan]]
|
||||
[humidity.utils :refer [expt]]))
|
||||
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
(def C magic-numbers)
|
||||
|
||||
(defn wet-bulb-temperature
|
||||
(defn wet-bulb-temperature-celsius-fn
|
||||
"Returns wet bulb temperature (in ° C) given this `temperature` in
|
||||
° C and `relative-humidity` percentage (0...100)"
|
||||
[& {:keys [temperature-celsius relative-humidity]}]
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
(ns humidity.absolute-test
|
||||
(:require [clojure.test :refer [deftest is testing]]
|
||||
[humidity.constants :refer [celsius-offset]]
|
||||
[humidity.absolute :refer [abs-humidity]]))
|
||||
[humidity.absolute-humidity :refer [absolute-humidity-fn]]))
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;
|
||||
;;; Copyright (C) 2026 Simon Brooke
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
(testing "definitional"
|
||||
(let [min-expected 39.58
|
||||
max-expected 39.59
|
||||
actual (abs-humidity :relative-humidity 100
|
||||
actual (absolute-humidity-fn :relative-humidity 100
|
||||
:saturation-vapour-pressure 56.29
|
||||
:temperature-kelvin (+ 35 celsius-offset))]
|
||||
(is (< min-expected actual max-expected)))))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(ns humidity.core-test
|
||||
(:require
|
||||
[clojure.test :refer [deftest is testing]]
|
||||
[humidity.core :refer [resolution]]))
|
||||
[humidity.core :refer [resolution validate]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;
|
||||
|
|
@ -25,18 +25,62 @@
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(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 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"
|
||||
(let [expected true
|
||||
actual (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})]
|
||||
(is (= actual expected)))
|
||||
(let [expected false
|
||||
actual (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, ;; <-- deliberately wrong
|
||||
:saturation-vapour-pressure 5622.488734516426,
|
||||
:temperature-celsius 35,
|
||||
:temperature-kelvin 308.15,
|
||||
:volume 1.1281347245103424,
|
||||
:wet-bulb-temperature-celsius 35.11625928298915})]
|
||||
(is (= actual expected)))))
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
(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]]))
|
||||
[humidity.dew-point-celsius :refer [dew-point-celsius-fn]]))
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;
|
||||
;;; Copyright (C) 2026 Simon Brooke
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(ns humidity.relative-test
|
||||
(:require [clojure.test :refer [deftest is testing]]
|
||||
[humidity.constants :refer [celsius-offset]]
|
||||
[humidity.relative :refer [rel-humidity rel-humidity]]))
|
||||
[humidity.relative-humidity :refer [relative-humidity-fn]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;
|
||||
|
|
@ -28,10 +28,10 @@
|
|||
(testing "definitional"
|
||||
(let [min-expected 99
|
||||
max-expected 101
|
||||
actual (rel-humidity :absolute-humidity 3953.618 :saturation-vapour-pressure 5622.49 :temperature-kelvin (+ 35 celsius-offset))]
|
||||
actual (relative-humidity-fn :absolute-humidity 3953.618 :saturation-vapour-pressure 5622.49 :temperature-kelvin (+ 35 celsius-offset))]
|
||||
(is (< min-expected actual max-expected))))
|
||||
(testing "Online examples"
|
||||
(let [min-expected 99
|
||||
max-expected 101
|
||||
actual (rel-humidity :dew-point 35 :temperature-celsius 35)]
|
||||
actual (relative-humidity-fn :dew-point 35 :temperature-celsius 35)]
|
||||
(is (< min-expected actual max-expected)))))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
(ns humidity.saturation-vp-test
|
||||
(:require [clojure.test :refer [deftest is testing]]
|
||||
[humidity.saturation-vp :refer [saturation-vp]]))
|
||||
[humidity.constants :refer [celsius-offset]]
|
||||
[humidity.saturation-vapour-pressure :refer [saturation-vapour-pressure-fn]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;
|
||||
|
|
@ -27,5 +28,9 @@
|
|||
(testing "from online sources"
|
||||
(let [min-expected 5622
|
||||
max-expected 5629
|
||||
actual (saturation-vp {:temperature-celsius 35})]
|
||||
(is (< min-expected actual max-expected)))))
|
||||
actual (saturation-vapour-pressure-fn {:temperature-celsius 35})]
|
||||
(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)))))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
(ns humidity.wet-bulb-test
|
||||
(:require [clojure.test :refer [deftest is testing]]
|
||||
[humidity.wet-bulb :refer [wet-bulb-temperature]]))
|
||||
[humidity.wet-bulb-temperature-celsius :refer [wet-bulb-temperature-celsius-fn]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
(let [expected 13.7
|
||||
temperature 20
|
||||
rel-humidity 50
|
||||
actual (wet-bulb-temperature :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
actual (wet-bulb-temperature-celsius-fn :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
(println (format "Expected: %s; actual: %s" expected actual))
|
||||
(is (< (abs (- expected actual)) 0.001))))
|
||||
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
max-expected 33
|
||||
temperature 37
|
||||
rel-humidity 70
|
||||
actual (wet-bulb-temperature :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
actual (wet-bulb-temperature-celsius-fn :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
(println (format "Expected: %s...%s; actual: %s" min-expected max-expected actual))
|
||||
(is (< min-expected actual max-expected)))
|
||||
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
max-expected 36
|
||||
temperature 39.7
|
||||
rel-humidity 70
|
||||
actual (wet-bulb-temperature :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
actual (wet-bulb-temperature-celsius-fn :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
(println (format "Expected: %s...%s; actual: %s" min-expected max-expected actual))
|
||||
(is (< min-expected actual max-expected)))
|
||||
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
max-expected 34
|
||||
temperature 40
|
||||
rel-humidity 60
|
||||
actual (wet-bulb-temperature :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
actual (wet-bulb-temperature-celsius-fn :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
(println (format "Expected: %s...%s; actual: %s" min-expected max-expected actual))
|
||||
(is (< min-expected actual max-expected)))
|
||||
|
||||
|
|
@ -61,6 +61,6 @@
|
|||
max-expected 35
|
||||
temperature 51
|
||||
rel-humidity 30
|
||||
actual (wet-bulb-temperature :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
actual (wet-bulb-temperature-celsius-fn :temperature-celsius temperature :relative-humidity rel-humidity)]
|
||||
(println (format "Expected: %s...%s; actual: %s" min-expected max-expected actual))
|
||||
(is (< min-expected actual max-expected)))))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue