From 85aa7207e87c7015381676916b4ee3d05b47a3f0 Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Thu, 31 Jul 2025 09:47:44 +0100 Subject: [PATCH 01/12] Adds k parameter to temperature.ross --- pvlib/temperature.py | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 6c274d79b7..fac2a4be0c 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -618,7 +618,7 @@ def faiman_rad(poa_global, temp_air, wind_speed=1.0, ir_down=None, return temp_air + temp_difference -def ross(poa_global, temp_air, noct): +def ross(poa_global, temp_air, noct=None, k=None): r''' Calculate cell temperature using the Ross model. @@ -638,6 +638,9 @@ def ross(poa_global, temp_air, noct): 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. + k: numeric + Ross coefficient [Km^2/W], which is an alternative to employing + NOCT in Ross's equation. Returns ------- @@ -650,19 +653,52 @@ def ross(poa_global, temp_air, noct): .. math:: - T_{C} = T_{a} + \frac{NOCT - 20}{80} S + T_{C} = T_{a} + \frac{NOCT - 20}{80} S = T_{a} + k × S where :math:`S` is the plane of array irradiance in :math:`mW/{cm}^2`. This function expects irradiance in :math:`W/m^2`. + Reference values for k are provided in [2]_, covering different types + of mounting and module structure. + + +---------------------+-----------+ + | Mounting | :math:`k` | + +=====================+===========+ + | well_cooled | 0.02 | + +---------------------+-----------+ + | free_standing | 0.0208 | + +---------------------+-----------+ + | flat_on_roof | 0.026 | + +---------------------+-----------+ + | not_so_well_cooled | 0.0342 | + +---------------------+-----------+ + | transparent_pv | 0.0455 | + +---------------------+-----------+ + | facade_integrated | 0.0538 | + +---------------------+-----------+ + | on_sloped_roof | 0.0563 | + +---------------------+-----------+ + References ---------- .. [1] Ross, R. G. Jr., (1981). "Design Techniques for Flat-Plate Photovoltaic Arrays". 15th IEEE Photovoltaic Specialist Conference, Orlando, FL. + .. [2] E. Skoplaki and J. A. Palyvos, “Operating temperature of + photovoltaic modules: A survey of pertinent correlations,” Renewable + Energy, vol. 34, no. 1, pp. 23–29, Jan. 2009, doi: + https://doi.org/10.1016/j.renene.2008.04.009. ''' - # factor of 0.1 converts irradiance from W/m2 to mW/cm2 - return temp_air + (noct - 20.) / 80. * poa_global * 0.1 + if (noct is None) & (k is None): + raise ValueError("noct or k need to be provided as numeric input.") + elif (noct is not None) & (k is not None): + raise ValueError("Provide only noct or k, not both.") + elif k is None: + # factor of 0.1 converts irradiance from W/m2 to mW/cm2 + return temp_air + (noct - 20.) / 80. * poa_global * 0.1 + elif noct is None: + # k assumes irradiance in W.m-2, dismissing 0.1 factor + return temp_air + k * poa_global def _fuentes_hconv(tave, windmod, tinoct, temp_delta, xlen, tilt, From 50da75f5aab70a26d27cab385870340a6ec2fbdc Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Thu, 31 Jul 2025 10:09:10 +0100 Subject: [PATCH 02/12] Updates whatsnew file for v13.1 --- docs/sphinx/source/whatsnew/v0.13.1.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index 14ae31170b..7ab9efc4fb 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -19,8 +19,11 @@ Bug fixes Enhancements ~~~~~~~~~~~~ - - +* Rename parameter name ``aparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. + (:issue:`2479`, :pull:`2480`) +* Adds k coefficient in :py:func:`~pvlib.temperature.ross` + (:issue:`2506`, :pull:`2521`) + Documentation ~~~~~~~~~~~~~ * Substantiate definitions of solar/surface azimuth/zenith and aoi on the @@ -48,4 +51,5 @@ Contributors ~~~~~~~~~~~~ * Elijah Passmore (:ghuser:`eljpsm`) * Rajiv Daxini (:ghuser:`RDaxini`) -* Omar Bahamida (:ghuser:`OmarBahamida`) \ No newline at end of file +* Omar Bahamida (:ghuser:`OmarBahamida`) +* Rodrigo Amaro e Silva (:ghuser:`ramaroesilva`) \ No newline at end of file From 48866ee84d6064e18faeb99602a07b03a0bf0bfd Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Thu, 31 Jul 2025 18:09:30 +0100 Subject: [PATCH 03/12] Corrects minor typos. --- docs/sphinx/source/whatsnew/v0.13.1.rst | 2 +- pvlib/temperature.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index 7ab9efc4fb..8dd1c0da18 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -21,7 +21,7 @@ Enhancements ~~~~~~~~~~~~ * Rename parameter name ``aparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. (:issue:`2479`, :pull:`2480`) -* Adds k coefficient in :py:func:`~pvlib.temperature.ross` +* Add k coefficient in :py:func:`~pvlib.temperature.ross` (:issue:`2506`, :pull:`2521`) Documentation diff --git a/pvlib/temperature.py b/pvlib/temperature.py index fac2a4be0c..c131434343 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -686,8 +686,8 @@ def ross(poa_global, temp_air, noct=None, k=None): Orlando, FL. .. [2] E. Skoplaki and J. A. Palyvos, “Operating temperature of photovoltaic modules: A survey of pertinent correlations,” Renewable - Energy, vol. 34, no. 1, pp. 23–29, Jan. 2009, doi: - https://doi.org/10.1016/j.renene.2008.04.009. + Energy, vol. 34, no. 1, pp. 23–29, Jan. 2009, + doi: `10.1016/j.renene.2008.04.009` ''' if (noct is None) & (k is None): raise ValueError("noct or k need to be provided as numeric input.") From c917c6103b318429e433d3ea28a49b7bd321d8e3 Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Thu, 31 Jul 2025 18:15:37 +0100 Subject: [PATCH 04/12] Corrects doi formatting. --- pvlib/temperature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index c131434343..013a9fe91c 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -687,7 +687,7 @@ def ross(poa_global, temp_air, noct=None, k=None): .. [2] E. Skoplaki and J. A. Palyvos, “Operating temperature of photovoltaic modules: A survey of pertinent correlations,” Renewable Energy, vol. 34, no. 1, pp. 23–29, Jan. 2009, - doi: `10.1016/j.renene.2008.04.009` + :doi:`10.1016/j.renene.2008.04.009` ''' if (noct is None) & (k is None): raise ValueError("noct or k need to be provided as numeric input.") From b3b10fc89949c37303e3fa117d6d0b4f7e6a58fa Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Thu, 31 Jul 2025 23:03:08 +0100 Subject: [PATCH 05/12] Improve docstring and initial test messages. --- pvlib/temperature.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 013a9fe91c..d00caeb9a7 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -658,25 +658,25 @@ def ross(poa_global, temp_air, noct=None, k=None): where :math:`S` is the plane of array irradiance in :math:`mW/{cm}^2`. This function expects irradiance in :math:`W/m^2`. - Reference values for k are provided in [2]_, covering different types + Representative values for k are provided in [2]_, covering different types of mounting and module structure. +---------------------+-----------+ | Mounting | :math:`k` | +=====================+===========+ - | well_cooled | 0.02 | + | Well cooled | 0.02 | +---------------------+-----------+ - | free_standing | 0.0208 | + | Free standing | 0.0208 | +---------------------+-----------+ - | flat_on_roof | 0.026 | + | Flat on roof | 0.026 | +---------------------+-----------+ - | not_so_well_cooled | 0.0342 | + | Not so well cooled | 0.0342 | +---------------------+-----------+ - | transparent_pv | 0.0455 | + | Transparent pv | 0.0455 | +---------------------+-----------+ - | facade_integrated | 0.0538 | + | Facade integrated | 0.0538 | +---------------------+-----------+ - | on_sloped_roof | 0.0563 | + | On sloped roof | 0.0563 | +---------------------+-----------+ References @@ -690,9 +690,9 @@ def ross(poa_global, temp_air, noct=None, k=None): :doi:`10.1016/j.renene.2008.04.009` ''' if (noct is None) & (k is None): - raise ValueError("noct or k need to be provided as numeric input.") + raise ValueError("Either noct or k need is required.") elif (noct is not None) & (k is not None): - raise ValueError("Provide only noct or k, not both.") + raise ValueError("Provide only one of noct or k, not both.") elif k is None: # factor of 0.1 converts irradiance from W/m2 to mW/cm2 return temp_air + (noct - 20.) / 80. * poa_global * 0.1 From 70616e15d69eb112f0037f3bbd3f8b1e4bc4c665 Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Fri, 1 Aug 2025 15:50:47 +0100 Subject: [PATCH 06/12] Adds tests for temperature.ross --- tests/test_temperature.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/tests/test_temperature.py b/tests/test_temperature.py index bf72a16e22..a28ba1ffc0 100644 --- a/tests/test_temperature.py +++ b/tests/test_temperature.py @@ -152,11 +152,38 @@ def test_faiman_rad_ir(): 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) + # single values + result1 = temperature.ross(1000,30,noct=50) + result2 = temperature.ross(1000,30,k=0.0375) + + expected = 67.5 + assert_allclose(expected, result1) + assert_allclose(expected, result2) + + # pd.Series + times = pd.date_range('2025-07-30 14:00', '2025-07-30 16:00', freq='H') + + df = pd.DataFrame({'t_air': np.array([20,30,40]), + 'ghi': np.array([800,700,600])}, + index=times) + + result1 = temperature.ross(df['ghi'],df['t_air'],noct=50) + result2 = temperature.ross(df['ghi'],df['t_air'],k=0.0375) + + expected = pd.Series([50,56.25,62.5],index=times) + assert_allclose(expected, result1) + assert_allclose(expected, result2) + + # np.array + ghi_array = df['ghi'].values + t_air_array = df['t_air'].values + + result1 = temperature.ross(ghi_array, t_air_array, noct=50) + result2 = temperature.ross(ghi_array, t_air_array, k=0.0375) + + expected = expected.values + assert_allclose(expected, result1) + assert_allclose(expected, result2) def test_faiman_series(): From b71960b3e3f7a5b7cbb76f0793f9ae016f8cea3b Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Fri, 1 Aug 2025 15:56:12 +0100 Subject: [PATCH 07/12] Changes variables in test from int to float. --- tests/test_temperature.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_temperature.py b/tests/test_temperature.py index a28ba1ffc0..7de1e0a21f 100644 --- a/tests/test_temperature.py +++ b/tests/test_temperature.py @@ -153,24 +153,24 @@ def test_faiman_rad_ir(): def test_ross(): # single values - result1 = temperature.ross(1000,30,noct=50) - result2 = temperature.ross(1000,30,k=0.0375) + result1 = temperature.ross(1000.,30.,noct=50) + result2 = temperature.ross(1000.,30.,k=0.0375) expected = 67.5 assert_allclose(expected, result1) assert_allclose(expected, result2) # pd.Series - times = pd.date_range('2025-07-30 14:00', '2025-07-30 16:00', freq='H') + times = pd.date_range('2025-07-30 14:00', '2025-07-30 16:00', freq='h') - df = pd.DataFrame({'t_air': np.array([20,30,40]), - 'ghi': np.array([800,700,600])}, + df = pd.DataFrame({'t_air': np.array([20.,30.,40.]), + 'ghi': np.array([800.,700.,600.])}, index=times) - result1 = temperature.ross(df['ghi'],df['t_air'],noct=50) + result1 = temperature.ross(df['ghi'],df['t_air'],noct=50.) result2 = temperature.ross(df['ghi'],df['t_air'],k=0.0375) - expected = pd.Series([50,56.25,62.5],index=times) + expected = pd.Series([50.,56.25,62.5],index=times) assert_allclose(expected, result1) assert_allclose(expected, result2) @@ -178,7 +178,7 @@ def test_ross(): ghi_array = df['ghi'].values t_air_array = df['t_air'].values - result1 = temperature.ross(ghi_array, t_air_array, noct=50) + result1 = temperature.ross(ghi_array, t_air_array, noct=50.) result2 = temperature.ross(ghi_array, t_air_array, k=0.0375) expected = expected.values From 3d8db5ea186cdcba6c0b1e3f78b2d61e5771e350 Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Fri, 1 Aug 2025 16:05:50 +0100 Subject: [PATCH 08/12] Addresses flake8 issues. --- tests/test_temperature.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_temperature.py b/tests/test_temperature.py index 7de1e0a21f..be9e62fff4 100644 --- a/tests/test_temperature.py +++ b/tests/test_temperature.py @@ -153,8 +153,8 @@ def test_faiman_rad_ir(): def test_ross(): # single values - result1 = temperature.ross(1000.,30.,noct=50) - result2 = temperature.ross(1000.,30.,k=0.0375) + result1 = temperature.ross(1000., 30., noct=50) + result2 = temperature.ross(1000., 30., k=0.0375) expected = 67.5 assert_allclose(expected, result1) @@ -163,14 +163,14 @@ def test_ross(): # pd.Series times = pd.date_range('2025-07-30 14:00', '2025-07-30 16:00', freq='h') - df = pd.DataFrame({'t_air': np.array([20.,30.,40.]), - 'ghi': np.array([800.,700.,600.])}, + df = pd.DataFrame({'t_air': np.array([20., 30., 40.]), + 'ghi': np.array([800., 700., 600.])}, index=times) - result1 = temperature.ross(df['ghi'],df['t_air'],noct=50.) - result2 = temperature.ross(df['ghi'],df['t_air'],k=0.0375) + result1 = temperature.ross(df['ghi'], df['t_air'], noct=50.) + result2 = temperature.ross(df['ghi'], df['t_air'], k=0.0375) - expected = pd.Series([50.,56.25,62.5],index=times) + expected = pd.Series([50., 56.25, 62.5], index=times) assert_allclose(expected, result1) assert_allclose(expected, result2) From 058e1f0072139778b019d78efb2913d9bd97e99b Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Fri, 1 Aug 2025 16:11:22 +0100 Subject: [PATCH 09/12] Fixes flake8 issue. --- tests/test_temperature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_temperature.py b/tests/test_temperature.py index be9e62fff4..3f3c3d03f1 100644 --- a/tests/test_temperature.py +++ b/tests/test_temperature.py @@ -173,7 +173,7 @@ def test_ross(): expected = pd.Series([50., 56.25, 62.5], index=times) assert_allclose(expected, result1) assert_allclose(expected, result2) - + # np.array ghi_array = df['ghi'].values t_air_array = df['t_air'].values From 97535a0a6266e3ea486327728fe664b0f6edfd7f Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Fri, 1 Aug 2025 17:49:18 +0100 Subject: [PATCH 10/12] Covers errors in testing temperature.ross --- tests/test_temperature.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_temperature.py b/tests/test_temperature.py index 3f3c3d03f1..ac5500f3bd 100644 --- a/tests/test_temperature.py +++ b/tests/test_temperature.py @@ -186,6 +186,13 @@ def test_ross(): assert_allclose(expected, result2) +def test_ross_errors(): + with pytest.raises(ValueError, match='Either noct or k need is required'): + temperature.ross(1000., 30.) + with pytest.raises(ValueError, match='Provide only one of noct or k'): + temperature.ross(1000., 30., noct=45., k=0.02) + + 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) From 0cc4b5faec6ff58c1d667b048e5ee7df4ba664a7 Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Fri, 1 Aug 2025 22:05:27 +0100 Subject: [PATCH 11/12] Improves documentation on temperature.ross --- pvlib/temperature.py | 48 ++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index d00caeb9a7..818a0e6693 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -659,25 +659,32 @@ def ross(poa_global, temp_air, noct=None, k=None): This function expects irradiance in :math:`W/m^2`. Representative values for k are provided in [2]_, covering different types - of mounting and module structure. - - +---------------------+-----------+ - | Mounting | :math:`k` | - +=====================+===========+ - | Well cooled | 0.02 | - +---------------------+-----------+ - | Free standing | 0.0208 | - +---------------------+-----------+ - | Flat on roof | 0.026 | - +---------------------+-----------+ - | Not so well cooled | 0.0342 | - +---------------------+-----------+ - | Transparent pv | 0.0455 | - +---------------------+-----------+ - | Facade integrated | 0.0538 | - +---------------------+-----------+ - | On sloped roof | 0.0563 | - +---------------------+-----------+ + of mounting and degrees of back ventialtion. The naming designations, + however, are adapted from [3]_ to enhance clarity and usability. + + +--------------------------------------+-----------+ + | Mounting | :math:`k` | + +======================================+===========+ + | Sloped roof, well ventilated | 0.02 | + +--------------------------------------+-----------+ + | Free-standing system | 0.0208 | + +--------------------------------------+-----------+ + | Flat roof, well ventilated | 0.026 | + +--------------------------------------+-----------+ + | Sloped roof, poorly ventilated | 0.0342 | + +--------------------------------------+-----------+ + | Facade integrated, semi-ventilated | 0.0455 | + +--------------------------------------+-----------+ + | Facade integrated, poorly ventilted | 0.0538 | + +--------------------------------------+-----------+ + | Sloped roof, non-ventilated | 0.0563 | + +--------------------------------------+-----------+ + + It is also worth noting that the semi-ventilated facade case refers to + partly transparent compound glass insulation modules, while the non- + ventilated case corresponds to opaque, insulated PV-cladding elements. + However, the emphasis in [3]_ appears to be on ventilation conditions + rather than module construction. References ---------- @@ -688,6 +695,9 @@ def ross(poa_global, temp_air, noct=None, k=None): photovoltaic modules: A survey of pertinent correlations,” Renewable Energy, vol. 34, no. 1, pp. 23–29, Jan. 2009, :doi:`10.1016/j.renene.2008.04.009` + .. [3] T. Nordmann and D. Clavadetscher, “Understanding temperature + effects on PV system performance," Proceedings of 3rd World Conference + on Photovoltaic Energy Conversion, May 2003. ''' if (noct is None) & (k is None): raise ValueError("Either noct or k need is required.") From fd72680bbb7e5b8ce2e748f590fa1bfb31c588b6 Mon Sep 17 00:00:00 2001 From: Rodrigo Amaro e Silva Date: Fri, 1 Aug 2025 22:07:12 +0100 Subject: [PATCH 12/12] Corrects minor typo. --- pvlib/temperature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 818a0e6693..a276714fca 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -695,7 +695,7 @@ def ross(poa_global, temp_air, noct=None, k=None): photovoltaic modules: A survey of pertinent correlations,” Renewable Energy, vol. 34, no. 1, pp. 23–29, Jan. 2009, :doi:`10.1016/j.renene.2008.04.009` - .. [3] T. Nordmann and D. Clavadetscher, “Understanding temperature + .. [3] T. Nordmann and L. Clavadetscher, “Understanding temperature effects on PV system performance," Proceedings of 3rd World Conference on Photovoltaic Energy Conversion, May 2003. '''