Skip to content
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

Calculate Relative Humidity via Magnus Tetens Equation #2286

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
76 changes: 76 additions & 0 deletions pvlib/atmosphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,82 @@
return pw


def rh_from_tdew(temperature, dewpoint, coeff=(6.112, 17.62, 243.12)):
"""
Calculate relative humidity from dewpoint temperatureusing Magnus equation.
This function was used by First Solar in creating their spectral model
and is therefore relevant to the first solar spectral model in pvlib.
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved
Default magnus equation coefficients are from [2].

Parameters
----------
temperature : numeric
Air temperature (dry-bulb temperature) in degrees Celsius
dewpoint : numeric
Dewpoint temperature in degrees Celsius
coeff: tuple
Magnus equation coefficient (A, B, C)

Returns
-------
pd.Series
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved
Relative humidity as percentage (0.0-100.0)

References
----------
.. [1] https://library.wmo.int/viewer/68695/?offset=3#page=220&viewer=picture&o=bookmark&n=0&q=

Check failure on line 363 in pvlib/atmosphere.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E501 line too long (99 > 79 characters)
.. [2] https://www.schweizerbart.de//papers/metz/detail/3/89544/Advancements_in_the_field_of_hygrometry?af=crossref

Check failure on line 364 in pvlib/atmosphere.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E501 line too long (119 > 79 characters)
"""

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

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

return relative_humidity


def tdew_from_rh(
temperature, relative_humidity, coeff=(6.112, 17.62, 243.12)
):
"""
Calculate dewpoint temperature using Magnus equation.
This is just a reversal of the calculation in calculate_relative_humidity.
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
temperature : numeric
Air temperature (dry-bulb temperature) in degrees Celsius
relative_humidity : numeric
Relative humidity as percentage (0-100)

Returns
-------
pd.Series
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved
Dewpoint temperature in degrees Celsius

References
----------
.. [1] https://library.wmo.int/viewer/68695/?offset=3#page=220&viewer=picture&o=bookmark&n=0&q=

Check failure on line 398 in pvlib/atmosphere.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E501 line too long (99 > 79 characters)
"""
# 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] * temperature) / (coeff[2] + temperature)
+ 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
108 changes: 108 additions & 0 deletions pvlib/spectrum/magnus_tetens.py
Copy link
Member

Choose a reason for hiding this comment

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

I agree that atmosphere.py is a better home for this functionality

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved in latest commit

Copy link
Member

Choose a reason for hiding this comment

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

Still need to remove this magnus_tetens.py file

Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import numpy as np


def magnus_tetens_aekr(temperature, dewpoint, A=6.112, B=17.62, C=243.12):
"""
Calculate relative humidity using Magnus equation with AEKR coefficients.
This function was used by First Solar in creating their spectral model
and is therefore relevant to the first solar spectral model in pvlib.
Default magnus equation coefficients are from [2].
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
temperature : pd.Series
Air temperature in degrees Celsius
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved
dewpoint : pd.Series
Dewpoint temperature in degrees Celsius
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved
A: float
Magnus equation coefficient A
B: float
Magnus equation coefficient B
C: float
Magnus equation coefficient C
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
pd.Series
Relative humidity as percentage (0-100)

Notes
-----
Uses the AEKR coefficients which minimize errors between -40 and
50 degrees C according to reference [1].

References
----------
.. [1] https://www.osti.gov/servlets/purl/548871-PjpxAP/webviewable/
.. [2] https://www.schweizerbart.de//papers/metz/detail/3/89544/Advancements_in_the_field_of_hygrometry?af=crossref

Check failure on line 37 in pvlib/spectrum/magnus_tetens.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E501 line too long (119 > 79 characters)
"""

# Calculate vapor pressure (e) and saturation vapor pressure (es)
e = A * np.exp((B * temperature) / (C + temperature))
es = A * np.exp((B * dewpoint) / (C + dewpoint))

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

return relative_humidity


def reverse_magnus_tetens_aekr(
temperature, relative_humidity, B=17.62, C=243.12
):
"""
Calculate dewpoint temperature using Magnus equation with
AEKR coefficients. This is just a reversal of the calculation
in calculate_relative_humidity.

Parameters
----------
temperature : pd.Series
Air temperature in degrees Celsius
relative_humidity : pd.Series
Relative humidity as percentage (0-100)

Returns
-------
pd.Series
Dewpoint temperature in degrees Celsius

Notes
-----
Derived by solving the Magnus equation for dewpoint given
relative humidity.
Valid for temperatures between -40 and 50 degrees C.

References
----------
.. [1] https://www.osti.gov/servlets/purl/548871-PjpxAP/webviewable/
"""
# 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 = (
(B * temperature) / (C + temperature)
+ np.log(relative_humidity/100)
)

# Then solve for dewpoint
dewpoint = C * ln_term / (B - ln_term)

return dewpoint


if __name__ == "__main__":
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved
import pandas as pd
rh = magnus_tetens_aekr(
temperature=pd.Series([20.0, 25.0, 30.0, 15.0, 10.0]),
dewpoint=pd.Series([15.0, 20.0, 25.0, 12.0, 8.0])
)

dewpoint = reverse_magnus_tetens_aekr(
temperature=pd.Series([20.0, 25.0, 30.0, 15.0, 10.0]),
relative_humidity=rh
)
print(rh)
print(dewpoint)
19 changes: 19 additions & 0 deletions pvlib/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,22 @@
'IXXO': 3.18803,
'FD': 1}
return parameters


@pytest.fixture(scope='function')
def tdew_rh_conversion_temperature():
temperature = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
return temperature


@pytest.fixture(scope='function')
def tdew_rh_conversion_dewpoint():
dewpoint = pd.Series([15.0, 20.0, 25.0, 12.0, 8.0])
return dewpoint

@pytest.fixture(scope='function')

Check failure on line 490 in pvlib/tests/conftest.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E302 expected 2 blank lines, found 1
def tdew_rh_conversion_relative_humidity():
relative_humidity = pd.Series([
72.938767, 73.802512, 74.628205, 82.261353, 87.383237
])
return relative_humidity
37 changes: 37 additions & 0 deletions pvlib/tests/test_atmosphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,43 @@

assert_allclose(pws, expected, atol=0.01)

# Unit tests
def test_rh_from_tdew(

Check failure on line 91 in pvlib/tests/test_atmosphere.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E302 expected 2 blank lines, found 1
tdew_rh_conversion_temperature, tdew_rh_conversion_dewpoint,
tdew_rh_conversion_relative_humidity
):

# Calculate relative humidity
rh = atmosphere.rh_from_tdew(
temperature=tdew_rh_conversion_temperature,
dewpoint=tdew_rh_conversion_dewpoint
)

# test
pd.testing.assert_series_equal(
rh,
tdew_rh_conversion_relative_humidity,
check_names=False
)
Copy link
Member

Choose a reason for hiding this comment

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

For completeness, let's add corresponding tests where the inputs are float and array. We will also need to include tests where alternative coefficient values are specified.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kandersolar just reworked the unit tests, I added tests which take in a numpy array and a float as well as a pandas series with a different set of coefficients. Let me know what you think, happy to adjust in any way.



# Unit tests
def test_tdew_from_rh(
tdew_rh_conversion_temperature, tdew_rh_conversion_dewpoint,
tdew_rh_conversion_relative_humidity
):

# Calculate relative humidity
dewpoint = atmosphere.tdew_from_rh(
temperature=tdew_rh_conversion_temperature,
relative_humidity=tdew_rh_conversion_relative_humidity
)

# test
pd.testing.assert_series_equal(
dewpoint, spectrum_dewpoint, check_names=False

Check failure on line 124 in pvlib/tests/test_atmosphere.py

View workflow job for this annotation

GitHub Actions / flake8-linter

F821 undefined name 'spectrum_dewpoint'
)


def test_first_solar_spectral_correction_deprecated():
with pytest.warns(pvlibDeprecationWarning,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
kurt-rhee marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ optional = [
]
doc = [
'ipython',
'pickleshare', # required by ipython
'pickleshare', # required by ipython
'matplotlib',
'sphinx == 7.3.7',
'pydata-sphinx-theme == 0.15.4',
Expand Down
Loading