Skip to content

Migrate spectral modifier functions to pvlib.spectrum #1628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 9, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Spectrum
spectrum.get_example_spectral_response
spectrum.get_am15g
spectrum.calc_spectral_mismatch_field
spectrum.spectral_factor_firstsolar
spectrum.spectral_factor_sapm
5 changes: 5 additions & 0 deletions docs/sphinx/source/whatsnew/v0.9.6.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ Breaking Changes

Deprecations
~~~~~~~~~~~~
* Functions for calculating spectral modifiers have been moved to :py:mod:`pvlib.spectrum`:
:py:func:`pvlib.atmosphere.first_solar_spectral_correction` is deprecated and
replaced by :py:func:`~pvlib.spectrum.spectral_factor_firstsolar`, and
:py:func:`pvlib.pvsystem.sapm_spectral_loss` is deprecated and replaced by
:py:func:`~pvlib.spectrum.spectral_factor_sapm`. (:pull:`1628`)
* Removed the ``get_ecmwf_macc`` and ``read_ecmwf_macc`` iotools functions as the
MACC dataset has been `removed by ECMWF <https://confluence.ecmwf.int/display/DAC/Decommissioning+of+ECMWF+Public+Datasets+Service>`_
(data period 2003-2012). Instead, ECMWF recommends to use CAMS global
Expand Down
4 changes: 3 additions & 1 deletion pvlib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from pvlib.version import __version__ # noqa: F401

from pvlib import ( # noqa: F401
# list spectrum first so it's available for atmosphere & pvsystem (GH 1628)
spectrum,

atmosphere,
bifacial,
clearsky,
Expand All @@ -20,7 +23,6 @@
soiling,
solarposition,
spa,
spectrum,
temperature,
tools,
tracking,
Expand Down
177 changes: 6 additions & 171 deletions pvlib/atmosphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
absolute airmass and to determine pressure from altitude or vice versa.
"""

from warnings import warn

import numpy as np
import pandas as pd
import pvlib

from pvlib._deprecation import deprecated

APPARENT_ZENITH_MODELS = ('simple', 'kasten1966', 'kastenyoung1989',
'gueymard1993', 'pickering2002')
Expand Down Expand Up @@ -336,175 +336,10 @@ def gueymard94_pw(temp_air, relative_humidity):
return pw


def first_solar_spectral_correction(pw, airmass_absolute,
module_type=None, coefficients=None,
min_pw=0.1, max_pw=8):
r"""
Spectral mismatch modifier based on precipitable water and absolute
(pressure-adjusted) airmass.

Estimates a spectral mismatch modifier :math:`M` representing the effect on
module short circuit current of variation in the spectral
irradiance. :math:`M` is estimated from absolute (pressure currected) air
mass, :math:`AM_a`, and precipitable water, :math:`Pw`, using the following
function:

.. math::

M = c_1 + c_2 AM_a + c_3 Pw + c_4 AM_a^{0.5}
+ c_5 Pw^{0.5} + c_6 \frac{AM_a} {Pw^{0.5}}

Default coefficients are determined for several cell types with
known quantum efficiency curves, by using the Simple Model of the
Atmospheric Radiative Transfer of Sunshine (SMARTS) [1]_. Using
SMARTS, spectrums are simulated with all combinations of AMa and
Pw where:

* :math:`0.5 \textrm{cm} <= Pw <= 5 \textrm{cm}`
* :math:`1.0 <= AM_a <= 5.0`
* Spectral range is limited to that of CMP11 (280 nm to 2800 nm)
* spectrum simulated on a plane normal to the sun
* All other parameters fixed at G173 standard

From these simulated spectra, M is calculated using the known
quantum efficiency curves. Multiple linear regression is then
applied to fit Eq. 1 to determine the coefficients for each module.

Based on the PVLIB Matlab function ``pvl_FSspeccorr`` by Mitchell
Lee and Alex Panchula of First Solar, 2016 [2]_.

Parameters
----------
pw : array-like
atmospheric precipitable water. [cm]

airmass_absolute : array-like
absolute (pressure-adjusted) airmass. [unitless]

min_pw : float, default 0.1
minimum atmospheric precipitable water. Any pw value lower than min_pw
is set to min_pw to avoid model divergence. [cm]

max_pw : float, default 8
maximum atmospheric precipitable water. Any pw value higher than max_pw
is set to NaN to avoid model divergence. [cm]

module_type : None or string, default None
a string specifying a cell type. Values of 'cdte', 'monosi', 'xsi',
'multisi', and 'polysi' (can be lower or upper case). If provided,
module_type selects default coefficients for the following modules:

* 'cdte' - First Solar Series 4-2 CdTe module.
* 'monosi', 'xsi' - First Solar TetraSun module.
* 'multisi', 'polysi' - anonymous multi-crystalline silicon module.
* 'cigs' - anonymous copper indium gallium selenide module.
* 'asi' - anonymous amorphous silicon module.

The module used to calculate the spectral correction
coefficients corresponds to the Multi-crystalline silicon
Manufacturer 2 Model C from [3]_. The spectral response (SR) of CIGS
and a-Si modules used to derive coefficients can be found in [4]_

coefficients : None or array-like, default None
Allows for entry of user-defined spectral correction
coefficients. Coefficients must be of length 6. Derivation of
coefficients requires use of SMARTS and PV module quantum
efficiency curve. Useful for modeling PV module types which are
not included as defaults, or to fine tune the spectral
correction to a particular PV module. Note that the parameters for
modules with very similar quantum efficiency should be similar,
in most cases limiting the need for module specific coefficients.

Returns
-------
modifier: array-like
spectral mismatch factor (unitless) which is can be multiplied
with broadband irradiance reaching a module's cells to estimate
effective irradiance, i.e., the irradiance that is converted to
electrical current.

References
----------
.. [1] Gueymard, Christian. SMARTS2: a simple model of the atmospheric
radiative transfer of sunshine: algorithms and performance
assessment. Cocoa, FL: Florida Solar Energy Center, 1995.
.. [2] Lee, Mitchell, and Panchula, Alex. "Spectral Correction for
Photovoltaic Module Performance Based on Air Mass and Precipitable
Water." IEEE Photovoltaic Specialists Conference, Portland, 2016
.. [3] Marion, William F., et al. User's Manual for Data for Validating
Models for PV Module Performance. National Renewable Energy
Laboratory, 2014. http://www.nrel.gov/docs/fy14osti/61610.pdf
.. [4] Schweiger, M. and Hermann, W, Influence of Spectral Effects
on Energy Yield of Different PV Modules: Comparison of Pwat and
MMF Approach, TUV Rheinland Energy GmbH report 21237296.003,
January 2017
"""

# --- Screen Input Data ---

# *** Pw ***
# Replace Pw Values below 0.1 cm with 0.1 cm to prevent model from
# diverging"
pw = np.atleast_1d(pw)
pw = pw.astype('float64')
if np.min(pw) < min_pw:
pw = np.maximum(pw, min_pw)
warn(f'Exceptionally low pw values replaced with {min_pw} cm to '
'prevent model divergence')

# Warn user about Pw data that is exceptionally high
if np.max(pw) > max_pw:
pw[pw > max_pw] = np.nan
warn('Exceptionally high pw values replaced by np.nan: '
'check input data.')

# *** AMa ***
# Replace Extremely High AM with AM 10 to prevent model divergence
# AM > 10 will only occur very close to sunset
if np.max(airmass_absolute) > 10:
airmass_absolute = np.minimum(airmass_absolute, 10)

# Warn user about AMa data that is exceptionally low
if np.min(airmass_absolute) < 0.58:
warn('Exceptionally low air mass: ' +
'model not intended for extra-terrestrial use')
# pvl_absoluteairmass(1,pvl_alt2pres(4340)) = 0.58 Elevation of
# Mina Pirquita, Argentian = 4340 m. Highest elevation city with
# population over 50,000.

_coefficients = {}
_coefficients['cdte'] = (
0.86273, -0.038948, -0.012506, 0.098871, 0.084658, -0.0042948)
_coefficients['monosi'] = (
0.85914, -0.020880, -0.0058853, 0.12029, 0.026814, -0.0017810)
_coefficients['xsi'] = _coefficients['monosi']
_coefficients['polysi'] = (
0.84090, -0.027539, -0.0079224, 0.13570, 0.038024, -0.0021218)
_coefficients['multisi'] = _coefficients['polysi']
_coefficients['cigs'] = (
0.85252, -0.022314, -0.0047216, 0.13666, 0.013342, -0.0008945)
_coefficients['asi'] = (
1.12094, -0.047620, -0.0083627, -0.10443, 0.098382, -0.0033818)

if module_type is not None and coefficients is None:
coefficients = _coefficients[module_type.lower()]
elif module_type is None and coefficients is not None:
pass
elif module_type is None and coefficients is None:
raise TypeError('No valid input provided, both module_type and ' +
'coefficients are None')
else:
raise TypeError('Cannot resolve input, must supply only one of ' +
'module_type and coefficients')

# Evaluate Spectral Shift
coeff = coefficients
ama = airmass_absolute
modifier = (
coeff[0] + coeff[1]*ama + coeff[2]*pw + coeff[3]*np.sqrt(ama) +
coeff[4]*np.sqrt(pw) + coeff[5]*ama/np.sqrt(pw))

return modifier
first_solar_spectral_correction = deprecated(
since='0.10.0',
alternative='pvlib.spectrum.spectral_factor_firstsolar'
)(pvlib.spectrum.spectral_factor_firstsolar)


def bird_hulstrom80_aod_bb(aod380, aod500):
Expand Down
61 changes: 14 additions & 47 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pvlib._deprecation import deprecated

from pvlib import (atmosphere, iam, inverter, irradiance,
singlediode as _singlediode, temperature)
singlediode as _singlediode, spectrum, temperature)
from pvlib.tools import _build_kwargs, _build_args


Expand Down Expand Up @@ -672,8 +672,8 @@ def sapm_celltemp(self, poa_global, temp_air, wind_speed):
@_unwrap_single_value
def sapm_spectral_loss(self, airmass_absolute):
"""
Use the :py:func:`sapm_spectral_loss` function, the input
parameters, and ``self.module_parameters`` to calculate F1.
Use the :py:func:`pvlib.spectrum.spectral_factor_sapm` function,
the input parameters, and ``self.module_parameters`` to calculate F1.

Parameters
----------
Expand All @@ -686,7 +686,8 @@ def sapm_spectral_loss(self, airmass_absolute):
The SAPM spectral loss coefficient.
"""
return tuple(
sapm_spectral_loss(airmass_absolute, array.module_parameters)
spectrum.spectral_factor_sapm(airmass_absolute,
array.module_parameters)
for array in self.arrays
)

Expand Down Expand Up @@ -884,7 +885,7 @@ def noct_sam_celltemp(self, poa_global, temp_air, wind_speed,
@_unwrap_single_value
def first_solar_spectral_loss(self, pw, airmass_absolute):
"""
Use :py:func:`pvlib.atmosphere.first_solar_spectral_correction` to
Use :py:func:`pvlib.spectrum.spectral_factor_firstsolar` to
calculate the spectral loss modifier. The model coefficients are
specific to the module's cell type, and are determined by searching
for one of the following keys in self.module_parameters (in order):
Expand Down Expand Up @@ -925,9 +926,8 @@ def _spectral_correction(array, pw):
module_type = array._infer_cell_type()
coefficients = None

return atmosphere.first_solar_spectral_correction(
pw, airmass_absolute,
module_type, coefficients
return spectrum.spectral_factor_firstsolar(
pw, airmass_absolute, module_type, coefficients
)
return tuple(
itertools.starmap(_spectral_correction, zip(self.arrays, pw))
Expand Down Expand Up @@ -2602,43 +2602,10 @@ def sapm(effective_irradiance, temp_cell, module):
return out


def sapm_spectral_loss(airmass_absolute, module):
"""
Calculates the SAPM spectral loss coefficient, F1.

Parameters
----------
airmass_absolute : numeric
Absolute airmass

module : dict-like
A dict, Series, or DataFrame defining the SAPM performance
parameters. See the :py:func:`sapm` notes section for more
details.

Returns
-------
F1 : numeric
The SAPM spectral loss coefficient.

Notes
-----
nan airmass values will result in 0 output.
"""

am_coeff = [module['A4'], module['A3'], module['A2'], module['A1'],
module['A0']]

spectral_loss = np.polyval(am_coeff, airmass_absolute)

spectral_loss = np.where(np.isnan(spectral_loss), 0, spectral_loss)

spectral_loss = np.maximum(0, spectral_loss)

if isinstance(airmass_absolute, pd.Series):
spectral_loss = pd.Series(spectral_loss, airmass_absolute.index)

return spectral_loss
sapm_spectral_loss = deprecated(
since='0.10.0',
alternative='pvlib.spectrum.spectral_factor_sapm'
)(spectrum.spectral_factor_sapm)


def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
Expand Down Expand Up @@ -2698,11 +2665,11 @@ def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
See also
--------
pvlib.iam.sapm
pvlib.pvsystem.sapm_spectral_loss
pvlib.spectrum.spectral_factor_sapm
pvlib.pvsystem.sapm
"""

F1 = sapm_spectral_loss(airmass_absolute, module)
F1 = spectrum.spectral_factor_sapm(airmass_absolute, module)
F2 = iam.sapm(aoi, module)

Ee = F1 * (poa_direct * F2 + module['FD'] * poa_diffuse)
Expand Down
9 changes: 7 additions & 2 deletions pvlib/spectrum/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from pvlib.spectrum.spectrl2 import spectrl2 # noqa: F401
from pvlib.spectrum.mismatch import (get_example_spectral_response, get_am15g,
calc_spectral_mismatch_field)
from pvlib.spectrum.mismatch import ( # noqa: F401
calc_spectral_mismatch_field,
get_am15g,
get_example_spectral_response,
spectral_factor_firstsolar,
spectral_factor_sapm,
)
Loading