diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 846a8f7701..0e8c9b0f9c 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -268,6 +268,15 @@ PVWatts model pvsystem.pvwatts_dc pvsystem.pvwatts_ac pvsystem.pvwatts_losses + pvsystem.pvwatts_losses + +PVsyst model +------------ + +.. autosummary:: + :toctree: generated/ + + pvsystem.pvsyst_celltemp Other ----- diff --git a/docs/sphinx/source/whatsnew/v0.6.1.rst b/docs/sphinx/source/whatsnew/v0.6.1.rst index 92c2d48589..c4c6b59a72 100644 --- a/docs/sphinx/source/whatsnew/v0.6.1.rst +++ b/docs/sphinx/source/whatsnew/v0.6.1.rst @@ -57,6 +57,7 @@ Enhancements * Add warning message when :py:func:`pvlib.spa` is reloaded. (:issue:`401`) * Add option for :py:func:`pvlib.irradiance.disc` to use relative airmass by supplying `pressure=None`. (:issue:`449`) +* Created :py:func:`pvlib.pvsystem.pvsyst_celltemp` to implement PVsyst's cell temperature model. (:issue:`552`) Bug fixes @@ -86,4 +87,5 @@ Contributors * Cliff Hansen (:ghuser:`cwhanse`) * Mark Mikofski (:ghuser:`mikofski`) * Anton Driesse (:ghuser:`adriesse`) +* Cameron Stark (:ghuser:`camerontstark`) * Jonathan Gaffiot (:ghuser:`jgaffiot`) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 64fa54d032..6215c1a73d 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1887,6 +1887,87 @@ def sapm_celltemp(poa_global, wind_speed, temp_air, return pd.DataFrame({'temp_cell': temp_cell, 'temp_module': temp_module}) +def pvsyst_celltemp(poa_global, wind_speed, temp_air, eta_m=0.1, + alpha_absorption=0.9, temp_model="freestanding"): + """ + Calculate cell temperature using the PVSyst model. + + Parameters + ---------- + poa_global : numeric + Total incident irradiance in W/m^2. + + wind_speed : numeric + Wind speed in m/s at a height of 10 meters. + + temp_air : numeric + Ambient dry bulb temperature in degrees C. + + eta_m : numeric + Module external efficiency as a fraction, i.e., DC power / poa_global. + + alpha_absorption : float + Absorption coefficient, default is 0.9. + + temp_model : string, tuple, or list, default 'freestanding' (no dict) + Model to be used. + + If string, can be: + + * 'freestanding' (default) + Modules with rear surfaces exposed to open air (e.g. rack + mounted). + * 'insulated' + Modules with rear surfaces in close proximity to another + surface (e.g. roof mounted). + + If tuple/list, supply parameters in the following order: + + * natural_convenction_coeff : float + Natural convection coefficient. Freestanding default is 29, + fully insulated arrays is 15. + + * forced_convection_coeff : float + Forced convection coefficient, default is 0. + + Returns + ------- + temp_cell : numeric or Series + Cell temperature in degrees Celsius + + References + ---------- + [1]"PVsyst 6 Help", Files.pvsyst.com, 2018. [Online]. Available: + http://files.pvsyst.com/help/index.html. [Accessed: 10- Dec- 2018]. + + [2] Faiman, D. (2008). "Assessing the outdoor operating temperature of + photovoltaic modules." Progress in Photovoltaics 16(4): 307-315. + """ + + temp_models = {"freestanding": (29.0, 0), "insulated": (15.0, 0)} + + if isinstance(temp_model, str): + natural_convenction_coeff, forced_convection_coeff = temp_models[ + temp_model.lower() + ] + elif isinstance(temp_model, (tuple, list)): + natural_convenction_coeff, forced_convection_coeff = temp_model + else: + raise TypeError( + "Please format temp_model as a str, or tuple/list." + ) + + combined_convection_coeff = ( + forced_convection_coeff * wind_speed + ) + natural_convenction_coeff + + absorption_coeff = alpha_absorption * poa_global * (1 - eta_m) + temp_difference = absorption_coeff / combined_convection_coeff + temp_cell = temp_air + temp_difference + + return temp_cell + + def sapm_spectral_loss(airmass_absolute, module): """ Calculates the SAPM spectral loss coefficient, F1. diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index c0e89c5095..bff3277a54 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -1078,6 +1078,46 @@ def test_PVSystem_sapm_celltemp(mocker): assert out.shape == (1, 2) +def test_pvsyst_celltemp_default(): + default = pvsystem.pvsyst_celltemp(900, 5, 20, 0.1) + assert_allclose(default, 45.137, 0.001) + + +def test_pvsyst_celltemp_non_model(): + tup_non_model = pvsystem.pvsyst_celltemp(900, 5, 20, 0.1, + temp_model=(23.5, 6.25)) + assert_allclose(tup_non_model, 33.315, 0.001) + + list_non_model = pvsystem.pvsyst_celltemp(900, 5, 20, 0.1, + temp_model=[26.5, 7.68]) + assert_allclose(list_non_model, 31.233, 0.001) + + +def test_pvsyst_celltemp_model_wrong_type(): + with pytest.raises(TypeError): + pvsystem.pvsyst_celltemp( + 900, 5, 20, 0.1, + temp_model={"won't": 23.5, "work": 7.68}) + + +def test_pvsyst_celltemp_model_non_option(): + with pytest.raises(KeyError): + pvsystem.pvsyst_celltemp( + 900, 5, 20, 0.1, + temp_model="not_an_option") + + +def test_pvsyst_celltemp_with_index(): + times = pd.DatetimeIndex(start="2015-01-01", end="2015-01-02", freq="12H") + temps = pd.Series([0, 10, 5], index=times) + irrads = pd.Series([0, 500, 0], index=times) + winds = pd.Series([10, 5, 0], index=times) + + pvtemps = pvsystem.pvsyst_celltemp(irrads, winds, temps) + expected = pd.Series([0.0, 23.96551, 5.0], index=times) + assert_series_equal(expected, pvtemps) + + def test_adrinverter(sam_data): inverters = sam_data['adrinverter'] testinv = 'Ablerex_Electronics_Co___Ltd___' \