Skip to content

Commit

Permalink
Calculate Relative Humidity via Magnus Tetens Equation (#2286)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
6 people authored Dec 12, 2024
1 parent c12a477 commit cb37129
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 0 deletions.
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 @@ -13,6 +13,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 @@ -25,6 +27,7 @@ Bug Fixes
* Changed ``dni_extra`` to a required parameter in :py:func:`pvlib.irradiance.ghi_from_poa_driesse_2023`
(:issue:`2279` :pull:`2331`)


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)
)

# 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

0 comments on commit cb37129

Please sign in to comment.