Skip to content

Commit cb37129

Browse files
kurt-rheeKurt RheeadriesseKurt Rheekandersolar
authored
Calculate Relative Humidity via Magnus Tetens Equation (#2286)
* changed default types to float in solarposition.py * switched to floats in the docstrings and removed type hints * added magnus_tetens equations * line too long in solarposition.py * revert solarposition.py to pre-PR state * set default magnus coefficients per conversation in #1744 * moved equations to atmosphere.py, updated function names to suggested, changed type hints in docstring, changed to suggested coefficient format * moved tests to atmosphere.py tests * changed reference to WMO * dry-bult temperature in the docstring * revert pyproject.toml * remove uv.lock * Update pvlib/atmosphere.py Co-authored-by: Anton Driesse <[email protected]> * fixing flake8 errors for tdew/rh functions I have left the formatting of functions outside of the tdew and rh conversion functions. I can format them to satisfy flake8 linter if that is desired by maintainers. Just let me know how I can help. * Update pvlib/atmosphere.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Kevin Anderson <[email protected]> * added some unit tests for different coefficients and input types * refactored tests, removed magnus_tetens, updated whatsnew * reference and whatsnew * revert line 36 (linter) * Update pvlib/atmosphere.py Co-authored-by: Anton Driesse <[email protected]> * Update docs/sphinx/source/whatsnew/v0.11.2.rst Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/tests/test_atmosphere.py Co-authored-by: Kevin Anderson <[email protected]> * Update pyproject.toml Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Adam R. Jensen <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Adam R. Jensen <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Adam R. Jensen <[email protected]> * Update pvlib/atmosphere.py Co-authored-by: Adam R. Jensen <[email protected]> * added a round-trip test for magnus tetens Added a new test to show that you can calculate relative humidity from dewpoint and then calculate dewpoint from relative humidity and it will be the same as the original dewpoint values * temperature -> temp_air; dewpoint -> temp_dew * miscellaneous other cleanup * tests: put assertions next to calculations * linter --------- Co-authored-by: Kurt Rhee <[email protected]> Co-authored-by: Anton Driesse <[email protected]> Co-authored-by: Kurt Rhee <[email protected]> Co-authored-by: Kevin Anderson <[email protected]> Co-authored-by: Adam R. Jensen <[email protected]>
1 parent c12a477 commit cb37129

File tree

4 files changed

+231
-0
lines changed

4 files changed

+231
-0
lines changed

docs/sphinx/source/reference/airmass_atmospheric.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Airmass and atmospheric models
1111
atmosphere.get_relative_airmass
1212
atmosphere.pres2alt
1313
atmosphere.alt2pres
14+
atmosphere.tdew_from_rh
15+
atmosphere.rh_from_tdew
1416
atmosphere.gueymard94_pw
1517
atmosphere.first_solar_spectral_correction
1618
atmosphere.bird_hulstrom80_aod_bb

docs/sphinx/source/whatsnew/v0.11.2.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Deprecations
1313

1414
Enhancements
1515
~~~~~~~~~~~~
16+
* :py:func:`~pvlib.atmosphere.rh_from_tdew` and :py:func:`~pvlib.atmosphere.tdew_from_rh`
17+
added. (:issue:`1744`, :pull:`2286`)
1618
* :py:func:`~pvlib.ivtools.sdm.fit_desoto` now allows input of initial
1719
parameter guesses. (:issue:`1014`, :pull:`2291`)
1820

@@ -25,6 +27,7 @@ Bug Fixes
2527
* Changed ``dni_extra`` to a required parameter in :py:func:`pvlib.irradiance.ghi_from_poa_driesse_2023`
2628
(:issue:`2279` :pull:`2331`)
2729

30+
2831
Bug fixes
2932
~~~~~~~~~
3033
* :py:func:`~pvlib.spa.julian_day_dt` now accounts for the 10 day difference

pvlib/atmosphere.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,85 @@ def gueymard94_pw(temp_air, relative_humidity):
337337
return pw
338338

339339

340+
def rh_from_tdew(temp_air, temp_dew, coeff=(6.112, 17.62, 243.12)):
341+
"""
342+
Calculate relative humidity from dewpoint temperature using the Magnus
343+
equation.
344+
345+
Parameters
346+
----------
347+
temp_air : numeric
348+
Air temperature (dry-bulb temperature). [°C]
349+
temp_dew : numeric
350+
Dew-point temperature. [°C]
351+
coeff : tuple, default (6.112, 17.62, 243.12)
352+
Magnus equation coefficients (A, B, C). The default values are those
353+
recommended by the WMO [1]_.
354+
355+
Returns
356+
-------
357+
numeric
358+
Relative humidity (0.0-100.0). [%]
359+
360+
References
361+
----------
362+
.. [1] "Guide to Instruments and Methods of Observation",
363+
World Meteorological Organization, WMO-No. 8, 2023.
364+
https://library.wmo.int/idurl/4/68695
365+
"""
366+
367+
# Calculate vapor pressure (e) and saturation vapor pressure (es)
368+
e = coeff[0] * np.exp((coeff[1] * temp_air) / (coeff[2] + temp_air))
369+
es = coeff[0] * np.exp((coeff[1] * temp_dew) / (coeff[2] + temp_dew))
370+
371+
# Calculate relative humidity as percentage
372+
relative_humidity = 100 * (es / e)
373+
374+
return relative_humidity
375+
376+
377+
def tdew_from_rh(temp_air, relative_humidity, coeff=(6.112, 17.62, 243.12)):
378+
"""
379+
Calculate dewpoint temperature using the Magnus equation.
380+
This is a reversal of the calculation in :py:func:`rh_from_tdew`.
381+
382+
Parameters
383+
----------
384+
temp_air : numeric
385+
Air temperature (dry-bulb temperature). [°C]
386+
relative_humidity : numeric
387+
Relative humidity (0-100). [%]
388+
coeff: tuple, default (6.112, 17.62, 243.12)
389+
Magnus equation coefficients (A, B, C). The default values are those
390+
recommended by the WMO [1]_.
391+
392+
Returns
393+
-------
394+
numeric
395+
Dewpoint temperature. [°C]
396+
397+
References
398+
----------
399+
.. [1] "Guide to Instruments and Methods of Observation",
400+
World Meteorological Organization, WMO-No. 8, 2023.
401+
https://library.wmo.int/idurl/4/68695
402+
"""
403+
# Calculate the term inside the log
404+
# From RH = 100 * (es/e), we get es = (RH/100) * e
405+
# Substituting the Magnus equation and solving for dewpoint
406+
407+
# First calculate ln(es/A)
408+
ln_term = (
409+
(coeff[1] * temp_air) / (coeff[2] + temp_air)
410+
+ np.log(relative_humidity/100)
411+
)
412+
413+
# Then solve for dewpoint
414+
dewpoint = coeff[2] * ln_term / (coeff[1] - ln_term)
415+
416+
return dewpoint
417+
418+
340419
first_solar_spectral_correction = deprecated(
341420
since='0.10.0',
342421
alternative='pvlib.spectrum.spectral_factor_firstsolar'

pvlib/tests/test_atmosphere.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,153 @@ def test_gueymard94_pw():
8888
assert_allclose(pws, expected, atol=0.01)
8989

9090

91+
def test_tdew_to_rh_to_tdew():
92+
93+
# dewpoint temp calculated with wmo and aekr coefficients
94+
dewpoint_original = pd.Series([
95+
15.0, 20.0, 25.0, 12.0, 8.0
96+
])
97+
98+
temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
99+
100+
# Calculate relative humidity using pandas series as input
101+
relative_humidity = atmosphere.rh_from_tdew(
102+
temp_air=temperature_ambient,
103+
temp_dew=dewpoint_original
104+
)
105+
106+
dewpoint_calculated = atmosphere.tdew_from_rh(
107+
temp_air=temperature_ambient,
108+
relative_humidity=relative_humidity
109+
)
110+
111+
# test
112+
pd.testing.assert_series_equal(
113+
dewpoint_original,
114+
dewpoint_calculated,
115+
check_names=False
116+
)
117+
118+
119+
def test_rh_from_tdew():
120+
121+
dewpoint = pd.Series([
122+
15.0, 20.0, 25.0, 12.0, 8.0
123+
])
124+
125+
# relative humidity calculated with wmo and aekr coefficients
126+
relative_humidity_wmo = pd.Series([
127+
72.95185312581116, 73.81500029087906, 74.6401272083123,
128+
82.27063889868842, 87.39018119185337
129+
])
130+
relative_humidity_aekr = pd.Series([
131+
72.93876680928582, 73.8025121880607, 74.62820502423823,
132+
82.26135295757305, 87.38323744820416
133+
])
134+
135+
temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
136+
137+
# Calculate relative humidity using pandas series as input
138+
rh_series = atmosphere.rh_from_tdew(
139+
temp_air=temperature_ambient,
140+
temp_dew=dewpoint
141+
)
142+
143+
pd.testing.assert_series_equal(
144+
rh_series,
145+
relative_humidity_wmo,
146+
check_names=False
147+
)
148+
149+
# Calulate relative humidity using pandas series as input
150+
# with AEKR coefficients
151+
rh_series_aekr = atmosphere.rh_from_tdew(
152+
temp_air=temperature_ambient,
153+
temp_dew=dewpoint,
154+
coeff=(6.1094, 17.625, 243.04)
155+
)
156+
157+
pd.testing.assert_series_equal(
158+
rh_series_aekr,
159+
relative_humidity_aekr,
160+
check_names=False
161+
)
162+
163+
# Calculate relative humidity using array as input
164+
rh_array = atmosphere.rh_from_tdew(
165+
temp_air=temperature_ambient.to_numpy(),
166+
temp_dew=dewpoint.to_numpy()
167+
)
168+
169+
np.testing.assert_allclose(rh_array, relative_humidity_wmo.to_numpy())
170+
171+
# Calculate relative humidity using float as input
172+
rh_float = atmosphere.rh_from_tdew(
173+
temp_air=temperature_ambient.iloc[0],
174+
temp_dew=dewpoint.iloc[0]
175+
)
176+
177+
assert np.isclose(rh_float, relative_humidity_wmo.iloc[0])
178+
179+
180+
# Unit tests
181+
def test_tdew_from_rh():
182+
183+
dewpoint = pd.Series([
184+
15.0, 20.0, 25.0, 12.0, 8.0
185+
])
186+
187+
# relative humidity calculated with wmo and aekr coefficients
188+
relative_humidity_wmo = pd.Series([
189+
72.95185312581116, 73.81500029087906, 74.6401272083123,
190+
82.27063889868842, 87.39018119185337
191+
])
192+
relative_humidity_aekr = pd.Series([
193+
72.93876680928582, 73.8025121880607, 74.62820502423823,
194+
82.26135295757305, 87.38323744820416
195+
])
196+
197+
temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
198+
199+
# test as series
200+
dewpoint_series = atmosphere.tdew_from_rh(
201+
temp_air=temperature_ambient,
202+
relative_humidity=relative_humidity_wmo
203+
)
204+
205+
pd.testing.assert_series_equal(
206+
dewpoint_series, dewpoint, check_names=False
207+
)
208+
209+
# test as series with AEKR coefficients
210+
dewpoint_series_aekr = atmosphere.tdew_from_rh(
211+
temp_air=temperature_ambient,
212+
relative_humidity=relative_humidity_aekr,
213+
coeff=(6.1094, 17.625, 243.04)
214+
)
215+
216+
pd.testing.assert_series_equal(
217+
dewpoint_series_aekr, dewpoint,
218+
check_names=False
219+
)
220+
221+
# test as numpy array
222+
dewpoint_array = atmosphere.tdew_from_rh(
223+
temp_air=temperature_ambient.to_numpy(),
224+
relative_humidity=relative_humidity_wmo.to_numpy()
225+
)
226+
227+
np.testing.assert_allclose(dewpoint_array, dewpoint.to_numpy())
228+
229+
# test as float
230+
dewpoint_float = atmosphere.tdew_from_rh(
231+
temp_air=temperature_ambient.iloc[0],
232+
relative_humidity=relative_humidity_wmo.iloc[0]
233+
)
234+
235+
assert np.isclose(dewpoint_float, dewpoint.iloc[0])
236+
237+
91238
def test_first_solar_spectral_correction_deprecated():
92239
with pytest.warns(pvlibDeprecationWarning,
93240
match='Use pvlib.spectrum.spectral_factor_firstsolar'):

0 commit comments

Comments
 (0)