Skip to content

Commit ebf29a4

Browse files
kandersolarcwhanse
andauthored
Migrate spectral modifier functions to pvlib.spectrum (#1628)
* migrate spectral modifiers to pvlib.spectrum * stickler * get fslr correction to 100% coverage * stickler! * change new names to sapm and first_solar in coordination with #1658, drop "spectral_correction" from the new function names * improve whatsnew * more renaming * rename to spectral_factor_X * tweaks * v0.9.5 -> v0.9.6 * 0.9.5 whatsnew cleanup * 0.9.6 -> 0.10.0 in deprecation warnings Co-Authored-By: Cliff Hansen <[email protected]> --------- Co-authored-by: Cliff Hansen <[email protected]>
1 parent 311781d commit ebf29a4

File tree

10 files changed

+363
-302
lines changed

10 files changed

+363
-302
lines changed

docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ Spectrum
1010
spectrum.get_example_spectral_response
1111
spectrum.get_am15g
1212
spectrum.calc_spectral_mismatch_field
13+
spectrum.spectral_factor_firstsolar
14+
spectrum.spectral_factor_sapm

docs/sphinx/source/whatsnew/v0.9.6.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ Breaking Changes
1515

1616
Deprecations
1717
~~~~~~~~~~~~
18+
* Functions for calculating spectral modifiers have been moved to :py:mod:`pvlib.spectrum`:
19+
:py:func:`pvlib.atmosphere.first_solar_spectral_correction` is deprecated and
20+
replaced by :py:func:`~pvlib.spectrum.spectral_factor_firstsolar`, and
21+
:py:func:`pvlib.pvsystem.sapm_spectral_loss` is deprecated and replaced by
22+
:py:func:`~pvlib.spectrum.spectral_factor_sapm`. (:pull:`1628`)
1823
* Removed the ``get_ecmwf_macc`` and ``read_ecmwf_macc`` iotools functions as the
1924
MACC dataset has been `removed by ECMWF <https://confluence.ecmwf.int/display/DAC/Decommissioning+of+ECMWF+Public+Datasets+Service>`_
2025
(data period 2003-2012). Instead, ECMWF recommends to use CAMS global

pvlib/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from pvlib.version import __version__ # noqa: F401
22

33
from pvlib import ( # noqa: F401
4+
# list spectrum first so it's available for atmosphere & pvsystem (GH 1628)
5+
spectrum,
6+
47
atmosphere,
58
bifacial,
69
clearsky,
@@ -20,7 +23,6 @@
2023
soiling,
2124
solarposition,
2225
spa,
23-
spectrum,
2426
temperature,
2527
tools,
2628
tracking,

pvlib/atmosphere.py

Lines changed: 6 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
absolute airmass and to determine pressure from altitude or vice versa.
44
"""
55

6-
from warnings import warn
7-
86
import numpy as np
97
import pandas as pd
8+
import pvlib
109

10+
from pvlib._deprecation import deprecated
1111

1212
APPARENT_ZENITH_MODELS = ('simple', 'kasten1966', 'kastenyoung1989',
1313
'gueymard1993', 'pickering2002')
@@ -336,175 +336,10 @@ def gueymard94_pw(temp_air, relative_humidity):
336336
return pw
337337

338338

339-
def first_solar_spectral_correction(pw, airmass_absolute,
340-
module_type=None, coefficients=None,
341-
min_pw=0.1, max_pw=8):
342-
r"""
343-
Spectral mismatch modifier based on precipitable water and absolute
344-
(pressure-adjusted) airmass.
345-
346-
Estimates a spectral mismatch modifier :math:`M` representing the effect on
347-
module short circuit current of variation in the spectral
348-
irradiance. :math:`M` is estimated from absolute (pressure currected) air
349-
mass, :math:`AM_a`, and precipitable water, :math:`Pw`, using the following
350-
function:
351-
352-
.. math::
353-
354-
M = c_1 + c_2 AM_a + c_3 Pw + c_4 AM_a^{0.5}
355-
+ c_5 Pw^{0.5} + c_6 \frac{AM_a} {Pw^{0.5}}
356-
357-
Default coefficients are determined for several cell types with
358-
known quantum efficiency curves, by using the Simple Model of the
359-
Atmospheric Radiative Transfer of Sunshine (SMARTS) [1]_. Using
360-
SMARTS, spectrums are simulated with all combinations of AMa and
361-
Pw where:
362-
363-
* :math:`0.5 \textrm{cm} <= Pw <= 5 \textrm{cm}`
364-
* :math:`1.0 <= AM_a <= 5.0`
365-
* Spectral range is limited to that of CMP11 (280 nm to 2800 nm)
366-
* spectrum simulated on a plane normal to the sun
367-
* All other parameters fixed at G173 standard
368-
369-
From these simulated spectra, M is calculated using the known
370-
quantum efficiency curves. Multiple linear regression is then
371-
applied to fit Eq. 1 to determine the coefficients for each module.
372-
373-
Based on the PVLIB Matlab function ``pvl_FSspeccorr`` by Mitchell
374-
Lee and Alex Panchula of First Solar, 2016 [2]_.
375-
376-
Parameters
377-
----------
378-
pw : array-like
379-
atmospheric precipitable water. [cm]
380-
381-
airmass_absolute : array-like
382-
absolute (pressure-adjusted) airmass. [unitless]
383-
384-
min_pw : float, default 0.1
385-
minimum atmospheric precipitable water. Any pw value lower than min_pw
386-
is set to min_pw to avoid model divergence. [cm]
387-
388-
max_pw : float, default 8
389-
maximum atmospheric precipitable water. Any pw value higher than max_pw
390-
is set to NaN to avoid model divergence. [cm]
391-
392-
module_type : None or string, default None
393-
a string specifying a cell type. Values of 'cdte', 'monosi', 'xsi',
394-
'multisi', and 'polysi' (can be lower or upper case). If provided,
395-
module_type selects default coefficients for the following modules:
396-
397-
* 'cdte' - First Solar Series 4-2 CdTe module.
398-
* 'monosi', 'xsi' - First Solar TetraSun module.
399-
* 'multisi', 'polysi' - anonymous multi-crystalline silicon module.
400-
* 'cigs' - anonymous copper indium gallium selenide module.
401-
* 'asi' - anonymous amorphous silicon module.
402-
403-
The module used to calculate the spectral correction
404-
coefficients corresponds to the Multi-crystalline silicon
405-
Manufacturer 2 Model C from [3]_. The spectral response (SR) of CIGS
406-
and a-Si modules used to derive coefficients can be found in [4]_
407-
408-
coefficients : None or array-like, default None
409-
Allows for entry of user-defined spectral correction
410-
coefficients. Coefficients must be of length 6. Derivation of
411-
coefficients requires use of SMARTS and PV module quantum
412-
efficiency curve. Useful for modeling PV module types which are
413-
not included as defaults, or to fine tune the spectral
414-
correction to a particular PV module. Note that the parameters for
415-
modules with very similar quantum efficiency should be similar,
416-
in most cases limiting the need for module specific coefficients.
417-
418-
Returns
419-
-------
420-
modifier: array-like
421-
spectral mismatch factor (unitless) which is can be multiplied
422-
with broadband irradiance reaching a module's cells to estimate
423-
effective irradiance, i.e., the irradiance that is converted to
424-
electrical current.
425-
426-
References
427-
----------
428-
.. [1] Gueymard, Christian. SMARTS2: a simple model of the atmospheric
429-
radiative transfer of sunshine: algorithms and performance
430-
assessment. Cocoa, FL: Florida Solar Energy Center, 1995.
431-
.. [2] Lee, Mitchell, and Panchula, Alex. "Spectral Correction for
432-
Photovoltaic Module Performance Based on Air Mass and Precipitable
433-
Water." IEEE Photovoltaic Specialists Conference, Portland, 2016
434-
.. [3] Marion, William F., et al. User's Manual for Data for Validating
435-
Models for PV Module Performance. National Renewable Energy
436-
Laboratory, 2014. http://www.nrel.gov/docs/fy14osti/61610.pdf
437-
.. [4] Schweiger, M. and Hermann, W, Influence of Spectral Effects
438-
on Energy Yield of Different PV Modules: Comparison of Pwat and
439-
MMF Approach, TUV Rheinland Energy GmbH report 21237296.003,
440-
January 2017
441-
"""
442-
443-
# --- Screen Input Data ---
444-
445-
# *** Pw ***
446-
# Replace Pw Values below 0.1 cm with 0.1 cm to prevent model from
447-
# diverging"
448-
pw = np.atleast_1d(pw)
449-
pw = pw.astype('float64')
450-
if np.min(pw) < min_pw:
451-
pw = np.maximum(pw, min_pw)
452-
warn(f'Exceptionally low pw values replaced with {min_pw} cm to '
453-
'prevent model divergence')
454-
455-
# Warn user about Pw data that is exceptionally high
456-
if np.max(pw) > max_pw:
457-
pw[pw > max_pw] = np.nan
458-
warn('Exceptionally high pw values replaced by np.nan: '
459-
'check input data.')
460-
461-
# *** AMa ***
462-
# Replace Extremely High AM with AM 10 to prevent model divergence
463-
# AM > 10 will only occur very close to sunset
464-
if np.max(airmass_absolute) > 10:
465-
airmass_absolute = np.minimum(airmass_absolute, 10)
466-
467-
# Warn user about AMa data that is exceptionally low
468-
if np.min(airmass_absolute) < 0.58:
469-
warn('Exceptionally low air mass: ' +
470-
'model not intended for extra-terrestrial use')
471-
# pvl_absoluteairmass(1,pvl_alt2pres(4340)) = 0.58 Elevation of
472-
# Mina Pirquita, Argentian = 4340 m. Highest elevation city with
473-
# population over 50,000.
474-
475-
_coefficients = {}
476-
_coefficients['cdte'] = (
477-
0.86273, -0.038948, -0.012506, 0.098871, 0.084658, -0.0042948)
478-
_coefficients['monosi'] = (
479-
0.85914, -0.020880, -0.0058853, 0.12029, 0.026814, -0.0017810)
480-
_coefficients['xsi'] = _coefficients['monosi']
481-
_coefficients['polysi'] = (
482-
0.84090, -0.027539, -0.0079224, 0.13570, 0.038024, -0.0021218)
483-
_coefficients['multisi'] = _coefficients['polysi']
484-
_coefficients['cigs'] = (
485-
0.85252, -0.022314, -0.0047216, 0.13666, 0.013342, -0.0008945)
486-
_coefficients['asi'] = (
487-
1.12094, -0.047620, -0.0083627, -0.10443, 0.098382, -0.0033818)
488-
489-
if module_type is not None and coefficients is None:
490-
coefficients = _coefficients[module_type.lower()]
491-
elif module_type is None and coefficients is not None:
492-
pass
493-
elif module_type is None and coefficients is None:
494-
raise TypeError('No valid input provided, both module_type and ' +
495-
'coefficients are None')
496-
else:
497-
raise TypeError('Cannot resolve input, must supply only one of ' +
498-
'module_type and coefficients')
499-
500-
# Evaluate Spectral Shift
501-
coeff = coefficients
502-
ama = airmass_absolute
503-
modifier = (
504-
coeff[0] + coeff[1]*ama + coeff[2]*pw + coeff[3]*np.sqrt(ama) +
505-
coeff[4]*np.sqrt(pw) + coeff[5]*ama/np.sqrt(pw))
506-
507-
return modifier
339+
first_solar_spectral_correction = deprecated(
340+
since='0.10.0',
341+
alternative='pvlib.spectrum.spectral_factor_firstsolar'
342+
)(pvlib.spectrum.spectral_factor_firstsolar)
508343

509344

510345
def bird_hulstrom80_aod_bb(aod380, aod500):

pvlib/pvsystem.py

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from pvlib._deprecation import deprecated
2020

2121
from pvlib import (atmosphere, iam, inverter, irradiance,
22-
singlediode as _singlediode, temperature)
22+
singlediode as _singlediode, spectrum, temperature)
2323
from pvlib.tools import _build_kwargs, _build_args
2424

2525

@@ -672,8 +672,8 @@ def sapm_celltemp(self, poa_global, temp_air, wind_speed):
672672
@_unwrap_single_value
673673
def sapm_spectral_loss(self, airmass_absolute):
674674
"""
675-
Use the :py:func:`sapm_spectral_loss` function, the input
676-
parameters, and ``self.module_parameters`` to calculate F1.
675+
Use the :py:func:`pvlib.spectrum.spectral_factor_sapm` function,
676+
the input parameters, and ``self.module_parameters`` to calculate F1.
677677
678678
Parameters
679679
----------
@@ -686,7 +686,8 @@ def sapm_spectral_loss(self, airmass_absolute):
686686
The SAPM spectral loss coefficient.
687687
"""
688688
return tuple(
689-
sapm_spectral_loss(airmass_absolute, array.module_parameters)
689+
spectrum.spectral_factor_sapm(airmass_absolute,
690+
array.module_parameters)
690691
for array in self.arrays
691692
)
692693

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

928-
return atmosphere.first_solar_spectral_correction(
929-
pw, airmass_absolute,
930-
module_type, coefficients
929+
return spectrum.spectral_factor_firstsolar(
930+
pw, airmass_absolute, module_type, coefficients
931931
)
932932
return tuple(
933933
itertools.starmap(_spectral_correction, zip(self.arrays, pw))
@@ -2602,43 +2602,10 @@ def sapm(effective_irradiance, temp_cell, module):
26022602
return out
26032603

26042604

2605-
def sapm_spectral_loss(airmass_absolute, module):
2606-
"""
2607-
Calculates the SAPM spectral loss coefficient, F1.
2608-
2609-
Parameters
2610-
----------
2611-
airmass_absolute : numeric
2612-
Absolute airmass
2613-
2614-
module : dict-like
2615-
A dict, Series, or DataFrame defining the SAPM performance
2616-
parameters. See the :py:func:`sapm` notes section for more
2617-
details.
2618-
2619-
Returns
2620-
-------
2621-
F1 : numeric
2622-
The SAPM spectral loss coefficient.
2623-
2624-
Notes
2625-
-----
2626-
nan airmass values will result in 0 output.
2627-
"""
2628-
2629-
am_coeff = [module['A4'], module['A3'], module['A2'], module['A1'],
2630-
module['A0']]
2631-
2632-
spectral_loss = np.polyval(am_coeff, airmass_absolute)
2633-
2634-
spectral_loss = np.where(np.isnan(spectral_loss), 0, spectral_loss)
2635-
2636-
spectral_loss = np.maximum(0, spectral_loss)
2637-
2638-
if isinstance(airmass_absolute, pd.Series):
2639-
spectral_loss = pd.Series(spectral_loss, airmass_absolute.index)
2640-
2641-
return spectral_loss
2605+
sapm_spectral_loss = deprecated(
2606+
since='0.10.0',
2607+
alternative='pvlib.spectrum.spectral_factor_sapm'
2608+
)(spectrum.spectral_factor_sapm)
26422609

26432610

26442611
def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
@@ -2698,11 +2665,11 @@ def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
26982665
See also
26992666
--------
27002667
pvlib.iam.sapm
2701-
pvlib.pvsystem.sapm_spectral_loss
2668+
pvlib.spectrum.spectral_factor_sapm
27022669
pvlib.pvsystem.sapm
27032670
"""
27042671

2705-
F1 = sapm_spectral_loss(airmass_absolute, module)
2672+
F1 = spectrum.spectral_factor_sapm(airmass_absolute, module)
27062673
F2 = iam.sapm(aoi, module)
27072674

27082675
Ee = F1 * (poa_direct * F2 + module['FD'] * poa_diffuse)

pvlib/spectrum/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
from pvlib.spectrum.spectrl2 import spectrl2 # noqa: F401
2-
from pvlib.spectrum.mismatch import (get_example_spectral_response, get_am15g,
3-
calc_spectral_mismatch_field)
2+
from pvlib.spectrum.mismatch import ( # noqa: F401
3+
calc_spectral_mismatch_field,
4+
get_am15g,
5+
get_example_spectral_response,
6+
spectral_factor_firstsolar,
7+
spectral_factor_sapm,
8+
)

0 commit comments

Comments
 (0)