"""
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