diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 077a5e121d..2e882e9a46 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 50e01401f5..b6590283f2 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 @@ -35,3 +37,5 @@ Requirements Contributors ~~~~~~~~~~~~ * Kevin Anderson (:ghuser:`kanderso-nrel`) +* Will Holmgren (:ghuser:`wholmgren`) +* Cliff Hansen (:ghuser:`cwhanse`) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index fecc93075e..6be4237a17 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -245,9 +245,16 @@ def get_aoi(self, solar_zenith, solar_azimuth): The angle of incidence """ - aoi = irradiance.aoi(self.surface_tilt, self.surface_azimuth, - solar_zenith, solar_azimuth) - return aoi + try: + aoi = [irradiance.aoi(t, a, solar_zenith, solar_azimuth) + for t, a in zip(self.surface_tilt, self.surface_azimuth)] + if isinstance(self.surface_tilt, np.ndarray): + aoi = np.asarray(aoi) + return aoi + except TypeError: + return irradiance.aoi(self.surface_tilt, self.surface_azimuth, + solar_zenith, solar_azimuth) + def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, dni_extra=None, airmass=None, model='haydavies', @@ -293,15 +300,20 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, if airmass is None: airmass = atmosphere.get_relative_airmass(solar_zenith) - return irradiance.get_total_irradiance(self.surface_tilt, - self.surface_azimuth, - solar_zenith, solar_azimuth, - dni, ghi, dhi, - dni_extra=dni_extra, - airmass=airmass, - model=model, - albedo=self.albedo, - **kwargs) + try: + poa = [irradiance.get_total_irradiance( + t, a, solar_zenith, solar_azimuth, dni, ghi, dhi, + dni_extra=dni_extra, airmass=airmass, model=model, + albedo=self.albedo, **kwargs) + for t, a in zip(self.surface_tilt, self.surface_azimuth)] + if isinstance(self.surface_tilt, np.ndarray): + poa = np.asarray(poa) + return poa + except TypeError: + return irradiance.get_total_irradiance( + self.surface_tilt, self.surface_azimuth, solar_zenith, + solar_azimuth, dni, ghi, dhi, dni_extra=dni_extra, + airmass=airmass, model=model, albedo=self.albedo, **kwargs) def get_iam(self, aoi, iam_model='physical'): """ @@ -370,7 +382,9 @@ def calcparams_desoto(self, effective_irradiance, temp_cell, **kwargs): 'R_s', 'alpha_sc', 'EgRef', 'dEgdT', 'irrad_ref', 'temp_ref'], self.module_parameters) - + if isinstance(effective_irradiance, list): + return [calcparams_desoto(ee, tc, **kwargs) + for ee, tc in zip(effective_irradiance, temp_cell)] return calcparams_desoto(effective_irradiance, temp_cell, **kwargs) def calcparams_cec(self, effective_irradiance, temp_cell, **kwargs): @@ -524,7 +538,7 @@ def sapm_spectral_loss(self, airmass_absolute): """ return sapm_spectral_loss(airmass_absolute, self.module_parameters) - def sapm_effective_irradiance(self, poa_direct, poa_diffuse, + def sapm_effective_irradiance(self, total_irrad, airmass_absolute, aoi, reference_irradiance=1000): """ @@ -534,11 +548,10 @@ def sapm_effective_irradiance(self, poa_direct, poa_diffuse, Parameters ---------- - poa_direct : numeric - The direct irradiance incident upon the module. [W/m2] - - poa_diffuse : numeric - The diffuse irradiance incident on module. [W/m2] + total_irrad : Dataframe + Contains 'poa_direct', the direct irradiance incident upon the + module [W/m2], and 'poa_diffuse', the diffuse irradiance incident + on the module [W/m2]. airmass_absolute : numeric Absolute airmass. [unitless] @@ -551,9 +564,16 @@ def sapm_effective_irradiance(self, poa_direct, poa_diffuse, effective_irradiance : numeric The SAPM effective irradiance. [W/m2] """ - return sapm_effective_irradiance( - poa_direct, poa_diffuse, airmass_absolute, aoi, - self.module_parameters) + try: + ee = [sapm_effective_irradiance( + df['poa_direct'], df['poa_diffuse'], airmass_absolute, aoi, + self.module_parameters) + for df, aoi in zip(total_irrad, aoi)] + return ee + except TypeError: + return sapm_effective_irradiance( + total_irrad['poa_direct'], total_irrad['poa_diffuse'], + airmass_absolute, aoi, self.module_parameters) def pvsyst_celltemp(self, poa_global, temp_air, wind_speed=1.0): """Uses :py:func:`temperature.pvsyst_cell` to calculate cell diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 1d98736b4f..b1966faea9 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 ignores the effects of wind. 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 diff --git a/pvlib/tests/test_temperature.py b/pvlib/tests/test_temperature.py index 245c5d2807..f3c3fa9fa1 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)