Source code for pvpltools.iec61853

"""
This module implements the calculations energy rating calculations
described in standard IEC-61853.

The four main calculation steps are:

    1. Calculate the angle-of-incidence correction using the functions:
        - martin_ruiz()
        - martin_ruiz_diffuse()
    2. Evaluate the spectral factor using the functions:
        - convert_to_banded()
        - calc_spectral_factor()
    3. Estimate the operating temperature using the function:
        - faiman()
    4. Determine the module efficiency using the class:
        - BilinearInterpolator()

Copyright (c) 2019-2021 Anton Driesse, PV Performance Labs.
"""

import numpy as np
import pandas as pd

from scipy.interpolate import RegularGridInterpolator

__all__ = [
    'convert_to_banded',
    'calc_spectral_factor',
    'BilinearInterpolator',
    'martin_ruiz',
    'martin_ruiz_diffuse',
    'faiman',
    ]

# This constant defines the fixed boundaries between the 29 spectral bands
# used in the IEC 61853-4 climate profiles.

SPECTRAL_BAND_EDGES = (
     306.8,  327.8,  362.5,  407.5,  452.0,  517.7,  540.0,  549.5,
     566.6,  605.0,  625.0,  666.7,  684.2,  704.4,  742.6,  791.5,
     844.5,  889.0,  974.9, 1045.7, 1194.2, 1515.9, 1613.5, 1964.8,
     2153.5, 2275.2, 3001.9, 3635.4, 3991.0, 4605.65
    )

# This constant defines irradiance of the AM15G spectrum in each of the 29
# spectral bands used in the IEC 61853-4 climate profiles.
# NB: the value in the last band represents only the range 3991-4000nm.

BANDED_AM15G = (
     3.69618,   16.60690,  34.22227,  56.13480,  101.36658,  33.98919,
     14.45719,  25.91979,  56.57334,  29.05096,  58.35457,   24.50170,
     25.22513,  45.09050,  52.90410,  52.42015,  42.28474,   47.29518,
     49.45154,  61.54425,  67.93106,  24.83419,  34.04581,   13.98155,
     9.11025,   9.07606,   4.22986,   3.04112,   0.06470
    )


[docs] def convert_to_banded(spectral_response): """ Calculate the mean spectral response in standard wavelength bands. The mean value for each band is calculated as the area under the linearly interpolated spectral response (SR) curve between band edges, divided by the width of the band. The band edges are defined in IEC 61853-4. [1]_ Parameters ---------- spectral_response : pandas.Series Spectral response. The index of spectral_response must contain wavelengths [nm]; the values in spectral_response may be relative or absolute. Returns ------- np.array Mean values of the spectral_response in the specified wavelength bands. Notes: ------ - The trapezoid method is used to calculate the area under the SR curve. - The SR at the band edges is estimated by linear interpolation. - Negative values in the SR are replaced with zero. References ---------- .. [1] "IEC 61853-4 Photovoltaic (PV) module performance testing and energy rating - Part 4: Standard reference climatic profiles". IEC, Geneva, 2018. Author: Anton Driesse, PV Performance Labs """ sr = spectral_response band_edges = SPECTRAL_BAND_EDGES # remember these limits for calculating the areas sr_left = sr.index.min() sr_right = sr.index.max() # insert extra points into the SR at the band edges extra_wavelengths = set(band_edges) - set(sr.index) sr = pd.concat((sr, pd.Series(np.nan, extra_wavelengths))) sr = sr.sort_index() sr = sr.clip(0.0) sr = sr.interpolate(method='index', limit_area='inside') sr = sr.fillna(0.0) # calculate the mean value of the SR within each band band_means = [] for band_left, band_right in zip(band_edges[:-1], band_edges[1:]): limit_left = max(sr_left, band_left) limit_right = min(sr_right, band_right) band = sr.loc[limit_left:limit_right] area = np.trapz(band, band.index) width = band_right - band_left band_means.append(area / width) return np.array(band_means)
[docs] def calc_spectral_factor(banded_irradiance, banded_responsivity, integration_limit=None): """ Calculate the spectral correction/mismatch factor(s) for the given spectral irradiance and device responsivity. Parameters ---------- banded_irradiance : 1-D or 2-D array_like Spectral irradiance integrated in 29 bands [W/m²]. banded_responsivity : 1-D array_like Spectral responsivity averaged in 29 bands [unitless]. integration_limit : integer or None, default None Last spectral band to use for the integrals in the spectral mismatch calculation. When None is specified, the banded_irradiance is integrated over the full available range (29 bands) and the value 1000 W/m² is used for the integrated reference spectrum. See Notes section below for further information. Raises ------ ValueError If the number of bands in the arguments is incorrect. Returns ------- scalar or 1-D array The calculated spectral correction factors [unitless]. See also -------- SPECTRAL_BAND_EDGES, BANDED_AM15G, convert_to_banded Notes ----- The calculation method used here does not correspond precisely to the description in IEC 61853-3 [1]_ because the latter has inconsistencies. In particular: - The standard specifies the limits of 300 and 4000 nm for integration. - But the outer edges of the spectral bands in the standard climate profile data are 306.8 and 4605.65 nm, and there is no band edge at 4000 nm; therefore, it is not possible to integrate using the specified limits. - The broadband reference irradiance in eq. 6 of [1]_ is 1000 W/m². - This implies integration limits of 280 nm and infinity for AM15G. - The broadband irradiance in the climate profiles (also used in eq. 6) is equal to the sum of the all 29 spectral irradiance bands. - This implies an integration from 306.8 to 4605.65 nm. In this function the lower integration limit is fixed at the band edge 306.8 nm. The integral of AM15G over the range 280-306.8 nm is 0.066 W/m², therefore this choice does not significantly affect the spectral factor. The upper integration limit is more relevant since the integral of AM15G from 4000 nm to infinity is 2.53 W/m². Using the default option, `integration_limit=None`, eq. 6 is taken at face value and the implied integration limits are used. This is equivalent to the recommendation in [2]_. It is also possible to set the `integration_limit` to a specific band number, which is then used as upper limit for all four integrals in the mismatch calculation. References ---------- .. [1] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018. .. [2] M.Vogt et al "PV module energy rating standard IEC 61853‐3 intercomparison and best practice guidelines for implementation and validation", submitted for publication, 2021. Author: Anton Driesse, PV Performance Labs """ banded_irradiance = np.asanyarray(banded_irradiance) banded_responsivity = np.asanyarray(banded_responsivity) if (banded_irradiance.ndim < 1) or (banded_irradiance.shape[-1] != 29): raise ValueError('banded_irradiance must 29 bands') if (banded_responsivity.ndim != 1) or (banded_responsivity.shape[0] != 29): raise ValueError('banded_responsivity must have 29 bands') # the mask is used to select/ignore bands in all the spectra mask = np.ones(29) if integration_limit is None: integrated_am15g = 1000. else: mask[integration_limit:] = 0 integrated_am15g = np.sum(mask * BANDED_AM15G) with np.errstate(invalid='ignore'): uf_real = (np.sum(mask * banded_irradiance * banded_responsivity, -1) / np.sum(mask * banded_irradiance, -1)) uf_am15 = (np.sum(mask * BANDED_AM15G * banded_responsivity) / integrated_am15g) spectral_factor = uf_real / uf_am15 return spectral_factor
[docs] class BilinearInterpolator(RegularGridInterpolator): """ Bilinear interpolation and extrapolation of a matrix of values. The values in the matrix are measurements at various combinations of irradiance and temperature. The matrix may be completely filled, or there may be missing values at high irradiance/low temperature, or at low irradiance/high temperature combinations. These are filled in using the method described in [1]_, which ensures a continuous interpolation/extrapolation surface. Parameters ---------- matrix : pandas.DataFrame The row index of matrix must contain irradiance values and the column index of matrix must contain temperature values. The data in the dataframe can represent anything, but will usually be PV module efficiency. This can be relative or absolute, as fraction or percent. Methods ------- __call__ Notes ----- This class is implemented as a subclass of scipy.interpolate.RegularGridInterpolator. Essentially it ensures the regular grid is completely filled, as required, and that the appropriate options are selected. It also provides some domain-specific documentation. Example ------- >>> eta = pd.DataFrame(index=[1000, 1100], ... columns=[15, 25], ... data=[[ 22.0, 20.0], ... [np.nan, 19.0]]) >>> eta 15 25 1000 22.0 20.0 1100 NaN 19.0 >>> interpolate_eta = BilinearInterpolator(eta) >>> interpolate_eta.values array([[22., 20.], [21., 19.]]) >>> interpolate_eta([900, 1100], [25, 15]) array([21., 21.]) See also -------- scipy.interpolate.RegularGridInterpolator References ---------- .. [1] A. Driesse and J. S. Stein, "From IEC 61853 power measurements to PV system simulations," Sandia Report No. SAND2020-3877, 2020. .. [2] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018. Author: Anton Driesse, PV Performance Labs """
[docs] def __init__(self, matrix): m = matrix.sort_index(axis=0).sort_index(axis=1) num_iterations = max(m.shape) - 1 for i in range(num_iterations): # extrapolate and fill toward upper right corner of matrix m.fillna(m.shift(+1, axis=1) + m.shift(-1, axis=0) - m.shift(-1, axis=0).shift(+1, axis=1), inplace=True) # extrapolate and fill toward lower left corner of matrix m.fillna(m.shift(-1, axis=1) + m.shift(+1, axis=0) - m.shift(+1, axis=0).shift(-1, axis=1), inplace=True) return super().__init__( (m.index, m.columns), m.values, method="linear", bounds_error=False, fill_value=None, )
[docs] def __call__(self, irradiance, temperature): ''' Interpolate/extrapolate at specified conditions Parameters ---------- irradiance, temperature : array_like The conditions to be interpolated/extrapolated from the matrix. ''' return super().__call__((irradiance, temperature))
[docs] def martin_ruiz(aoi, a_r=0.16): r''' Determine the incidence angle modifier (IAM) using the Martin and Ruiz incident angle model. Parameters ---------- aoi : numeric, degrees The angle of incidence between the module normal vector and the sun-beam vector in degrees. a_r : numeric The angular losses coefficient described in equation 3 of [1]_. This is an empirical dimensionless parameter. Values of ``a_r`` are generally on the order of 0.08 to 0.25 for flat-plate PV modules. Returns ------- iam : numeric The incident angle modifier(s) Notes ----- `martin_ruiz` calculates the incidence angle modifier (IAM) as described in [1]_. The information required is the incident angle (AOI) and the angular losses coefficient (a_r). Note that [1]_ has a corrigendum [2]_ which clarifies a mix-up of 'alpha's and 'a's in the former. The incident angle modifier is defined as .. math:: IAM = \frac{1 - \exp(-\cos(\frac{aoi}{a_r}))} {1 - \exp(\frac{-1}{a_r}} which is presented as :math:`AL(\alpha) = 1 - IAM` in equation 4 of [1]_, with :math:`\alpha` representing the angle of incidence AOI. Thus IAM = 1 at AOI = 0, and IAM = 0 at AOI = 90. This equation is only valid for -90 <= aoi <= 90, therefore `iam` is constrained to 0.0 outside this interval. References ---------- .. [1] N. Martin and J. M. Ruiz, "Calculation of the PV modules angular losses under field conditions by means of an analytical model", Solar Energy Materials & Solar Cells, vol. 70, pp. 25-38, 2001. .. [2] N. Martin and J. M. Ruiz, "Corrigendum to 'Calculation of the PV modules angular losses under field conditions by means of an analytical model'", Solar Energy Materials & Solar Cells, vol. 110, pp. 154, 2013. Author: Anton Driesse, PV Performance Labs ''' aoi_input = aoi aoi = np.asanyarray(aoi) a_r = np.asanyarray(a_r) if np.any(np.less_equal(a_r, 0)): raise ValueError("The parameter 'a_r' cannot be zero or negative.") from numpy import cos, radians with np.errstate(invalid='ignore'): iam = (1 - np.exp(-cos(radians(aoi)) / a_r)) / (1 - np.exp(-1 / a_r)) iam = np.where(np.abs(aoi) >= 90.0, 0.0, iam) if isinstance(aoi_input, pd.Series): iam = pd.Series(iam, index=aoi_input.index) return iam
[docs] def martin_ruiz_diffuse(surface_tilt, a_r=0.16, c1=None, c2=None): ''' Determine the incidence angle modifiers (iam) for diffuse sky and ground-reflected irradiance using the Martin and Ruiz incident angle model. Parameters ---------- surface_tilt: float or array-like, default 0 Surface tilt angles in decimal degrees. The tilt angle is defined as degrees from horizontal (e.g. surface facing up = 0, surface facing horizon = 90) surface_tilt must be in the range [0, 180] a_r : numeric The angular losses coefficient described in equation 3 of [1]_. This is an empirical dimensionless parameter. Values of a_r are generally on the order of 0.08 to 0.25 for flat-plate PV modules. a_r must be greater than zero. c1 : float First fitting parameter for the expressions that approximate the integral of diffuse irradiance coming from different directions. If c1 is None, the constant 4 / 3 / pi (~0.4244) is used [1]_. c2 : float Second fitting parameter for the expressions that approximate the integral of diffuse irradiance coming from different directions. If c2 is None, it will be calculated according to the linear relationship given in [3]_. Returns ------- iam_sky : numeric The incident angle modifier for sky diffuse iam_ground : numeric The incident angle modifier for ground-reflected diffuse Notes ----- Sky and ground modifiers are complementary: iam_sky for tilt = 30 is equal to iam_ground for tilt = 180 - 30. For vertical surfaces, tilt = 90, the two factors are equal. References ---------- .. [1] N. Martin and J. M. Ruiz, "Calculation of the PV modules angular losses under field conditions by means of an analytical model", Solar Energy Materials & Solar Cells, vol. 70, pp. 25-38, 2001. .. [2] N. Martin and J. M. Ruiz, "Corrigendum to 'Calculation of the PV modules angular losses under field conditions by means of an analytical model'", Solar Energy Materials & Solar Cells, vol. 110, pp. 154, 2013. .. [3] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018. Author: Anton Driesse, PV Performance Labs ''' if isinstance(surface_tilt, pd.Series): out_index = surface_tilt.index else: out_index = None surface_tilt = np.asanyarray(surface_tilt) # avoid undefined results for horizontal or upside-down surfaces zeroang = 1e-06 surface_tilt = np.where(surface_tilt == 0, zeroang, surface_tilt) surface_tilt = np.where(surface_tilt == 180, 180 - zeroang, surface_tilt) if c1 is None: # This value is from [1] c1 = 4 / 3 / np.pi if c2 is None: # This equation is from [3] Sect. 7.2 c2 = 0.5 * a_r - 0.154 beta = np.radians(surface_tilt) from numpy import pi, sin, cos, exp # because sin(pi) isn't exactly zero sin_beta = np.where(surface_tilt < 90, sin(beta), sin(pi - beta)) trig_term_sky = sin_beta + (pi - beta - sin_beta) / (1 + cos(beta)) trig_term_gnd = sin_beta + (beta - sin_beta) / (1 - cos(beta)) # noqa: E222 E261 E501 iam_sky = 1 - exp(-(c1 + c2 * trig_term_sky) * trig_term_sky / a_r) iam_gnd = 1 - exp(-(c1 + c2 * trig_term_gnd) * trig_term_gnd / a_r) if out_index is not None: iam_sky = pd.Series(iam_sky, index=out_index, name='iam_sky') iam_gnd = pd.Series(iam_gnd, index=out_index, name='iam_ground') return iam_sky, iam_gnd
[docs] def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84): ''' Calculate cell or module temperature using an empirical heat loss factor model as proposed by Faiman and adopted in the IEC 61853 standard. Model is described in [1]_ and its usage in the IEC 61853 standards [2]_ and [3]_ does not distinguish between cell and module temperature. Parameters ---------- poa_global : numeric Total incident irradiance [W/m^2]. temp_air : numeric Ambient dry bulb temperature [C]. wind_speed : numeric, default 1.0 Wind speed in m/s measured at the same height for which the wind loss factor was determined. The default value 1.0 m/s is the wind speed at module height used to determine NOCT. [m/s] u0 : numeric, default 25.0 Combined heat loss factor coefficient. The default value is one determined by Faiman for 7 silicon modules. [W/(m^2 C)]. u1 : numeric, default 6.84 Combined heat loss factor influenced by wind. The default value is one determined by Faiman for 7 silicon modules. [(W/m^2 C)(m/s)]. Returns ------- numeric, values in degrees Celsius Notes ----- All arguments may be scalars or vectors. If multiple arguments are vectors they must be the same length. References ---------- .. [1] Faiman, D. (2008). "Assessing the outdoor operating temperature of photovoltaic modules." Progress in Photovoltaics 16(4): 307-315. .. [2] "IEC 61853-2 Photovoltaic (PV) module performance testing and energy rating - Part 2: Spectral responsivity, incidence angle and module operating temperature measurements". IEC, Geneva, 2018. .. [3] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018. Author: Anton Driesse, PV Performance Labs ''' # The following lines may seem odd since u0 & u1 are probably scalar, # but it serves an indirect and easy way of allowing lists and # tuples for the other function arguments. u0 = np.asanyarray(u0) u1 = np.asanyarray(u1) total_loss_factor = u0 + u1 * wind_speed heat_input = poa_global temp_difference = heat_input / total_loss_factor return temp_air + temp_difference