From 3874a37a868ddc564a89956355962a6ec8760366 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 9 Aug 2026 13:32:32 +0100 Subject: [PATCH] I have not yet got the overall architecture right, but the functions are beginning to work. --- project.clj | 7 +++ resources/absolute-humidity-eqn.gif | Bin 0 -> 7896 bytes resources/katzenberger.txt | 49 +++++++++++++++++++ resources/saturation-vapour-pressure-eqn.png | Bin 0 -> 3628 bytes resources/wet-bulb-temp-eqn.png | Bin 0 -> 31588 bytes src/humidity/absolute.clj | 38 ++++++++++++++ src/humidity/constants.clj | 22 +++++++++ src/humidity/core.clj | 49 +++++++++++++++++++ src/humidity/relative.clj | 1 + src/humidity/saturation_vp.clj | 25 ++++++++++ src/humidity/utils.clj | 27 ++++++++++ src/humidity/wet_bulb.clj | 26 ++++++++++ test/humidity/wet_bulb_test.clj | 47 ++++++++++++++++++ 13 files changed, 291 insertions(+) create mode 100644 project.clj create mode 100644 resources/absolute-humidity-eqn.gif create mode 100644 resources/katzenberger.txt create mode 100644 resources/saturation-vapour-pressure-eqn.png create mode 100644 resources/wet-bulb-temp-eqn.png create mode 100644 src/humidity/absolute.clj create mode 100644 src/humidity/constants.clj create mode 100644 src/humidity/core.clj create mode 100644 src/humidity/relative.clj create mode 100644 src/humidity/saturation_vp.clj create mode 100644 src/humidity/utils.clj create mode 100644 src/humidity/wet_bulb.clj create mode 100644 test/humidity/wet_bulb_test.clj diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..326d174 --- /dev/null +++ b/project.clj @@ -0,0 +1,7 @@ +(defproject humidity "0.1.0-SNAPSHOT" + :description "FIXME: write description" + :url "http://example.com/FIXME" + :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" + :url "https://www.eclipse.org/legal/epl-2.0/"} + :dependencies [[org.clojure/clojure "1.11.1"]] + :repl-options {:init-ns humidity.core}) diff --git a/resources/absolute-humidity-eqn.gif b/resources/absolute-humidity-eqn.gif new file mode 100644 index 0000000000000000000000000000000000000000..e620841a8d7bb1a2099a6ca0fcb5e2cdb4c52791 GIT binary patch literal 7896 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 C>dM~$ literal 0 HcmV?d00001 diff --git a/resources/katzenberger.txt b/resources/katzenberger.txt new file mode 100644 index 0000000..76c60dc --- /dev/null +++ b/resources/katzenberger.txt @@ -0,0 +1,49 @@ +# I'm guessing this is Python + +# Saturated water vapor pressure in hPa, Magnus Formula +# Parameters from Sonntag1990, for −45 °C ≤ T ≤ 60 °C (error ±0.35 °C). +def saturatedVaporPressureMagnusSonntag1990(temperatureCelsius): + a = 6.112 + b = 17.62 + c = 243.12 + + saturatedVaporPressure = a * math.exp((b * temperatureCelsius) / (c + temperatureCelsius)) + return saturatedVaporPressure + + +# Saturated water vapor pressure in hPa, Tetens Formula +def saturatedVaporPressureTetens(temperatureCelsius): + a = 6.1078 + b = 17.27 + c = 237.3 + + saturatedVaporPressure = a * math.exp((b * temperatureCelsius) / (c + temperatureCelsius)) + return saturatedVaporPressure + + +# Saturated water vapor pressure in hPa, Buck 1996 Formula +def saturatedVaporPressureBuck1996(temperatureCelsius): + if temperatureCelsius < 0: + return 6.1115 * math.exp( + (23.06 - temperatureCelsius / 333.7) * (temperatureCelsius / (279.82 + temperatureCelsius))) + else: + return 6.1121 * math.exp( + (18.678 - temperatureCelsius / 234.5) * (temperatureCelsius / (257.14 + temperatureCelsius))) + + +# Actual water vapor pressure in hPa +def vaporPressure(relativeHumidity, temperatureCelsius): + vaporPressure = relativeHumidity / 100.0 * saturatedVaporPressureBuck1996(temperatureCelsius) + return vaporPressure + + +# Absolute humidity in g/m³ +def absoluteHumidity(relativeHumidity, temperatureCelsius): + molarMassOfWaterVapor = 18.01528 + universalGasConstant = 8314.46261815324 + zeroCelsiusInKelvin = 273.15 + + absoluteHumidity = 10 ** 5 * molarMassOfWaterVapor / universalGasConstant * vaporPressure(relativeHumidity, + temperatureCelsius) / ( + temperatureCelsius + zeroCelsiusInKelvin) + return absoluteHumidity \ No newline at end of file diff --git a/resources/saturation-vapour-pressure-eqn.png b/resources/saturation-vapour-pressure-eqn.png new file mode 100644 index 0000000000000000000000000000000000000000..d2dd39df038563174fad017097cebce1a640f0e5 GIT binary patch literal 3628 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 literal 0 HcmV?d00001 diff --git a/resources/wet-bulb-temp-eqn.png b/resources/wet-bulb-temp-eqn.png new file mode 100644 index 0000000000000000000000000000000000000000..df55be663c5e087cb125ab128e2f722e2ebc25f9 GIT binary patch literal 31588 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 $UrJsI+gKj^?Ip?zAdwh->LeD=8^iMVhKV(H^P` zkBUIVs9Xm)+_xw`9fI(vs>%@*$mOnZ5(afzd)vvn3_;6JS3Ua}l4-lq^ErwXlLU=Z zmD4}STO4GG{K_E8_aKy*JN^}W``)u$sd}`*C0=LwdOO42xQX^mvhU}P(iNkl@-D6} zU&rI90eRFdDk$h8cmfY`I36P=HVjWy+yuKE09y(z9mh&l4iaqT5V~k%Cj!`!x^H=IZf=Tl8Ft9`c!8>ny869G0Nk*Qv)XKpQ<|8VWJ=AH z_OFD$R)~pNd^VphroP_uR$yY~4BB664DNqL5z^fg{H2G@demp325=jL$-L-On=e_U z!o(v8eDk|I2XGlHY#-q*mTDrTw1#%hZTHqcRvnMN&~BuDUH~z_D$J7v7c9O9Y7g@7 zFJ;s=XV2XpF0d9wyl-u>bN(LDn}yN&W(UjtX>pZ0s)2?V6|C_fRtA2^OIZuv(;o9?@<#Hg}WMlfPA?khiy{=R30Gf4obiGQ1^m z-aejy_>?YfMuo1Lo__QC6#+u{e)a1ZH`1uY2dS*Ar)I_^P9|0PKPSn_DI2$y#=)WC zKh$PLJ#REH-h${uCaa%U}!vp0@X5sJJemq(|Z^n zbW(w@PY2wKy9>tP(dhQyUmVh6X3AeC>A!mQ>g4P~tkP&`tiYDsh!bRC-ehdnG@&xDy%S_VKnb!Fo*PSckI*-$L)6XrK20f7iN*i#vV6X*=^C6y&t-;pcQ_2%Z4&&Ej#NanbqN zxnarH+}vWJ0(Rs5Q=?)u%P+F5$Z0twzrv~CsuF+iuW#NOd{)*&bi?uom$0Edj6DR3 zNUFU}Tl_#Gvv)fToyMQK(Gm6fP!k7LEv=@(N4LAI6g`LZuTgZ#$;meM_T6#1UJCUF z#|N7)EiEmRe>2uMEO*yhugI6_@W(Og>f~-vuiLJCzkkjo>u%&Z0Mx(D_JwKPVnaZ3 zrPo$q)=INI0uK+*V%+Ki0|Vfo|Ee12*Ws%a`Rf`Fb%Yr7+U|A00pdQ~GvQugc@zVA zdq^}}Jfgz*xy8e~4^}MOfKj^+F$j4ljLwD*8>!;4tfXAPR_oNnhiHe02m2TYCXoEfhR1jC*vFXgo@d zQY~()H=?;ShE)gqe_X@1ArOE=)tawXV$!F)?_OcOms7lX($dOJY+Ctq$rw;}s}OhT zuI1psmVOtO=N2Xi`wuiP$D!f;o8jWmG2e{7t@dU2Ld0l8oR4*yj$L~OlU)D;v0Ap- z7Wb!k|9)ewGYHg@ox8FiG!miw;k4W@tja-69KYD?o1BOVXUckuxht7X{$hB@$aI6o zxZL>Kiip=%>6W!U;^u&jz0GvAV=_po=Ur$bY8Zs8ga9kFE^=4*zW!Nr$4^g zS6*BB9WZB{p1OSJ`iuUrpk)>OxRcdFU_cnIlr$z~)*LDzQQ#rr1pEYyFuH9+Q*YXj zmwy4e8DtK<7YVn>wxo+X-~~qis7MvE@%_7!Q@-3hHK3q;zg8C!F^&+X-&5t3W8uYl8U+f&LP z1>qh}C`bYstx{*D0ClB|UejiM*0y+|Je^ihe^aunr^`n7L*OGH5rB>iqs+LB+DIgF z$ockmVI^ucMD{yFoxa}@PA~UWi@}#(EiGIQCq5d&l{YS9iAo>R4|TO-r21+7jL$E)DcTQCjW&yA|!w%bgL!|`RZ^E76M(UFU1;#^{qQ&<2=R%&K3;QK!i=0Lg(`N=$BXUbAH(z<2Q{)PEi}qVbXwiubV;o*_Gr^| zrTMy*R8_Bb#5{(&x_jQy(McZ#Tc}U=(Vt~g?_bveg3gyY@ZZ$$0b<+IV6KvnU~|qn z+k7wqqF1PE<<2U&sL`>p_L?+aWwgOo?`NIu$*HDZRinSW_ zL5}|>+m|2jGfPS$z*e$49uQ{Z6q6oW66sgTzsABE`=uliPQsce)Jw!{_mW1rz^9wrRLbdi zF#w;{xMOEb6b}!t&2GhOc7C>PuYnwgUI%8vEpO4>Aj{x{TKeLoCDSu$G#XW#^z9a|iP@RWa{G(UK=j_bh^)OmkZR)F zwSelTNyEKq9R?I7!|vYJUO=s|u*qZrCJHUy`ux;yuANoLdkG1m3IUpUd-k%rIxar1 z3qO&aVas{%(Hk6(BLM)yxih)= zGoAj16SxuLLwLQwioTIWrA2$ZyOwBudX%a{^ZDScA|e6fMzTI>8Vbmnucz^CMG#w79Ysc2XocP$IC5SKe&%q8qo!o1-Lw~ zq<_&Vza}P*aQr>9I#-$hS8@ab(c*F@(IH7M=XFM-${P`h=a=ZmkctEu@`Y&~@(UOK%vj;v9yAjd6-ea<1UF$;H)FO__0B=&~IRLkZzZV=8 z)hnticyo7?>6FHk#A?m3&8U_+E`qK9TlIst&<<-&l^VB&lQA*rzL{_LuwN80v9QpLA-n+LRsWolglF$74oPBjWucNu z@;4*FOk7Op1NJ&lanzb@$e?;7 z5Ge;dhKkMfx!Zs8Mkm;XqJOoJY?q2-$w%L`S zK%+1-F_A0@%__3p=*Rh`Tmvl^5%6t;c(roTKspTySuFS zyrDmik>RpD;kVuh!qG_gP*K%+9MV8VTo_oG)vw071;Uj*5rhU^e`BCDAV6ikz#L`IE5rdYzdxd}DM4r@3)^FTyn}}D(OZnrc7Z(>_$MPa3*C`4M3(I*N z@DR{Qw_)5!1w4Gdrx7nBAY^}zF9IYZ0tm6(=?`Oo`z0r56*c3<3MHX0(`bPq_&MH7 ztGkSGZ7(yW*fre)4;o^drnT^m-pIA-h?F|}dkz>_*putI%GbB^%45}PnNn*CAtiz@MOZp5d*(e8BAj%8pKp3pMAfGEfAIBh?Mgk5j%{8wc56UA@kz1dfOH zqXTu1o0AvF+t2W{``KEn2jksgi#r-)j_S*8kYs#(#Al+?b?e*8G=im@PV25>~ z+sLKHvv@V{vMsONFkR{AE!-QI-w8jn6}LXKoI44)KH!17?cyKt_C{DnCt2N&NC`$n z4+<%KB@P4-`*))QPNTl_*ZwF_J1W#;W@a{n z8#;5BKK=f)Y33bBFYS#6Nm<$LGx<Vc!0e(&h7h0q~IIr%&S9 zJu*y(-V?dv>qQ$AJ93M`ypER#Q^@Yu$0OJE$e$OT3v`-!sDMaOQ6{rclf+@kgPWsK z=Tgz3aLJjPI##2tS=eM8FeQkY!fa1vG?*F!+EFN%07_+WSVT9ox%tJv35o5{EBC9A zyNRm zyM!3)=_c0}ac@XgS`y7fh7(7ukID99$&tzM z-?{>0e0Zqy786jVi?v%xVtb-?TIg`|mhP{zM7ojc>~2}0D9-@Pz{J$l0Q3}JxrMvZ zo877TdeRnZW75(heswC}!*t+vqP{nVGQr#O4n25@TAE95xBgX|k2Xu714T|c%-UhkF(>IgxmC2?z+(0CIxnA}MJUqi%~$eaQvth3(pz zA)V$QJTEV=-T6Y+e*}b`Lb_nTky*FfyEveHCds$XMDLO+9n_eHvSvpuWUvVOr{lSJ zY%b-CEdOZsuenZFcm&a%n(Xcn`AEv-adi4h3Kqx=2MO6$Vb*|Rmvp{Q2bhFn?P>zY z{ppXtr%S>P7D1!Zc~znbZK18I#}74Y+uMiHzVsH471|rSEVf3OD&>7`&-$oV=Mpn+ zfRiJW#NtSkC!JPwwT~pixAhu>A8=9*agssN@lpE~6x)E?4Jyk4$lal z&PNtMqu(57zjwP z_07`_yh(3JadFh=691ASDw&_FeU-)m>X-WDN4$6`cmt_>qqW|GsWsIfL2I1#jy%^c zKI9`H-a!HMwwQZ&O(fKQ|Gr+OSPfIj>lO}*&bn+jqT`bM8=a5-qWlb*V&~}j^M-b` zK*4}cy9p0i(N4S{UV5Gt4{Bowijd|PJUGW%U{nY5dd<(z?ODFz&OH14H8svW|$z%coBn=G> zyA!IX5l4ZsSl}tjyMF6EHy#1LKk930ZzFLkT)e;5UO-T$C*t=CPhqiL=!Uy@I_{gP z2vrnw;UYRXsC(n{`t|E_y(Jz6t8!QSU)GsXfSuRxE>dw>-3m~8Z~3{d=N)Hin(fw^ z;b5AeoPN~+5V`ccpNN2t1_gmYKEmDQ1BN=aa>Vw3fUS+>;+*X-|WU-4A>_3w|Bblj(8CR{$TyB zwzxgcvR+4^`NPG_EeuF$Gso@49SBlB;NO4|w$?7M$g_g1GZ5;NHER}EF{}Wr8?fdU zh%KI)FO;TzK+^6IkHU{V7K*2F%~3uDx^Cmx)d!){A6;EyblO#TWMpJeeMjbJC=N|< zgi7lveZRqWkc*qBbn(THdHt9eOm4$BaswZbK#5^0Ql{1=cT(=&IIUX|e2e7&qHS_- zVw0g8E%&HbtDzU5uG@RA*m@!f z0ya0l5mnX%e&SbRAagb?H=eP2;In&)YjbgWB@U8LV+a57?NRiyt!;a%t9@8=x+}MK zE1Hng+m`A8I1Y=&U|_WbRAO!0&Y!=o#{pAR{fdAZ2b`gUlgi06 z0WmQjeAIyQ5I?M$`}qx&$u3H@|F^ei`}+fOfC%uL!b0NUwOLw2N>k3ZR7SkdbycU2 zZPyj(*IS#5K$CXzjtQg9o4lkO-0R7j=m%&~2E0rXzdaR$PQBuX-)y*nfMq>cXhxu& zDc;We*Rc3H_p^y5nX7Jjd3jqdGOo?_FmdkfO21;tTOG$~0CWx(D+vJ=M;G547u1M@ zJnB6*I@$)>bWCFWfVMUvJ^(@XXG*v7+T!7lzkgqIIpongT3)NE-=xzRqZu)-b~H!k z(rJwZ?bPnvZ&lkbm0cnCXlHf~{|I+QrKFGnFQQV^f6NO)=9fXfEX)0ek7I&;16dJw zp3Gq!I4+lGxr+j_Uh%nC#dTwntXY$ZjJi-H0?EzaFjg*nO~7W6ROTJPj$)&p4p zHRwMon3)3vEXC8ob#i$h5kZ@6z&9zWT>;FAf%p=SN2^+e6%OVF*kMJUpB}fi@g^oG z#imjtMT=5p1B;~hlh=1o4YVSIS7xl+Ll)zsfCy0(_CAgK z4X6!lUAQ+lsj(6w%LQ?m+ynVL#z-mCvxA8%(SN5z0Pwpr7BIW$<;ZMY2ImqJ5d z^t+A`YL5$7%jqS@;WbvqU{;}V+2R@Z*c7JvosQZOBqVfQ$>_;PxBcCdvYEl0tk0hi~uBXlp^@Kxt_ zJ7W{Sfq^Y>cd z)H0bbPVWC?_UQ2#9UblSs}mbx4oDYht1E=r?_kvz9x~G(``Qy-Q;4b3}lF5)WMA0a!wS;6hsNU_Gl{`1Jaqx)O-Lgjqdue7g+N)N#&v&(X;F1I`B4d zHvgt1%0MX>kU^5gKEoTQCrZQN!Q;z83W+?P;Y~{N`vip9&MN||=eI5l5?~p zt$28$cK43n+x;jP;puX&F&FmdectDW`J z_J53-0o@4Z(=DsfP&#Ervwb21l)qJa&VRkoO@p)y?y9PK)#Tf@MgaQcH5K## z2UK}=@X)#Q>l3(H;2QAKtcylYA7HiWc$vg$0=f4-$Pp0yMMidWvIYyRIp2YOMi@wL zSA)9D;EG?u!F`H*gA|}vi}ApVj^ z1amaGN;Q95`|tQ=`Rh|6R5EsxVbifW$x8>xS++}Ci1Z? z27-~5gP#34BO8eMTrQhTE_@_-25Qavl)&XaAfR(JVphC9VrC&tckMNm%)$V)Gy4S& zI<+QAvv{{tV|HiKK;}Rn9|#_>np^VTzS@*xHS|QO(0%5FJ*1zP^c==vRQH36sLg3+@d!;a5*8%7|R-)+~fc|G{FXOKr0EZ7OrYxn}^)%JTKnS*J+(-ItFue#t z0e4P`jH?IO5tehBbRN_1{VFsDm-Fm)`>8SM?YS7AWfc$arPR$q&9*+@9!imFc%9?$ z8u6u#v;Fs}U!5380YJGG8qwhoV|!Loht^O*j;BaDZsE#|qGx{w?M9KtRHv3mTwL6! zL`I#q-UY(r6@<>`J}j=;1%uU0VH;;h*o=|hh0M2N;^LXw?qk^E0QN(@LpviGYv*l* zQKbBS&#~{5m`vYQRaZ|57=e)<&;XnQ^$H>sLBKu+Bq!i6^8gO5nNpqZ$5ckh8yaOH zFbh)O^9a)121s{;j~7@}vOg&0&491yQ=97b63Iv2$8-GK=nwMI8~(5V+V zOz{E(&+M~7&`GcjAoo=-1-{LFgvSS#T(*sAQ|iHs?L4 zz=zwF;cXe;uf6`&ILO(0B};LuACK=FB4qWc`5p@4z)`dU@JDpA*0H!uM0>Rm^sX<;Z;Wi`M9(2NNB?~wf= zut^;9I`~XDN&t2V48!@9=}wH6>xss3(nW<|pzwL@A`E!m*E2fl>FI$EL8zPWkk0^x zy04GReVfi|sj1I0Dk9=z*cW=eOzX@OFQO}E6kvPpj4_y2oN?_8L_H3F_)jvNLbbhb;n7h>W-M=UZCFBqar-5%WSTI-pl)8@A*Qsuvk68qBRgCj`|kfTya!=4UI+@>K!t0|$ zUO+wf6ZvV$<`Y3jv=U0b$#EhA%T`+(1WZ85=zCo?)*fe;mGusxqJwV(Ln5=?MM0J; z*2lKxVKm*>Es~Z{^B6Fu#W|IK!$f?^Wo(Tus4EB^&jVHYW9y5@UqCGm+hK%m8N|HK z9sVI$lmw07!ijjzB2Gp!0AI{2eZS&R%9?q^97V$Yj@@4iP$c#{Bb|k@@9W=$*=aT@ z$7Mc&$T5;mi3C`9^=E9XGIzyh4n?Yx^trF4qHND+k~A>2FYlOAMtl{sInau45Zn zD|LE$5`e7akGynANwok854FtPQ~m=Lrp|Q(Uclc=2ux3Y)zkAGGw7u`kc{&OY`RvX z-JACI-HnG;Kxb2dcmHC4MOS}w6F37;HpdJ+?jBwNmr_tj zNa6TMYs1VekTceRFbf>=K`p)l($dmnnR>9$pE)etUup6D4&Zq@pBoApmI!DjMFY{| z`!B)NjW4vJw3NpHT(F*ng&#aBLFNkNcIV>a#0UHU3QB*r)C|M!a&wRd@&X+?$ke0 zr49}Fmbi2pBLSBEW!YG>J5fejq+I9+r5rr_ab!~1yL0-KDDphPh#Ax>2+~1M-^?!= zj9Gg`PI2dLT<$~XS&MXHy^~>{-^*bx4oV-nmTI-2JC#c41RpN{@NBK>!`s=P|S+ zmtg}eHUW-%6Se3+YoqxWa_MS}yF>hpaz@i?s=J3R7nt1!@eXF2k$p@G_P`VplkpLs z)hAsrfo1{bz@pOcXth2@$5|D@4@vG}ViA7_gOr(I>;b67>f=*nIg?=_k-{rF%}ij* zHU9hD$J}DLA+ML=%;6-=FnxX0b^AwORuu|!4rpa8JG_U$kdF%yvZ=^Pheo_sJgPEj?OI0!dk_{&B zXKUT%KB)#olfqOR_bZHsV}}5YZ!cWw@Bb1sN5JcZ%*^~uH69)r+T$a9rpiWUh#a9# zuL)If@LQik(-vyt{}vVb$Gri=Hg$*Ve&o{<^@}`|pFZU5S5r(DYT^PxE@*14QxSO} z`Z-~In(AQ&%7~ndWxHJLVkYKCWBd>V1EPUoDqgEvO9&&JOG z`}gmU(hl!08D5?hCVl#YDg2sx%M@oDG!bCI6%OaCZ67vYS!mUQ>6!i!a&g3%sCm{Y z=>5L}I0D)V13Q%laFhd6cD7N;&&Bb9eA)T0z=1+TsY78X3M?=CJ?2S-F|l^?M`PVVneff&XF95!FF99@B4Acf1g4w|T&`zp(j z>8;kIMAM0rg^mEVMl(n7B(N*TC#f(pEgPM=6u&B4BQ1xeEgWCxCmf&iD_d-R{>bs^b6Sk>ztOH98;zqwNI*k;PR=FV0Y}2sV3y23J)MLGxbx#=z+euT&@`JVjR4cJdUK&~MkleCsKA*C#pq|j{mxzXvP0J5$B2ZXr&Q>J{>1dHiN4Ac$we%;&28Rk^_P*8n|+Dawt$p zc)*m~r~g_TYFFr?JJE_qAlSiaF`!a^04W+<0fuf7Z(!p48yg$+hKtpq&PzZTY(%Ya zlCiSJE{yD)|3BS*_dAt;{QoJTNN5-drKHSI_K1)bAyPJ>?7dgnn`Ez$y~)bn+aVGi zD|^cx$M`(&_viXv*XM8e{#3ej?)%*LYdoKi^&$mn+1qj>`hALMHVsj*^?=vD;r#D< z=4%ArJT_CeQ`6F9elU7<>_8~UaeXZ|#F)U3j%8=&dF5DLFnS% zGR0$(wK?^k?uh7UUo~!X@JACnI-dXj0YjG79wY+K^c_t^!WS=O8=Yx?nU$NJ5}{xS z);S7kY3Z7|?d`3<%TDBtA|igFfxdE_)IpToS0I-~sWKT{{tjD<>>v$-V1|%+mN>o2 z>}*k6f&Hx}D222u+>=H1wXg@b`515BEHi3^GTk!>-?}O{;4?r1(P|YWH*W^y<(Vv2 z47M+_>mja?*!Fu2^o?_Sre5COcRt!o+E4H+U02BRO!Ch8;q^n) z#t<#g-Hfkaw$WZ| zlQAVGB=oG#Woi=g7LidS^n4-vH8+>3lt#wdnu5|+q<*8c?qDeI&9xK!%PpS{x3$YK zaQBbjMd5n5;T$y=McmO8aR2DKx5wkY$DE^*|H+n$3x(2jsux@6li<*rL26}y3*?do znM!*KZv%1zd{*nhrE5qZoPDBCSq*;0^{VQ-_joQ?78myqnEFh(9(@IE9S7z?@CZE_ zDa}U>etYeTvL{AH9qxi;+-(8rnVHRQh14)V+nn9L7`RBvNq@@C&Fyje^4057#C@xJ zd_b4SF%=x$+$JMo)hAV7qbDXDj%FX$xng~W^VJZ~BA62K%&C`_mPWq2;iwd9cmj3Z zWs(clmW!9JG#0G!7r6~eJYbSR-#RJen5-hKUpl!To?Cnm$PT#MnL|#tckc|oKEubE zpsY-x6>ggOh)+k?l#;4Q3T`!UuLbl zWz5ZMcHI0IvmSt{gt+aQENBhcDySE_qr;=48H!neGXs;VPxIysfuIm94)rJq`+0qZ zny2B1Etf8=eg=T_toBa2m)3zRnZ^CI)k{>Tc=_n~kdq2n3AK7V>cGH2Jt=?x9)pS} zE5oCHxcKH zV8D3XN6>9YGQ~}3XXo8`wT=GkLUkR$)S2(!2T$*Jzp}Fzp@%hF;n)K*HuVmhvhSl` z-A?ApNwK?3RQyJdF;%AgSh%5U7ccZoqC;O$aEA~r3wyM-wULdY zWbf~Zvq355Z7Q+SeL3A~$TWbYs*%(~!b-PtlZ@{B@-ZE-0y$#98)yI!=H~;d@}H)C zth^fUl7(D#tE#Hh-+X-900X}mEdIsAy=oS7*qzl@8Yia`0Q<9#*x)zo%Zz$MczKG5 zHzrFVT&bv>MXlJ{uJi}Y^H_FQ)jwwuGQc5hP$2fG)+$F0USsPDYL z`qwn=u`an@5J)OZ1c$luUIa$TzLtqcA{#wfZe^h8s_G^(v4ZP`~_;JL?kuVHHD zq&bCVFKwxgWInQM9gQtB9Sz9MwGW+)1y~|5An1z`*0saA989tP<%V!C6LpTwAm8}* zXQarCnsZgVVN6})4ZMD2=&b0DNX=Inn3l!Ah~<%5-&X5~uRU^9wsM_SYa$UE_a&nb zoW?S_SAPSY9}C~U$6@L=mvaWCGwoJVg?iHM(0q-uxZVV=L|k0F5&;n!xc<9%Zj*P^ zm@-4QB;jaGNPRMG{1d(V$Y6Con)PpD|5~}=26TVCkbc0B^FaWAew_KdYYy#r@adE$bpbG}N6C{Em!> zXvW)JK|9&4^lfcz-P;T1qV5dhyIBGop#K_3Uh_=5RZQke=B{EQK7WqUgnI1HZ0A)> zT575)G%2HQSl88N>^}3IJ0D>h_*FYR={IkOwRFr45AQ+|8DH;6Af{c zf-|OU4<7jacfI42iXI{4>ew)?16_V5CYkA`(IQuTva7e~N()wA7h38Qo}C=#X;yt^ za)&`tAuDNd(RjQDSFPA4wq@@L)9d1>A3vBI0B~@2a}%QC8;MEevn=V@BYX>DTeU~_ zn#Fhq$mBky;Eck${`2_Afj_~Or`8UGCGc0~&wqc~ePJ~|Lt`TK;X#_T4SlG=c5&E= zONb$}+hnuJd$p}>rDwc17+$9yy#58vG!Tnj-Tfl5sw?}rNU&$b3t1G5WhTD9vD9PM z3&K6XmP*Yq6hY95wQ0xA$4ps|XPS?m7*~~epS5{k@)kvMR|*6swfXddGO+}-NjYD`}wK8}487te{U zwNn%ZxS3!-mPEAU%>D6u3CT;96Sdv2mNEX~v$nEQWx&BfR*mkyzNirfNYD9tild5& z$`43ry=^x(5RQDunUD}gth(8x0gP{xh4Qtln1!CWR%G(mC-3-z?al1Jkq;?EW97p1D`P@uxTf!c#u8x5ZS) ztA%?SAe#$ZgTpYC8P@nGzlXye&xIu>_GY|D>fu+K9vdx1)*{>f!RSBS5Y@x=$L5}S zBJJ6s@4k5h1+wEn>dxe7i*Q@Fi);`(vMTHgY-@}>Y8z>C+^*sPLFDH*(P$>xf-xFn zs;V`PwwRNNT6M<_ayEzV)$;GNwHI>d8t=>7o2|X54=3*1OaE zZa%vg$sCdPdzjyBTB`Vhu4d&*BI%^neTlryX6n^_P=(0I$*H{W&#Pn~#$;vL`UKf~uJ{3yg+!4c&%+M1D%mIJ zPET9oilNJ#{VhRwjzm$;LD1RfTgyFgxC6|n{COnv+cz+@9^GYNzz6TFQdS+N!t5OC zh?wF*S)rD@0&4<4E30}5282=qjyn{)pz7?!`RP7|LV0xOV^Cs;o<}=tfmiVG6!qgP z-hVs-_z<}H_x(@#Ho{fGz^#d`@yW?yj5GQmwYKr6j0puW*fYANgSOuI{?U$$s7e>L zEyOI~@!<6(TBuSVwSkeMv09js<7)13o;V;6>3b)q&hn_IX>Gyx$`=;q5UJ&d>}(Fq z1u?X<4t8~o6tE0O!uf6oF*-&@@yztq@l4|VGxyWO+okHQ0m93Q-zGlkzl%Iu@XV@R zev%_&LcYLsw9xEqrUq=~-gNTN7hl4$lXV93lBlr{xVyUK3l*P2pWqj*J@Q)X4b&O>VIpP3imQ1@m7%btHlex zpR5!7;UdgH$k;uy3snnJ(&@DaM=+{EzablYziZ&tzcQ_x#e}H)@`(h43U^adQ$GRu z@f8%oR^8;>ZRbLw!netKSi}(-%I7pVN65((r~=dCIh$GpI0)e2?EK&?mqR& z!e1gI2UL~#MqOaoA@0|f)>cdZc)@kyr<$5Hab1!H%rrwzi%b4vW9qL!J`0Af1(x{u z_`ol-@4!nYVfM*x^Q*KnIK-A8HHCkjG%3jN;P z!oo}$+W5QCxU_-pDTE>dbA1{ZSy`!yYFOp9rY~p8eekR{jt@Ag3z>xE-$J(c_V7^I zfzjXsgK`3C@1@oIqh2*G)Jp`%32SIs4i((}(SYvh`7*+)SynQIYGB8I{`kYs3!cHj zGDE{7!lwuc`PUPlzWj{c;Ih;1;>XfV^le^V%2T(~qQ~6r!)usIu{~lz!Ae37b~HZC zNS!ScGz)sGSp#G8$w{@^0v5LcgOnaZ=k>^f6SixNjpSm{hh{wWTC4P-W#$LM!dkk@ zl^`K}XK2_hI1l=-j?T`;7~yaD86G9U8jk1BMHQ&Bme!SpHfc7zbz)0%q~d;$_&-DKt=7is=LZ*Y%kO zf<lP!q563FqsFARnlYeF=%r{j>OAh6EXl$q8 zo}cT%_{!*wVSOTL)82{;DAdu~Y~fLQ7VM`MXTKrlHInZJebU3l1*Su%O9a=6JnWW! z;(9FVb`Si000pG#botJ%xM!Tb#`p`s2b1r08jn@%=XTYY1Oq_v{rKSYB%&BYFX>3U+ghy;Jw zc_!xP4|SzO6Yaf#L%hDP56Hrnmw$}b`{cI8J-QRJ_b@d;WIZr6aU2v3OriwP6)#O5%H8~p>!HRf#KpDlj4igp6si`}F{_GU_!Fa62HebKXzY zV|a!bp12FNeBo)4v1$ex^v2y0W}{UhBU3H6!rixLyCmp3;4u}rfCj%2>$dsJ35ayn z>FB@no@HiCNleV)f@ZDDB@_gqw%71l|LfSkW1pJ?D^F3$1dHWL84T~d{Socqpjd=K zHjs<=8MR(`TH$D|UNhyjEhl%iv=B@f1?qPHMX}(3LX*dJ>pJ&EWk_~~bO;Jg!_GST zlJ9*dV7bv=U*%Q(q07tNq0*nnYuyhVB}gqBAn8u@*|YF3F&AJ{7I597XqQhs%04Fz z@Xpy;fA#r0b58+a=H~yFO5m=U@QHSMdS-e~(;=W zC{D^58oZkZ1@u3csgEE$^I*q|5b-QBIkW--Rn@>X7__did$+W-OeMurfmGpMF_QNz z$U@W*s^%zd1aYqY!^4fAy$MOdEAxx#^)$DjU;>Y80Qc)yW-zTez$T4!kQIJA%M&=| zzu)Dp)VWY1?l_Rq7I!y&vTq1tN0bMsr}5>c(#NVwii%TKD`X&J z@B>K1=J8`M??`&?O2sen+y&O5FQPg+{ODE`6cw8~IWsNyb|^ILeqHFb#s3k zANAOLB7xiRwWFU#3D}s_ZR-%jM~ZQUS&hE*&1&gsLl~4tqX*jhW*=`pnVamQq5A6GPAQU0S%8p z-b^a_;(BZpDAdgavYc`g$&8h z*{FlJE+#Incl7094VM4rO`8m;A!uI4e5txx*JlGz$*uAthJG|?fDW(BK z`$DH)<>~CD2CvIUYkv@HIxJRU$xXVU@qfI=sBRFO`)M(Ex~(yu-ps zfOpph|6ZltHl7=kO<=@-JDj~JsGW~bj-kZo22T5&rY2fj-!zy}ImbdjjqYyM7=ZUB z_+$q`6nvLuAGB1ohyjJ{$&NR3=RTOFY5tTMGn|#Zl!Qs3!|~2k-c>YX??HdAyg7vYi<-s4r$Iz|cE=B+p{zUG<*PwKmsc^y_%bWw zW2WUO$h&wAQqZSr&+!pzBMZ&Q$oTTL(sK}GeL9a5DENNfX@QlMPii6KRm-=BN&$(v z?69%8oBj#9+kPu0FHe2voR!Ndv-I=ZNrEc%A0M!(gPUHv){t1p^)eFVv%3H1@3DC2 zn?a*xBM53@GWwkz%n(X*7d~W0BD8HwnF(lB=lob5fQDw?XD6SV-=!)VDI;7+ zU`0Y5ENZRE-OVO~pnLC06r`%oH`V4ddKbP6+LEUru0j>r9CE;PZ7oTcfvFLSP%q1i z9g)B^HbGV~kT-p-FA2^{I_Uf@re(}k}mEp=QlInvC`1s9cp)4UZ)?b`gcF`>b(?_us z*it}VY*r>TbS17PgYId%*?BbAl5x5trTP6p2|c$l95Zp9YPbLg5wJAs{$>T;8})n? z8A0trPIfl3e)!Y-5d&++F}V@gRKa1RP=2~g4racdo+%66b;Acx3$!AAQjY91XM_-kbtvFCx>ur2Ne6Y15f=c#hX{rpdZ)~f9S{u~@d z=0sy^|DDEV_?DjDxH8H)A<~j8xF76$teT*|vS-u7&glt=7qa0%?f0IEp)e&N zfPguVnRJ`?Ce*=GOMj(cB$9~o05<4%897Vf%ZFn}3uw^)V%PI~PFmAbQ_q8}5*#Ix z>e^)YNRCGe6k2d29m+Zc&be!N?6kh=)xDK=XF>M{%Es2#a&eI~h)NI-LINOe>{Ari znYSlgaBpO-)#D@1CY&OHbYFbEHjmydzPv~8&pN_SMS1c{C?2sQ!lqGFR5EKI8FQKX zyr8RK4_CZ*e<&?PwmDJQJbti;8?s4qE$^b%rTFpTi~oqH$~oJLv?tVC+S>l^RF%F| zRQx2tLUfDA<>K=z)J*{K0qjE-H9tS1uXFCHRfA}L!`i)oN01+xlaqt|0rfdjvjX=Q zW6DJsUC1LdmZV5?(OXn?{f~F6OmwQ#?lQ^)62zyewpv`Qc}88aiKhQU0pLO7Hvj0udW*n=lMepzicwcjauy9$0f;uXerY_-#>2e z8mpGM;37n0)8Y2wyCKTEvM;K*P^ahiGTy#bXT-BKYi*LA86$`IAm17PYm2{gxgn2- zbc*)2eZQ>9)=>68r+M#~7_@j4y%)*W%GY0c4^Zaius_Y$WD=DUUdipnfqiRZ_2_SG>ZCC4w*06GL?sqV_2DUapR_0zEu zTxudBz#?f-9WQ9|E^a6CD}bLeh~gn{M4r@OMy16P-SSYjC-iv<{LTepyMgBBO019W zwvP7_6}oEb^^6oNR(+jkQ};{nj>bVjD!$jwjtweIRcF=Epuz-Gha>0)GV6<12%2Dx z3sf)r=e`C_ZAVX6(`mqo-%qG_%-1&!39`J8E732s&6Z z++hNOr1ojHUqd-j$1;5X+c&@|M{`~WQAp_H#7T0!`RjZ8Di!PFUvqK-Ky8|h$!|#| z{Pp@3c6tr+UVY5A=f(F#q@*tW=#5Xyd8?eQCVJg+`-rFoBBcP`UHn}?D>cr^!SN66 z>)!wE!?lSUn}~KLok=_*z=0-DDtag7uhS9~-oq>k)6gD# z|1e0FktVTq>Su^4e?NK^{;pf(FGX5*j^(Xih(C)3p}q)A@~mUmC`~xw;oPyqnNj2R zsWR9cOh)U7Q2&;&&e0(uSEB9LbA#-Le&tGiR~SJjVO?50wF4&R8OF$anbH_M;4;zG7)1Xav>$CTA@Z1`~cPWo*CcL_)XoyP?*L%D-~x zIOsmv4~rb^I~}02!b9LKm%g*o5j z`F$3k<+r?bvrLTK4+`dX@9hQmoW7q{W1AFUmDEw6`Xn#VobivDWOJ53rDzdDr1V)F z{iam01LxJ-E9=4@Fv2c#KVcbQu?Sk=>Q2rt7( z*w*a@2oPZ@Ax;&FX143xHg6gE%!uA>)Q#pfh>1h*<`}&uN zi9Msr$@nNJ9=sB2KodtYy>8QyrETdw1NF~}# zd!7YHgdFWf)O;m4IUohyUyCs9y$IGo^IV6;!0>HUWXKE|ByGGm>Z-2M_J{N@s zM)IJd2vJ|U`tJFi94XHmg(eFH)Uj-gR+Dd>f@%W5{?D<53q9F`6gozX#bF)}wp<9c z77EtP!A$x0lPNg%OsY^}RGL@L%*;53GG$FwY)-2Q5E?=5PzVptQ&{FuMW)?kV1oHB zW_x1dSPZQk4~-yWnXV?JdZ19G;h)KEr(UNdK{b=Ws$4-C+uJw%@ zG!(h&x48!){!%)i(5Nzsq`T46mwsZkq-xRucVVkTcdW1EE*c;!q zWiv(J_;B+MgrYc`-n4mTf|{#egcnm(Uh?_ir}!IjN~O#fxTvvO=hw?I_U7WHI|k_3 zZd0b^6DBZ~0LZ#HUd>%$delRSLjA5TP@DromgLqgFR;6IxyrMxSJ7U*`f2Lx?TH^S z12?z4xv$*fySJ%76ww~)SpTI90Q&U6=%T?fbt9h-FSTz+LC!8{th-kEp(#GAN7M>d zslb&zswe^QR7x=nP)_8GwyJdRp`OpR&L);y2bQbF<>4b{0xcJw`4TmQu!Izj3@}A^ zbTp)R`f3lQ6M}^Dj%81PGE)ka_i9vB@I0(Bo)8EC5`lRZRg|EsE2;qsS0wxv(ojCN zvReBG@_<_Y7G#*}Jjaji^gR@CIv{BddJz^I{*mc-p|zvZ?4^JE_$a8`x_Z1zTo+go zl^5X3;WrPYWo5m9l#Stcr*XPbiF(lSw}TXl6kJ!g4S%M-zOlXI8kg0IN#BkR$r!aa z(zga7KPM=e=UfHAEi2SZgpqnzLSQa>jSD&ZuerO_YoB6#&oEBK5UT3snl=XP+cvbKB|6ydcJ0755oVJ=R#++ur)>}-JiQV8}JCNnbG;*$4W=O_)BG{1|XVZ0V9!^JD0=P5TIMfTPn_IO+CHr%5NG-STV&AFe` zy9`RjmVxHy-|+0`dlK=1UbJj*qor$dzbruDEeb$MP>7EYg_iasqrMu){=whA!MkRx zt{$pD$01*&Pa?5u2oFqc{UjK>^#;(}Sgg#y@Ym~0;3KkqVw~w=Z^VmU!3MF$zaM&YrQ+s|-bw-Vu`q#HFdZ33%eHp_RS;?RHRc^@FFy zW_gIRv9a;uzwPFu7x9&|y|^0HPB*Gqn2P}zhNx3bT`f0Fp}kQI&`d-~!Y$$=1I+O< zzkS;eq7}#2R5Fs19)}@`p#SoLqrt9TiFi&?>2z>1iC;8ZjgE=#c@|R^Kw=x9+5sW4PiOy~ z8qQa!fuI=RZ!E}DUX*?B&wIm(P*xF{a^bv{AOb;`ya(jquIHG}7*vb?iDd*aqReM|t2%tT4H+@&g*%JatC0uWjJY z3i0!=mR=+3f5^myxce=@e~VpRCB1e10yt;ZQ)y8U9ONsP82qT&TlK=fK&!^y#bZat(JA!k;2!Y?w-PUsNWWm;d0drkdslm*+nnK7|peVIxvj$YZ zJCNqtZ?wfKWg;Dg$+2~5%jDWttISA^6)SugHYgN=VN!BkH~w9?aHMN!90(G-=HZDe&`RkG4VU<70Et6k zV4^tN!vrvhfcsJ~lnCi$)pU31ELsl<>Re?ST=$(NWv}P&3EIT zgNr@h-~Lcvi%`*B9zFg-jF@RVDWoA zasP0CcefQt*K=+CRe_{Hl0N;FV+1XAW3O05E7VY+FqtlD9-z~cax~B@qk4XYw(S^~ zu*hlrZT5*8*BNpG=q0O2gXR#kb=tD#s0+i6qQbao>O_D;m#+}G*PQXk+ zFk|^^loI$ziCiwQV2Aqy`+HDpC@-7H6}i7>5sj(PfycQ%?u@`y|6KR409!DkSMgf6 z5%nfuyGnl}NswpV0f~eIUj+w@Dq?uRHjfrPz-(n%TG=|`Thk7-!{*W;!-IzpQy_@$ z{a7VCyj<<|nnMo5MrwFyW->BJ?kPA9QsxRIDxok+ z5Ol?Z1MZ{~4D$t!w&n;S8L0nHVCgXML*7CQ#Grd-JZPY9W7Xs;k9N16Yy7Mo zbcashd`{#mde)oU+uNi^?AHN+aj-T_Pfx$Y$9EIbzZDluq*j>yL7%X*zR8bAMb&I} zR!7$zZM&*Cgl;QHPJbVCqU-{s2f(I^ou`D?+7)~6zksl)UAh(hphnPO&b~8jp4m*( zI(6*p?G=aB$*QDu;M)f!#1Crbh$Jjj@_@@QO{CdJeWElq#Q*S^Qc9{bYx1j`|FlQy zLfy*;V(-N6UHGK#ttSR<9$=upj((F057lDk<&9ON@J93>$cwo1EkR07&S$A#XHi=l zg~h>k|KOjSfVcqmB6Hy$txA+kG#lFyO3?%kSn`bYrdL{`t zflaqC;{1>lfnMq2ow+bz_COisEAzVGX+5=j$X&V;qgDbJ8Oa9!!OXe7sk_34e`^jq zy010-J=iQ+C%8k<+1=gT;C8}pH^ze`wC7bn1LimE?Dmfzx4puwvNW)E2i7ZOChGNd zs<}$1Ml_H(2Po&q{8~Yxn3HZ$&Xa<=oa%Q$wC(GQ3Vvn3%)Y-LxJ5|L#<)^|SZHR2 z0}37?Up0>B!&Qih4um*O>p*#iVsxA>f1&C{{{=g|;PK@KT?*e($hr`uVQ4g}8kylTY)#6mwDOvU3k z{}>i{HTXb%q#6T)un9Kge-H~+vVP@G4KSGg;MUdF(Z6)#MkMl2@S&&84>JGnNypy( svj4{&Buen#75~>^1Hb+M;g5&Hnqd(+74)Hy4va!ciOY! literal 0 HcmV?d00001 diff --git a/src/humidity/absolute.clj b/src/humidity/absolute.clj new file mode 100644 index 0000000..4bd1367 --- /dev/null +++ b/src/humidity/absolute.clj @@ -0,0 +1,38 @@ +(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 + (:require + [humidity.constants :refer [Rw]])) + +;; Absolute Humidity (grams/m3) = 6.112 × e^[(17.67 × T)/(T+243.5)] × rh × 2.1674 +;; ----- +;; (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 + 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 +;; `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] +;; (when (empty? (filter false? (map number? [temperature relative-humidity]))) +;; (let [T temperature +;; RHP relative-humidity] +;; (/ (* (C 1) (pow (* (/ (C 2) T) (+ T (C 3))) e) RHP (C 4)) +;; (+ celsius-offset temperature))))) + +;; AH=(RH × Ps)​​/(Rw ​× T × 100) + +(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))) + ) + diff --git a/src/humidity/constants.clj b/src/humidity/constants.clj new file mode 100644 index 0000000..94bcead --- /dev/null +++ b/src/humidity/constants.clj @@ -0,0 +1,22 @@ +(ns humidity.constants) + + +(def ^:const celsius-offset + "Value of 0° Celsius in ° Kelvin" + 273.15) + +(def ^:const e + "Base of natural logarithms" + 2.71828) + +(def ^:const Pc + "Critical pressure for water, in MPa (Megapascal)" + 22.064) + +(def ^:const Rw + "Specific gas constant for water vapour (J/(kg⋅K))" + 461.5) + +(def ^:const Tc + "Critical temperature for water, ° Kelvin" + 647.096) \ No newline at end of file diff --git a/src/humidity/core.clj b/src/humidity/core.clj new file mode 100644 index 0000000..893bd93 --- /dev/null +++ b/src/humidity/core.clj @@ -0,0 +1,49 @@ +(ns humidity.core + (:require [humidity.absolute :refer [abs-humidity]] + [humidity.constants :refer [celsius-offset]] + [humidity.saturation-vp :as saturation-vp] + [humidity.utils :refer [?assoc]] + [humidity.wet-bulb :refer [wet-bulb-temperature]])) + +(defn resolve-humidity + "Resolve humidity equations given the data in this packet." + [& {:keys [temperature temperature-celsius temperature-kelvin relative-humidity + pressure-kilopascals pressure-millibars] :as packet}] + (println packet) + (if temperature (resolve-humidity (assoc (dissoc packet :temperature) :temperature-celsius temperature)) + (?assoc packet + :temperature-celsius (when + (and temperature-kelvin (not temperature-celsius)) + (- temperature-kelvin celsius-offset)) + :temperature-kelvin (when + (and temperature-celsius (not temperature-kelvin)) + (+ temperature-celsius celsius-offset)) + :wet-bulb-temperature (when + (and temperature-celsius relative-humidity) + (wet-bulb-temperature temperature-celsius relative-humidity)) + :absolute-humidity (when (and temperature-celsius relative-humidity) + (abs-humidity temperature-celsius relative-humidity)) + :pressure-kilopascals (when-not pressure-kilopascals + (if pressure-millibars (/ pressure-millibars 10) 100)) + :pressure-millibars (when-not pressure-millibars + (if pressure-kilopascals (* pressure-kilopascals 10) 1000)) + :saturation-pressure (saturation-vp/saturation-vp packet)))) + + +;; the example in the paper gives this as 13.7 +(wet-bulb-temperature 20 50) +;; by definition, this must equal 35 +(wet-bulb-temperature 35 100) +;; from published graphs, I interpolatethis in the range 33...35 +(wet-bulb-temperature 40 70) + +;; (resolve-humidity {:relative-humidity 100 :temperature 35}) + +(defn resolution + "Repeatedly run `resolve-humidity`, q.v., until we can infer no further + information." + [packet] + (loop [p packet] + (let [p' (resolve-humidity p)] + (if (= (keys p) (keys p')) p + (recur p'))))) \ No newline at end of file diff --git a/src/humidity/relative.clj b/src/humidity/relative.clj new file mode 100644 index 0000000..b45c850 --- /dev/null +++ b/src/humidity/relative.clj @@ -0,0 +1 @@ +(ns humidity.relative) \ No newline at end of file diff --git a/src/humidity/saturation_vp.clj b/src/humidity/saturation_vp.clj new file mode 100644 index 0000000..b7dd8d4 --- /dev/null +++ b/src/humidity/saturation_vp.clj @@ -0,0 +1,25 @@ +(ns ^{:doc "Saturation vapour pressure function" + :authority "https://en.wikipedia.org/wiki/Tetens_equation" + :author "simon"} + humidity.saturation-vp + (: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' + formula, given temerature in ° Celsius." + [temperature-celsius] + (let [a 6.1078 ;; yet more magic numbers... + b 17.27 + c 237.3] + (* a (pow e (/ (* b temperature-celsius) (+ c temperature-celsius)))))) + +(defn saturation-vp + "Calculate and return saturation vapour pressure, is possible, + given the data in this packet." + [packet] + (let [Tc (packet :temperature-celsius) + Tk (packet :temperature-kelvin)] + (cond Tc (saturation-vp-tetens Tc) + Tk (saturation-vp-tetens (- Tk celsius-offset))))) diff --git a/src/humidity/utils.clj b/src/humidity/utils.clj new file mode 100644 index 0000000..1fef5ca --- /dev/null +++ b/src/humidity/utils.clj @@ -0,0 +1,27 @@ +(ns ^{:doc "Utility functions for humidity calculation"} humidity.utils + (:require [clojure.math :refer [pow]])) + +(def ^:const celsius-offset + "Value of 0° Celsius in ° Kelvin" + 273.15) + +(def ^:const e + "Base of natural logarithms" + 2.71828) + + +(defmacro expt + "Clojure.math `pow` function does not allow ratios as exponents; + this is equivalent but coerces the exponent argument to float." + [base exponent] + `(pow ~base (float ~exponent))) + +(defn ?assoc + "Same as assoc, but skip the assoc if v is nil. + Borrowed from https://stackoverflow.com/questions/16356888/assoc-if-in-clojure" + [m & kvs] + (->> kvs + (partition 2) + (filter second) + (map vec) + (into m))) \ No newline at end of file diff --git a/src/humidity/wet_bulb.clj b/src/humidity/wet_bulb.clj new file mode 100644 index 0000000..5443d0a --- /dev/null +++ b/src/humidity/wet_bulb.clj @@ -0,0 +1,26 @@ +(ns ^{:doc "Function to calculate wet bulb temperature (in ° C) given temperature in + ° C and relative-humidity percentage (0...100)"} + humidity.wet-bulb + (:require [clojure.math :refer [atan]] + [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 0.151977 8.313659 1.676331 0.00391838 0.023101 4.686035]) + +(def C magic-numbers) + +(defn wet-bulb-temperature + "Returns wet bulb temperature (in ° C) given this `temperature` in + ° C and `relative-humidity` percentage (0...100)" + [temperature relative-humidity] + (let [T temperature + RHP relative-humidity] + (+ (* T (atan (* (C 1) (expt (+ RHP (C 2)) 1/2)))) + (atan (+ T RHP)) + (- 0 (atan (- RHP (C 3)))) + (* (C 4) (expt RHP 3/2) (atan (* (C 5) RHP))) + (- 0 (C 6))))) diff --git a/test/humidity/wet_bulb_test.clj b/test/humidity/wet_bulb_test.clj new file mode 100644 index 0000000..6dc802b --- /dev/null +++ b/test/humidity/wet_bulb_test.clj @@ -0,0 +1,47 @@ +(ns humidity.wet-bulb-test + (:require [clojure.test :refer [deftest is testing]] + [humidity.wet-bulb :refer [wet-bulb-temperature]])) + + +(deftest wet-bulb-test + (testing "Example from paper" + (let [expected 13.7 + temperature 20 + rel-humidity 50 + actual (wet-bulb-temperature temperature rel-humidity)] + (println (format "Expected: %s; actual: %s" expected actual)) + (is (< (abs (- expected actual)) 0.001)))) + + (testing "Interpolations from graph" + (let [min-expected 31 + max-expected 33 + temperature 37 + rel-humidity 70 + actual (wet-bulb-temperature temperature rel-humidity)] + (println (format "Expected: %s...%s; actual: %s" min-expected max-expected actual)) + (is (< min-expected actual max-expected))) + + (let [min-expected 34 + max-expected 36 + temperature 39.7 + rel-humidity 70 + actual (wet-bulb-temperature temperature rel-humidity)] + (println (format "Expected: %s...%s; actual: %s" min-expected max-expected actual)) + (is (< min-expected actual max-expected))) + + + (let [min-expected 31 + max-expected 34 + temperature 40 + rel-humidity 60 + actual (wet-bulb-temperature temperature rel-humidity)] + (println (format "Expected: %s...%s; actual: %s" min-expected max-expected actual)) + (is (< min-expected actual max-expected))) + + (let [min-expected 32 + max-expected 35 + temperature 51 + rel-humidity 30 + actual (wet-bulb-temperature temperature rel-humidity)] + (println (format "Expected: %s...%s; actual: %s" min-expected max-expected actual)) + (is (< min-expected actual max-expected))))) \ No newline at end of file