Repository: pvlib/pvlib-python
Pull request #1045 (2020-09-04 16:52:57)

add Ross temperature model
 - [x] I am familiar with the [contributing guidelines](https://pvlib-python.readthedocs.io/en/latest/contributing.html)
 - [x] Tests added
 - [x] Updates entries to [`docs/sphinx/source/api.rst`](https://github.com/pvlib/pvlib-python/blob/master/docs/sphinx/source/api.rst) for API changes.
 - [x] Adds description and name entries in the appropriate "what's new" file in [`docs/sphinx/source/whatsnew`](https://github.com/pvlib/pvlib-python/tree/master/docs/sphinx/source/whatsnew) for all changes. Includes link to the GitHub Issue with `` :issue:`num` `` or this Pull Request with `` :pull:`num` ``. Includes contributor name and/or GitHub username (link with `` :ghuser:`user` ``).
 - [x] New code is fully documented. Includes [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) compliant docstrings, examples, and comments where necessary.
 - [x] Pull request is nearly complete and ready for detailed review.
 - [x] Maintainer: Appropriate GitHub Labels and Milestone are assigned to the Pull Request and linked Issue.

A simple cell temperature model using only NOCT as a parameter.

adriesse: Should this really be called a "Ross" model?  It's just basic heat loss and NOCT conditions:

```
total_loss_factor  = 800 / (noct - 20)
heat_input = poa_global
temp_difference = heat_input / total_loss_factor
return temp_air + temp_difference
```
I wouldn't say this approach ignores the effect of wind but rather that it tacitly assumes a wind speed of 1 m/s.

cwhanse: > Should this really be called a "Ross" model? It's just basic heat loss and NOCT conditions:

Agree that's what it is, but it needs a name, and others have cited it as Ross' model (author of the reference).

> I wouldn't say this approach ignores the effect of wind but rather that it tacitly assumes a wind speed of 1 m/s.

good point.
wholmgren: thanks @cwhanse!

Patch:
diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst
index 077a5e1..2e882e9 100644
--- a/docs/sphinx/source/api.rst
+++ b/docs/sphinx/source/api.rst
@@ -236,7 +236,10 @@ PV temperature models
    temperature.pvsyst_cell
    temperature.faiman
    temperature.fuentes
+   temperature.ross
    pvsystem.PVSystem.sapm_celltemp
+   pvsystem.PVSystem.pvsyst_celltemp
+   pvsystem.PVSystem.faiman_celltemp
 
 Temperature Model Parameters
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/sphinx/source/whatsnew/v0.8.1.rst b/docs/sphinx/source/whatsnew/v0.8.1.rst
index 61eb026..9b6e2a3 100644
--- a/docs/sphinx/source/whatsnew/v0.8.1.rst
+++ b/docs/sphinx/source/whatsnew/v0.8.1.rst
@@ -13,6 +13,8 @@ Deprecations
 
 Enhancements
 ~~~~~~~~~~~~
+* Added :py:func:`pvlib.temperature.ross` for cell temperature modeling using
+  only NOCT. (:pull:`1045`)
 
 
 Bug fixes
@@ -36,3 +38,5 @@ Requirements
 Contributors
 ~~~~~~~~~~~~
 * Kevin Anderson (:ghuser:`kanderso-nrel`)
+* Will Holmgren (:ghuser:`wholmgren`)
+* Cliff Hansen (:ghuser:`cwhanse`)
diff --git a/pvlib/temperature.py b/pvlib/temperature.py
index 1dd32fe..1f27180 100644
--- a/pvlib/temperature.py
+++ b/pvlib/temperature.py
@@ -377,9 +377,10 @@ def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
 
 def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):
     r'''
-    Calculate cell or module temperature using the Faiman model.  The Faiman
-    model uses an empirical heat loss factor model [1]_ and is adopted in the
-    IEC 61853 standards [2]_ and [3]_.
+    Calculate cell or module temperature using the Faiman model.
+
+    The Faiman model uses an empirical heat loss factor model [1]_ and is
+    adopted in the IEC 61853 standards [2]_ and [3]_.
 
     Usage of this model in the IEC 61853 standard does not distinguish
     between cell and module temperature.
@@ -443,6 +444,53 @@ def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):
     return temp_air + temp_difference
 
 
+def ross(poa_global, temp_air, noct):
+    r'''
+    Calculate cell temperature using the Ross model.
+
+    The Ross model [1]_ assumes the difference between cell temperature
+    and ambient temperature is proportional to the plane of array irradiance,
+    and assumes wind speed of 1 m/s. The model implicitly assumes steady or
+    slowly changing irradiance conditions.
+
+    Parameters
+    ----------
+    poa_global : numeric
+        Total incident irradiance. [W/m^2]
+
+    temp_air : numeric
+        Ambient dry bulb temperature. [C]
+
+    noct : numeric
+        Nominal operating cell temperature [C], determined at conditions of
+        800 W/m^2 irradiance, 20 C ambient air temperature and 1 m/s wind.
+
+    Returns
+    -------
+    cell_temperature : numeric
+        Cell temperature. [C]
+
+    Notes
+    -----
+    The Ross model for cell temperature :math:`T_{C}` is given in [1]_ as
+
+    .. math::
+
+        T_{C} = T_{a} + \frac{NOCT - 20}{80} S
+
+    where :math:`S` is the plane of array irradiance in :math:`mW/{cm}^2`.
+    This function expects irradiance in :math:`W/m^2`.
+
+    References
+    ----------
+    .. [1] Ross, R. G. Jr., (1981). "Design Techniques for Flat-Plate
+       Photovoltaic Arrays". 15th IEEE Photovoltaic Specialist Conference,
+       Orlando, FL.
+    '''
+    # factor of 0.1 converts irradiance from W/m2 to mW/cm2
+    return temp_air + (noct - 20.) / 80. * poa_global * 0.1
+
+
 def _fuentes_hconv(tave, windmod, tinoct, temp_delta, xlen, tilt,
                    check_reynold):
     # Calculate the convective coefficient as in Fuentes 1987 -- a mixture of


Test patch:
diff --git a/pvlib/tests/test_temperature.py b/pvlib/tests/test_temperature.py
index 411adcf..f8ea3a8 100644
--- a/pvlib/tests/test_temperature.py
+++ b/pvlib/tests/test_temperature.py
@@ -124,6 +124,14 @@ def test_faiman_ndarray():
     assert_allclose(expected, result, 3)
 
 
+def test_ross():
+    result = temperature.ross(np.array([1000., 600., 1000.]),
+                              np.array([20., 40., 60.]),
+                              np.array([40., 100., 20.]))
+    expected = np.array([45., 100., 60.])
+    assert_allclose(expected, result)
+
+
 def test_faiman_series():
     times = pd.date_range(start="2015-01-01", end="2015-01-02", freq="12H")
     temps = pd.Series([0, 10, 5], index=times)