Skip to content

Calculate Relative Humidity via Magnus Tetens Equation #2286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 40 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
70e0156
changed default types to float in solarposition.py
Oct 29, 2024
0ec0cbf
switched to floats in the docstrings and removed type hints
Oct 30, 2024
5eb9d00
added magnus_tetens equations
Oct 30, 2024
ab2de3a
line too long in solarposition.py
Oct 30, 2024
f62bd8e
revert solarposition.py to pre-PR state
Oct 30, 2024
77025c7
set default magnus coefficients per conversation in #1744
Nov 5, 2024
b52f9a9
moved equations to atmosphere.py, updated function names to suggested…
Nov 11, 2024
58b246e
moved tests to atmosphere.py tests
Nov 11, 2024
a06871a
changed reference to WMO
Nov 11, 2024
caa4417
dry-bult temperature in the docstring
Nov 11, 2024
edd84b1
revert pyproject.toml
Nov 11, 2024
9c32f6a
remove uv.lock
Nov 11, 2024
63ed5c7
Update pvlib/atmosphere.py
kurt-rhee Nov 18, 2024
6312f92
fixing flake8 errors for tdew/rh functions
Nov 18, 2024
410e95f
Update pvlib/atmosphere.py
kurt-rhee Nov 25, 2024
d94df47
Update pvlib/atmosphere.py
kurt-rhee Nov 25, 2024
e6b5908
Update pvlib/atmosphere.py
kurt-rhee Nov 25, 2024
63a9226
Update pvlib/atmosphere.py
kurt-rhee Nov 25, 2024
459d5a6
Update pvlib/atmosphere.py
kurt-rhee Nov 25, 2024
05b846f
Update pvlib/atmosphere.py
kurt-rhee Nov 25, 2024
0ae31e1
added some unit tests for different coefficients and input types
kurt-rhee Dec 1, 2024
337f723
refactored tests, removed magnus_tetens, updated whatsnew
kurt-rhee Dec 5, 2024
8bc9b86
reference and whatsnew
kurt-rhee Dec 5, 2024
9651c3b
revert line 36 (linter)
kurt-rhee Dec 5, 2024
b860ca5
Update pvlib/atmosphere.py
kurt-rhee Dec 7, 2024
2bdc1dc
Update docs/sphinx/source/whatsnew/v0.11.2.rst
kurt-rhee Dec 7, 2024
c39c449
Update pvlib/atmosphere.py
kurt-rhee Dec 7, 2024
b5a6f09
Update pvlib/tests/test_atmosphere.py
kurt-rhee Dec 7, 2024
aedc835
Update pyproject.toml
kurt-rhee Dec 7, 2024
4fcb9e3
Merge branch 'main' into relative-humidity
AdamRJensen Dec 9, 2024
c956c91
Update pvlib/atmosphere.py
kurt-rhee Dec 10, 2024
f682815
Update pvlib/atmosphere.py
kurt-rhee Dec 10, 2024
0bfc6cb
Update pvlib/atmosphere.py
kurt-rhee Dec 10, 2024
71a165e
Update pvlib/atmosphere.py
kurt-rhee Dec 10, 2024
25527d6
Update pvlib/atmosphere.py
kurt-rhee Dec 10, 2024
1eff53f
added a round-trip test for magnus tetens
kurt-rhee Dec 11, 2024
df33a31
temperature -> temp_air; dewpoint -> temp_dew
kandersolar Dec 12, 2024
b4802a3
miscellaneous other cleanup
kandersolar Dec 12, 2024
3893692
tests: put assertions next to calculations
kandersolar Dec 12, 2024
11d81c3
linter
kandersolar Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/sphinx/source/reference/airmass_atmospheric.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Airmass and atmospheric models
atmosphere.get_relative_airmass
atmosphere.pres2alt
atmosphere.alt2pres
atmosphere.tdew_from_rh
atmosphere.rh_from_tdew
atmosphere.gueymard94_pw
atmosphere.first_solar_spectral_correction
atmosphere.bird_hulstrom80_aod_bb
Expand Down
3 changes: 3 additions & 0 deletions docs/sphinx/source/whatsnew/v0.11.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Deprecations

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

Expand All @@ -20,6 +22,7 @@ Bug Fixes
* Handle DST transitions that happen at midnight in :py:func:`pvlib.solarposition.hour_angle`
(:issue:`2132` :pull:`2133`)


Bug fixes
~~~~~~~~~
* :py:func:`~pvlib.spa.julian_day_dt` now accounts for the 10 day difference
Expand Down
79 changes: 79 additions & 0 deletions pvlib/atmosphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,85 @@ def gueymard94_pw(temp_air, relative_humidity):
return pw


def rh_from_tdew(temp_air, temp_dew, coeff=(6.112, 17.62, 243.12)):
"""
Calculate relative humidity from dewpoint temperature using the Magnus
equation.

Parameters
----------
temp_air : numeric
Air temperature (dry-bulb temperature). [°C]
temp_dew : numeric
Dew-point temperature. [°C]
coeff : tuple, default (6.112, 17.62, 243.12)
Magnus equation coefficients (A, B, C). The default values are those
recommended by the WMO [1]_.

Returns
-------
numeric
Relative humidity (0.0-100.0). [%]

References
----------
.. [1] "Guide to Instruments and Methods of Observation",
World Meteorological Organization, WMO-No. 8, 2023.
https://library.wmo.int/idurl/4/68695
"""

# Calculate vapor pressure (e) and saturation vapor pressure (es)
e = coeff[0] * np.exp((coeff[1] * temp_air) / (coeff[2] + temp_air))
es = coeff[0] * np.exp((coeff[1] * temp_dew) / (coeff[2] + temp_dew))

# Calculate relative humidity as percentage
relative_humidity = 100 * (es / e)

return relative_humidity


def tdew_from_rh(temp_air, relative_humidity, coeff=(6.112, 17.62, 243.12)):
"""
Calculate dewpoint temperature using the Magnus equation.
This is a reversal of the calculation in :py:func:`rh_from_tdew`.

Parameters
----------
temp_air : numeric
Air temperature (dry-bulb temperature). [°C]
relative_humidity : numeric
Relative humidity (0-100). [%]
coeff: tuple, default (6.112, 17.62, 243.12)
Magnus equation coefficients (A, B, C). The default values are those
recommended by the WMO [1]_.

Returns
-------
numeric
Dewpoint temperature. [°C]

References
----------
.. [1] "Guide to Instruments and Methods of Observation",
World Meteorological Organization, WMO-No. 8, 2023.
https://library.wmo.int/idurl/4/68695
"""
# Calculate the term inside the log
# From RH = 100 * (es/e), we get es = (RH/100) * e
# Substituting the Magnus equation and solving for dewpoint

# First calculate ln(es/A)
ln_term = (
(coeff[1] * temp_air) / (coeff[2] + temp_air)
+ np.log(relative_humidity/100)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like RH zero might throw an error or warning here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a check for zero values or leave the original message in the stack trace?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary, RH=0% is practically impossible (source).

)

# Then solve for dewpoint
dewpoint = coeff[2] * ln_term / (coeff[1] - ln_term)

return dewpoint


first_solar_spectral_correction = deprecated(
since='0.10.0',
alternative='pvlib.spectrum.spectral_factor_firstsolar'
Expand Down
147 changes: 147 additions & 0 deletions pvlib/tests/test_atmosphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,153 @@ def test_gueymard94_pw():
assert_allclose(pws, expected, atol=0.01)


def test_tdew_to_rh_to_tdew():

# dewpoint temp calculated with wmo and aekr coefficients
dewpoint_original = pd.Series([
15.0, 20.0, 25.0, 12.0, 8.0
])

temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])

# Calculate relative humidity using pandas series as input
relative_humidity = atmosphere.rh_from_tdew(
temp_air=temperature_ambient,
temp_dew=dewpoint_original
)

dewpoint_calculated = atmosphere.tdew_from_rh(
temp_air=temperature_ambient,
relative_humidity=relative_humidity
)

# test
pd.testing.assert_series_equal(
dewpoint_original,
dewpoint_calculated,
check_names=False
)


def test_rh_from_tdew():

dewpoint = pd.Series([
15.0, 20.0, 25.0, 12.0, 8.0
])

# relative humidity calculated with wmo and aekr coefficients
relative_humidity_wmo = pd.Series([
72.95185312581116, 73.81500029087906, 74.6401272083123,
82.27063889868842, 87.39018119185337
])
relative_humidity_aekr = pd.Series([
72.93876680928582, 73.8025121880607, 74.62820502423823,
82.26135295757305, 87.38323744820416
])

temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])

# Calculate relative humidity using pandas series as input
rh_series = atmosphere.rh_from_tdew(
temp_air=temperature_ambient,
temp_dew=dewpoint
)

pd.testing.assert_series_equal(
rh_series,
relative_humidity_wmo,
check_names=False
)

# Calulate relative humidity using pandas series as input
# with AEKR coefficients
rh_series_aekr = atmosphere.rh_from_tdew(
temp_air=temperature_ambient,
temp_dew=dewpoint,
coeff=(6.1094, 17.625, 243.04)
)

pd.testing.assert_series_equal(
rh_series_aekr,
relative_humidity_aekr,
check_names=False
)

# Calculate relative humidity using array as input
rh_array = atmosphere.rh_from_tdew(
temp_air=temperature_ambient.to_numpy(),
temp_dew=dewpoint.to_numpy()
)

np.testing.assert_allclose(rh_array, relative_humidity_wmo.to_numpy())

# Calculate relative humidity using float as input
rh_float = atmosphere.rh_from_tdew(
temp_air=temperature_ambient.iloc[0],
temp_dew=dewpoint.iloc[0]
)

assert np.isclose(rh_float, relative_humidity_wmo.iloc[0])


# Unit tests
def test_tdew_from_rh():

dewpoint = pd.Series([
15.0, 20.0, 25.0, 12.0, 8.0
])

# relative humidity calculated with wmo and aekr coefficients
relative_humidity_wmo = pd.Series([
72.95185312581116, 73.81500029087906, 74.6401272083123,
82.27063889868842, 87.39018119185337
])
relative_humidity_aekr = pd.Series([
72.93876680928582, 73.8025121880607, 74.62820502423823,
82.26135295757305, 87.38323744820416
])

temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])

# test as series
dewpoint_series = atmosphere.tdew_from_rh(
temp_air=temperature_ambient,
relative_humidity=relative_humidity_wmo
)

pd.testing.assert_series_equal(
dewpoint_series, dewpoint, check_names=False
)

# test as series with AEKR coefficients
dewpoint_series_aekr = atmosphere.tdew_from_rh(
temp_air=temperature_ambient,
relative_humidity=relative_humidity_aekr,
coeff=(6.1094, 17.625, 243.04)
)

pd.testing.assert_series_equal(
dewpoint_series_aekr, dewpoint,
check_names=False
)

# test as numpy array
dewpoint_array = atmosphere.tdew_from_rh(
temp_air=temperature_ambient.to_numpy(),
relative_humidity=relative_humidity_wmo.to_numpy()
)

np.testing.assert_allclose(dewpoint_array, dewpoint.to_numpy())

# test as float
dewpoint_float = atmosphere.tdew_from_rh(
temp_air=temperature_ambient.iloc[0],
relative_humidity=relative_humidity_wmo.iloc[0]
)

assert np.isclose(dewpoint_float, dewpoint.iloc[0])


def test_first_solar_spectral_correction_deprecated():
with pytest.warns(pvlibDeprecationWarning,
match='Use pvlib.spectrum.spectral_factor_firstsolar'):
Expand Down
Loading