Skip to content

Implement enhanced Faiman temperature model #1595

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 16 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions docs/sphinx/source/reference/pv_modeling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ PV temperature models
temperature.sapm_cell_from_module
temperature.pvsyst_cell
temperature.faiman
temperature.faiman_rad
temperature.fuentes
temperature.ross
temperature.noct_sam
Expand Down
11 changes: 7 additions & 4 deletions docs/sphinx/source/whatsnew/v0.9.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ Enhancements
* Added a function to calculate one of GHI, DHI, and DNI from values of the other two.
:py:func:`~pvlib.irradiance.complete_irradiance`.
(:issue:`1565`, :pull:`1567`)
* Add optional ``return_components`` parameter to :py:func:`pvlib.irradiance.haydavies` to return
individual diffuse irradiance components. (:issue:`1553`, :pull:`1568`)

* Added optional ``return_components`` parameter to :py:func:`pvlib.irradiance.haydavies` to return
individual diffuse irradiance components (:issue:`1553`, :pull:`1568`)
* Added a module temperature model that accounts for radiative losses to the sky
in a simplified way, using the Faiman model as an example.
:py:func:`~pvlib.temperature.faiman_rad`
(:issue:`1594`, :pull:`1595`)

Bug fixes
~~~~~~~~~
Expand All @@ -42,7 +45,6 @@ Benchmarking
~~~~~~~~~~~~~
* Removed ``time_tracker_singleaxis`` function from tracking.py (:issue:`1508`, :pull:`1535`)


Requirements
~~~~~~~~~~~~

Expand All @@ -59,4 +61,5 @@ Contributors
* Kevin Anderson (:ghuser:`kanderso-nrel`)
* Karel De Brabandere (:ghuser:`kdebrab`)
* Naman Priyadarshi (:ghuser:`Naman-Priyadarshi`)
* Adam R. Jensen (:ghuser:`AdamRJensen`)
* Echedey Luis (:ghuser:`echedey-ls`)
128 changes: 123 additions & 5 deletions pvlib/temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pvlib._deprecation import warn_deprecated
from pvlib.tools import _get_sample_intervals
import scipy
import scipy.constants
import warnings


Expand Down Expand Up @@ -318,7 +319,7 @@ def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
u_v : float, default 0.0
Combined heat loss factor influenced by wind. Parameter :math:`U_{v}`
in :eq:`pvsyst`.
:math:`\left[ \frac{\text{W}/\text{m}^2}{\text{C}\ \left( \text{m/s} \right)} \right]` # noQA: E501
:math:`\left[ \frac{\text{W}/\text{m}^2}{\text{C}\ \left( \text{m/s} \right)} \right]`

eta_m : numeric, default None (deprecated, use module_efficiency instead)

Expand Down Expand Up @@ -375,7 +376,7 @@ def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
>>> params = TEMPERATURE_MODEL_PARAMETERS['pvsyst']['freestanding']
>>> pvsyst_cell(1000, 10, **params)
37.93103448275862
"""
""" # noQA: E501

if eta_m:
warn_deprecated(
Expand Down Expand Up @@ -413,12 +414,14 @@ def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):

u0 : numeric, default 25.0
Combined heat loss factor coefficient. The default value is one
determined by Faiman for 7 silicon modules.
determined by Faiman for 7 silicon modules
in the Negev desert on an open rack at 30.9° tilt.
:math:`\left[\frac{\text{W}/{\text{m}^2}}{\text{C}}\right]`

u1 : numeric, default 6.84
Combined heat loss factor influenced by wind. The default value is one
determined by Faiman for 7 silicon modules.
determined by Faiman for 7 silicon modules
in the Negev desert on an open rack at 30.9° tilt.
:math:`\left[ \frac{\text{W}/\text{m}^2}{\text{C}\ \left( \text{m/s} \right)} \right]`

Returns
Expand All @@ -434,6 +437,7 @@ def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):
----------
.. [1] Faiman, D. (2008). "Assessing the outdoor operating temperature of
photovoltaic modules." Progress in Photovoltaics 16(4): 307-315.
:doi:`10.1002/pip.813`

.. [2] "IEC 61853-2 Photovoltaic (PV) module performance testing and energy
rating - Part 2: Spectral responsivity, incidence angle and module
Expand All @@ -442,7 +446,12 @@ def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):
.. [3] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy
rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018.

'''
See also
--------
pvlib.temperature.faiman_rad

''' # noQA: E501

# Contributed by Anton Driesse (@adriesse), PV Performance Labs. Dec., 2019

# The following lines may seem odd since u0 & u1 are probably scalar,
Expand All @@ -457,6 +466,115 @@ def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):
return temp_air + temp_difference


def faiman_rad(poa_global, temp_air, wind_speed=1.0, ir_down=None,
u0=25.0, u1=6.84, sky_view=1.0, emissivity=0.88):
r'''
Calculate cell or module temperature using the Faiman model augmented
with a radiative loss term.

The Faiman model uses an empirical heat loss factor model [1]_ and is
adopted in the IEC 61853 standards [2]_ and [3]_. The radiative loss
term was proposed and developed by Driesse [4]_.

The model can be used to represent cell or module temperature.

Parameters
----------
poa_global : numeric
Total incident irradiance [W/m^2].

temp_air : numeric
Ambient dry bulb temperature [C].

wind_speed : numeric, default 1.0
Wind speed measured at the same height for which the wind loss
factor was determined. The default value 1.0 m/s is the wind
speed at module height used to determine NOCT. [m/s]

ir_down : numeric, default 0.0
Downwelling infrared radiation from the sky, measured on a horizontal
surface. [W/m^2]

u0 : numeric, default 25.0
Combined heat loss factor coefficient. The default value is one
determined by Faiman for 7 silicon modules
in the Negev desert on an open rack at 30.9° tilt.
:math:`\left[\frac{\text{W}/{\text{m}^2}}{\text{C}}\right]`

u1 : numeric, default 6.84
Combined heat loss factor influenced by wind. The default value is one
determined by Faiman for 7 silicon modules
in the Negev desert on an open rack at 30.9° tilt.
:math:`\left[ \frac{\text{W}/\text{m}^2}{\text{C}\ \left( \text{m/s} \right)} \right]`

sky_view : numeric, default 1.0
Effective view factor limiting the radiative exchange between the
Copy link
Member

Choose a reason for hiding this comment

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

Is "effective" adding something here? sky_view looks to be the fraction of the sky dome "seen" by the module, so that's the usual view factor. I wonder if the formula could be considered part of the model and the input would be surface_tilt, but I'll defer to the model author.

Copy link
Member Author

Choose a reason for hiding this comment

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

I snuck in the "effective" because the infra-red sky is very non-isotropic and that needs to be taken into account. The formula is really just a starting point for future work, not adequately explored and validated.

module and the sky. For a tilted array the expressions
(1 + 3*cos(tilt)) / 4 can be used as a first estimate for sky_view
as discussed in [4]_. The default value is for a horizontal module.
[unitless]

emissivity : numeric, default 0.88
Infrared emissivity of the module surface facing the sky. The default
value represents the middle of a range of values found in the
literature. [unitless]

Returns
-------
numeric, values in degrees Celsius

Notes
-----
All arguments may be scalars or vectors. If multiple arguments
are vectors they must be the same length.

When only irradiance, air temperature and wind speed inputs are provided
(`ir_down` is `None`) this function calculates the same device temperature
as the original faiman model. When down-welling long-wave radiation data
are provided as well (`ir_down` is not None) the default u0 and u1 values
from the original model should not be used because a portion of the
radiative losses would be double-counted.

References
----------
.. [1] Faiman, D. (2008). "Assessing the outdoor operating temperature of
photovoltaic modules." Progress in Photovoltaics 16(4): 307-315.
:doi:`10.1002/pip.813`

.. [2] "IEC 61853-2 Photovoltaic (PV) module performance testing and energy
rating - Part 2: Spectral responsivity, incidence angle and module
operating temperature measurements". IEC, Geneva, 2018.

.. [3] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy
rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018.

.. [4] Driesse, A. et al (2022) "Improving Common PV Module Temperature
Models by Incorporating Radiative Losses to the Sky". SAND2022-11604.
:doi:`10.2172/1884890`

See also
--------
pvlib.temperature.faiman

''' # noQA: E501

# Contributed by Anton Driesse (@adriesse), PV Performance Labs. Nov., 2022

abs_zero = -273.15
sigma = scipy.constants.Stefan_Boltzmann

if ir_down is None:
qrad_sky = 0.0
else:
ir_up = sigma * ((temp_air - abs_zero)**4)
qrad_sky = emissivity * sky_view * (ir_up - ir_down)

heat_input = poa_global - qrad_sky
total_loss_factor = u0 + u1 * wind_speed
temp_difference = heat_input / total_loss_factor
return temp_air + temp_difference


def ross(poa_global, temp_air, noct):
r'''
Calculate cell temperature using the Ross model.
Expand Down
33 changes: 29 additions & 4 deletions pvlib/tests/test_temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ def test_pvsyst_cell_eta_m_deprecated():

def test_faiman_default():
result = temperature.faiman(900, 20, 5)
assert_allclose(result, 35.203, 0.001)
assert_allclose(result, 35.203, atol=0.001)


def test_faiman_kwargs():
result = temperature.faiman(900, 20, wind_speed=5.0, u0=22.0, u1=6.)
assert_allclose(result, 37.308, 0.001)
assert_allclose(result, 37.308, atol=0.001)


def test_faiman_list():
Expand All @@ -122,7 +122,7 @@ def test_faiman_list():
winds = [10, 5, 0]
result = temperature.faiman(irrads, temps, wind_speed=winds)
expected = np.array([0.0, 18.446, 5.0])
assert_allclose(expected, result, 3)
assert_allclose(expected, result, atol=0.001)


def test_faiman_ndarray():
Expand All @@ -131,7 +131,32 @@ def test_faiman_ndarray():
winds = np.array([10, 5, 0])
result = temperature.faiman(irrads, temps, wind_speed=winds)
expected = np.array([0.0, 18.446, 5.0])
assert_allclose(expected, result, 3)
assert_allclose(expected, result, atol=0.001)


def test_faiman_rad_no_ir():
expected = temperature.faiman(900, 20, 5)
result = temperature.faiman_rad(900, 20, 5)
assert_allclose(result, expected)


def test_faiman_rad_ir():
ir_down = np.array([0, 100, 200, 315.6574, 400])
expected = [-11.111, -7.591, -4.071, -0.000, 2.969]
result = temperature.faiman_rad(0, 0, 0, ir_down)
assert_allclose(result, expected, atol=0.001)

sky_view = np.array([1.0, 0.5, 0.0])
expected = [-4.071, -2.036, 0.000]
result = temperature.faiman_rad(0, 0, 0, ir_down=200,
sky_view=sky_view)
assert_allclose(result, expected, atol=0.001)

emissivity = np.array([1.0, 0.88, 0.5, 0.0])
expected = [-4.626, -4.071, -2.313, 0.000]
result = temperature.faiman_rad(0, 0, 0, ir_down=200,
emissivity=emissivity)
assert_allclose(result, expected, atol=0.001)


def test_ross():
Expand Down