diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index d83667b302..23e6caf24b 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -20,6 +20,10 @@ Bug fixes Enhancements ~~~~~~~~~~~~ +* Rename parameter name ``aparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. + (:issue:`2479`, :pull:`2480`) +* Add k coefficient in :py:func:`~pvlib.temperature.ross` + (:issue:`2506`, :pull:`2521`) * Add :py:func:`pvlib.iotools.get_nasa_power` to retrieve data from NASA POWER free API. (:pull:`2500`) * :py:func:`pvlib.spectrum.spectral_factor_firstsolar` no longer emits warnings @@ -54,4 +58,5 @@ Contributors * Ioannis Sifnaios (:ghuser:`IoannisSifnaios`) * Rajiv Daxini (:ghuser:`RDaxini`) * Omar Bahamida (:ghuser:`OmarBahamida`) +* Rodrigo Amaro e Silva (:ghuser:`ramaroesilva`) * Kevin Anderson (:ghuser:`kandersolar`) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 6c274d79b7..a276714fca 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,62 @@ 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`. + Representative values for k are provided in [2]_, covering different types + 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 ---------- .. [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:`10.1016/j.renene.2008.04.009` + .. [3] T. Nordmann and L. Clavadetscher, “Understanding temperature + effects on PV system performance," Proceedings of 3rd World Conference + on Photovoltaic Energy Conversion, May 2003. ''' - # 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("Either noct or k need is required.") + elif (noct is not None) & (k is not None): + 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 + 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, diff --git a/tests/test_temperature.py b/tests/test_temperature.py index bf72a16e22..ac5500f3bd 100644 --- a/tests/test_temperature.py +++ b/tests/test_temperature.py @@ -152,11 +152,45 @@ 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_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():