diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index 2b2eb23e42..a039a627a5 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -20,6 +20,8 @@ Bug fixes Enhancements ~~~~~~~~~~~~ +* Add k coefficient in :py:func:`~pvlib.temperature.ross` + (:issue:`2506`, :pull:`2521`) * Add iotools functions to retrieve irradiance and weather data from Meteonorm: :py:func:`~pvlib.iotools.get_meteonorm_forecast_basic`, :py:func:`~pvlib.iotools.get_meteonorm_forecast_precision`, :py:func:`~pvlib.iotools.get_meteonorm_observation_realtime`, :py:func:`~pvlib.iotools.get_meteonorm_observation_training`, @@ -65,5 +67,6 @@ Contributors * Ioannis Sifnaios (:ghuser:`IoannisSifnaios`) * Rajiv Daxini (:ghuser:`RDaxini`) * Omar Bahamida (:ghuser:`OmarBahamida`) +* Rodrigo Amaro e Silva (:ghuser:`ramaroesilva`) * Kevin Anderson (:ghuser:`kandersolar`) * Mikaella Brewer (:ghuser:`brwerx`) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 6c274d79b7..53920ee8ab 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. @@ -630,14 +630,19 @@ def ross(poa_global, temp_air, noct): Parameters ---------- poa_global : numeric - Total incident irradiance. [W/m^2] + Total incident irradiance. [W/m⁻²] temp_air : numeric Ambient dry bulb temperature. [C] - noct : numeric + noct : numeric, optional Nominal operating cell temperature [C], determined at conditions of - 800 W/m^2 irradiance, 20 C ambient air temperature and 1 m/s wind. + 800 W/m⁻² irradiance, 20 C ambient air temperature and 1 m/s wind. + If ``noct`` is not provided, ``k`` is required. + k: numeric, optional + Ross coefficient [Km²W⁻¹], which is an alternative to employing + NOCT in Ross's equation. If ``k`` is not provided, ``noct`` is + required. Returns ------- @@ -650,19 +655,62 @@ def ross(poa_global, temp_air, noct): .. 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`. + T_{C} = T_{a} + \frac{NOCT - 20}{80} S = T_{a} + k × S + + where :math:`S` is the plane of array irradiance in mWcm⁻². + This function expects irradiance in Wm⁻². + + 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 ventilated | 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 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..e482df6214 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 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():