Skip to content

Commit 2e9c2c0

Browse files
RDaxiniadriesseAdamRJensenechedey-ls
authored
update spectral_factor_firstsolar (#2100)
* add new parameters, modify tests and warnings Added parameters to enable user to set min/max airmass Changed handling of values exceeding max pw to default to max pw rather than np.nan Replaced subjective phrasing such as "exceptionally high" with "high" and removed references to "divergence" in the warnings in order to align with the fact that the user can now determine their own limits, which may or may not be "exceptionally" high/low or cause model divergence Added tests for new AMa limit parameters, modified warnings and expected values in existing tests * out of limit input handling * Update mismatch.py * Update mismatch.py * Update test_spectrum.py * Update mismatch.py * Update mismatch.py * Create v0.11.1.rst * Update v0.11.1.rst * Apply suggestions from code review adriesse + AdamRJensen review Co-authored-by: Anton Driesse <[email protected]> Co-authored-by: Adam R. Jensen <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Anton Driesse <[email protected]> * create * Update test_spectrum.py * module_type typesetting plus removed a few commas, capitalised a letter * Update pvlib/spectrum/mismatch.py Co-authored-by: Adam R. Jensen <[email protected]> * Apply suggestions from code review Co-authored-by: Adam R. Jensen <[email protected]> * Update notes * review comments pw -> precipitable water * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update mismatch.py * Update mismatch.py * Update mismatch.py --------- Co-authored-by: Anton Driesse <[email protected]> Co-authored-by: Adam R. Jensen <[email protected]> Co-authored-by: Echedey Luis <[email protected]>
1 parent 0d09130 commit 2e9c2c0

File tree

3 files changed

+93
-67
lines changed

3 files changed

+93
-67
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Enhancements
1313
* Add new losses function that accounts for non-uniform irradiance on bifacial
1414
modules, :py:func:`pvlib.bifacial.power_mismatch_deline`.
1515
(:issue:`2045`, :pull:`2046`)
16+
* Add new parameters for min/max absolute air mass to
17+
:py:func:`pvlib.spectrum.spectral_factor_firstsolar`.
18+
(:issue:`2086`, :pull:`2100`)
1619

1720

1821
Bug fixes

pvlib/spectrum/mismatch.py

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -358,21 +358,17 @@ def integrate(e):
358358
def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
359359
module_type=None, coefficients=None,
360360
min_precipitable_water=0.1,
361-
max_precipitable_water=8):
361+
max_precipitable_water=8,
362+
min_airmass_absolute=0.58,
363+
max_airmass_absolute=10):
362364
r"""
363365
Spectral mismatch modifier based on precipitable water and absolute
364366
(pressure-adjusted) air mass.
365367
366-
Estimates a spectral mismatch modifier :math:`M` representing the effect on
367-
module short circuit current of variation in the spectral
368-
irradiance. :math:`M` is estimated from absolute (pressure currected) air
369-
mass, :math:`AM_a`, and precipitable water, :math:`Pw`, using the following
370-
function:
371-
372-
.. math::
373-
374-
M = c_1 + c_2 AM_a + c_3 Pw + c_4 AM_a^{0.5}
375-
+ c_5 Pw^{0.5} + c_6 \frac{AM_a} {Pw^{0.5}}
368+
Estimates the spectral mismatch modifier, :math:`M`, representing the
369+
effect of variation in the spectral irradiance on the module short circuit
370+
current :math:`M` is estimated from absolute (pressure-corrected) air
371+
mass, :math:`AM_a`, and precipitable water, :math:`Pw`.
376372
377373
Default coefficients are determined for several cell types with
378374
known quantum efficiency curves, by using the Simple Model of the
@@ -383,15 +379,13 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
383379
* :math:`0.5 \textrm{cm} <= Pw <= 5 \textrm{cm}`
384380
* :math:`1.0 <= AM_a <= 5.0`
385381
* Spectral range is limited to that of CMP11 (280 nm to 2800 nm)
386-
* spectrum simulated on a plane normal to the sun
382+
* Spectrum simulated on an equatorial facing surface with 37° tilt
387383
* All other parameters fixed at G173 standard
388384
389-
From these simulated spectra, M is calculated using the known
385+
From these simulated spectra, :math:`M` is calculated using the known
390386
quantum efficiency curves. Multiple linear regression is then
391-
applied to fit Eq. 1 to determine the coefficients for each module.
392-
393-
Based on the PVLIB Matlab function ``pvl_FSspeccorr`` by Mitchell
394-
Lee and Alex Panchula of First Solar, 2016 [2]_.
387+
applied to fit Eq. 1 to determine the coefficients for each module. More
388+
details on the model can be found in [2]_.
395389
396390
Parameters
397391
----------
@@ -406,11 +400,12 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
406400
'multisi', and 'polysi' (can be lower or upper case). If provided,
407401
module_type selects default coefficients for the following modules:
408402
409-
* 'cdte' - First Solar Series 4-2 CdTe module.
410-
* 'monosi', 'xsi' - First Solar TetraSun module.
411-
* 'multisi', 'polysi' - anonymous multi-crystalline silicon module.
412-
* 'cigs' - anonymous copper indium gallium selenide module.
413-
* 'asi' - anonymous amorphous silicon module.
403+
* ``'cdte'`` - First Solar Series 4-2 CdTe module.
404+
* ``'monosi'``, ``'xsi'`` - First Solar TetraSun module.
405+
* ``'multisi'``, ``'polysi'`` - anonymous multi-crystalline silicon
406+
module.
407+
* ``'cigs'`` - anonymous copper indium gallium selenide module.
408+
* ``'asi'`` - anonymous amorphous silicon module.
414409
415410
The module used to calculate the spectral correction
416411
coefficients corresponds to the Multi-crystalline silicon
@@ -430,12 +425,20 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
430425
min_precipitable_water : float, default 0.1
431426
minimum atmospheric precipitable water. Any ``precipitable_water``
432427
value lower than ``min_precipitable_water``
433-
is set to ``min_precipitable_water`` to avoid model divergence. [cm]
428+
is set to ``min_precipitable_water``. [cm]
434429
435430
max_precipitable_water : float, default 8
436431
maximum atmospheric precipitable water. Any ``precipitable_water``
437432
value greater than ``max_precipitable_water``
438-
is set to ``np.nan`` to avoid model divergence. [cm]
433+
is set to ``np.nan``. [cm]
434+
435+
min_airmass_absolute : float, default 0.58
436+
minimum absolute airmass. Any ``airmass_absolute`` value lower than
437+
``min_airmass_absolute`` is set to ``min_airmass_absolute``. [unitless]
438+
439+
max_airmass_absolute : float, default 10
440+
minimum absolute airmass. Any ``airmass_absolute`` value greater than
441+
``max_airmass_absolute`` is set to ``max_airmass_absolute``. [unitless]
439442
440443
Returns
441444
-------
@@ -445,6 +448,22 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
445448
effective irradiance, i.e., the irradiance that is converted to
446449
electrical current.
447450
451+
Notes
452+
----
453+
The ``spectral_factor_firstsolar`` model takes the following form:
454+
455+
.. math::
456+
457+
M = c_1 + c_2 AM_a + c_3 Pw + c_4 AM_a^{0.5}
458+
+ c_5 Pw^{0.5} + c_6 \frac{AM_a} {Pw^{0.5}}.
459+
460+
The default values for the limits applied to :math:`AM_a` and :math:`Pw`
461+
via the ``min_precipitable_water``, ``max_precipitable_water``,
462+
``min_airmass_absolute``, and ``max_airmass_absolute`` are set to prevent
463+
divergence of the model presented above. These default values were
464+
determined by the publication authors in the original pvlib-python
465+
implementation (:pull:`208`).
466+
448467
References
449468
----------
450469
.. [1] Gueymard, Christian. SMARTS2: a simple model of the atmospheric
@@ -461,36 +480,27 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
461480
MMF Approach, TUV Rheinland Energy GmbH report 21237296.003,
462481
January 2017
463482
"""
464-
465-
# --- Screen Input Data ---
466-
467-
# *** Pw ***
468-
# Replace Pw Values below 0.1 cm with 0.1 cm to prevent model from
469-
# diverging"
470483
pw = np.atleast_1d(precipitable_water)
471484
pw = pw.astype('float64')
472485
if np.min(pw) < min_precipitable_water:
473486
pw = np.maximum(pw, min_precipitable_water)
474-
warn('Exceptionally low pw values replaced with '
475-
f'{min_precipitable_water} cm to prevent model divergence')
487+
warn('Low precipitable water values replaced with '
488+
f'{min_precipitable_water} cm in the calculation of spectral '
489+
'mismatch.')
476490

477-
# Warn user about Pw data that is exceptionally high
478491
if np.max(pw) > max_precipitable_water:
479492
pw[pw > max_precipitable_water] = np.nan
480-
warn('Exceptionally high pw values replaced by np.nan: '
481-
'check input data.')
482-
483-
# *** AMa ***
484-
# Replace Extremely High AM with AM 10 to prevent model divergence
485-
# AM > 10 will only occur very close to sunset
486-
if np.max(airmass_absolute) > 10:
487-
airmass_absolute = np.minimum(airmass_absolute, 10)
488-
489-
# Warn user about AMa data that is exceptionally low
490-
if np.min(airmass_absolute) < 0.58:
491-
warn('Exceptionally low air mass: ' +
492-
'model not intended for extra-terrestrial use')
493-
# pvl_absoluteairmass(1,pvl_alt2pres(4340)) = 0.58 Elevation of
493+
warn('High precipitable water values replaced with np.nan in '
494+
'the calculation of spectral mismatch.')
495+
496+
airmass_absolute = np.minimum(airmass_absolute, max_airmass_absolute)
497+
498+
if np.min(airmass_absolute) < min_airmass_absolute:
499+
airmass_absolute = np.maximum(airmass_absolute, min_airmass_absolute)
500+
warn('Low airmass values replaced with 'f'{min_airmass_absolute} in '
501+
'the calculation of spectral mismatch.')
502+
# pvlib.atmosphere.get_absolute_airmass(1,
503+
# pvlib.atmosphere.alt2pres(4340)) = 0.58 Elevation of
494504
# Mina Pirquita, Argentian = 4340 m. Highest elevation city with
495505
# population over 50,000.
496506

@@ -519,7 +529,6 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
519529
raise TypeError('Cannot resolve input, must supply only one of ' +
520530
'module_type and coefficients')
521531

522-
# Evaluate Spectral Shift
523532
coeff = coefficients
524533
ama = airmass_absolute
525534
modifier = (
@@ -640,8 +649,8 @@ def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500,
640649
One of the following PV technology strings from [1]_:
641650
642651
* ``'cdte'`` - anonymous CdTe module.
643-
* ``'monosi'``, - anonymous sc-si module.
644-
* ``'multisi'``, - anonymous mc-si- module.
652+
* ``'monosi'`` - anonymous sc-si module.
653+
* ``'multisi'`` - anonymous mc-si- module.
645654
* ``'cigs'`` - anonymous copper indium gallium selenide module.
646655
* ``'asi'`` - anonymous amorphous silicon module.
647656
* ``'perovskite'`` - anonymous pervoskite module.
@@ -755,8 +764,8 @@ def spectral_factor_pvspec(airmass_absolute, clearsky_index,
755764
756765
* ``'fs4-1'`` - First Solar series 4-1 and earlier CdTe module.
757766
* ``'fs4-2'`` - First Solar 4-2 and later CdTe module.
758-
* ``'monosi'``, - anonymous monocrystalline Si module.
759-
* ``'multisi'``, - anonymous multicrystalline Si module.
767+
* ``'monosi'`` - anonymous monocrystalline Si module.
768+
* ``'multisi'`` - anonymous multicrystalline Si module.
760769
* ``'cigs'`` - anonymous copper indium gallium selenide module.
761770
* ``'asi'`` - anonymous amorphous silicon module.
762771

pvlib/tests/test_spectrum.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,22 @@ def test_spectral_factor_firstsolar_supplied():
264264
assert_allclose(out, expected, atol=1e-3)
265265

266266

267+
def test_spectral_factor_firstsolar_large_airmass_supplied_max():
268+
# test airmass > user-defined maximum is treated same as airmass=maximum
269+
m_eq11 = spectrum.spectral_factor_firstsolar(1, 11, 'monosi',
270+
max_airmass_absolute=11)
271+
m_gt11 = spectrum.spectral_factor_firstsolar(1, 15, 'monosi',
272+
max_airmass_absolute=11)
273+
assert_allclose(m_eq11, m_gt11)
274+
275+
276+
def test_spectral_factor_firstsolar_large_airmass():
277+
# test that airmass > 10 is treated same as airmass=10
278+
m_eq10 = spectrum.spectral_factor_firstsolar(1, 10, 'monosi')
279+
m_gt10 = spectrum.spectral_factor_firstsolar(1, 15, 'monosi')
280+
assert_allclose(m_eq10, m_gt10)
281+
282+
267283
def test_spectral_factor_firstsolar_ambiguous():
268284
with pytest.raises(TypeError):
269285
spectrum.spectral_factor_firstsolar(1, 1)
@@ -276,36 +292,34 @@ def test_spectral_factor_firstsolar_ambiguous_both():
276292
spectrum.spectral_factor_firstsolar(1, 1, 'cdte', coefficients=coeffs)
277293

278294

279-
def test_spectral_factor_firstsolar_large_airmass():
280-
# test that airmass > 10 is treated same as airmass==10
281-
m_eq10 = spectrum.spectral_factor_firstsolar(1, 10, 'monosi')
282-
m_gt10 = spectrum.spectral_factor_firstsolar(1, 15, 'monosi')
283-
assert_allclose(m_eq10, m_gt10)
284-
285-
286295
def test_spectral_factor_firstsolar_low_airmass():
287-
with pytest.warns(UserWarning, match='Exceptionally low air mass'):
296+
m_eq58 = spectrum.spectral_factor_firstsolar(1, 0.58, 'monosi')
297+
m_lt58 = spectrum.spectral_factor_firstsolar(1, 0.1, 'monosi')
298+
assert_allclose(m_eq58, m_lt58)
299+
with pytest.warns(UserWarning, match='Low airmass values replaced'):
288300
_ = spectrum.spectral_factor_firstsolar(1, 0.1, 'monosi')
289301

290302

291303
def test_spectral_factor_firstsolar_range():
292-
with pytest.warns(UserWarning, match='Exceptionally high pw values'):
293-
out = spectrum.spectral_factor_firstsolar(np.array([.1, 3, 10]),
294-
np.array([1, 3, 5]),
295-
module_type='monosi')
304+
out = spectrum.spectral_factor_firstsolar(np.array([.1, 3, 10]),
305+
np.array([1, 3, 5]),
306+
module_type='monosi')
296307
expected = np.array([0.96080878, 1.03055092, np.nan])
297308
assert_allclose(out, expected, atol=1e-3)
298-
with pytest.warns(UserWarning, match='Exceptionally high pw values'):
309+
with pytest.warns(UserWarning, match='High precipitable water values '
310+
'replaced'):
299311
out = spectrum.spectral_factor_firstsolar(6, 1.5,
300312
max_precipitable_water=5,
301313
module_type='monosi')
302-
with pytest.warns(UserWarning, match='Exceptionally low pw values'):
314+
with pytest.warns(UserWarning, match='Low precipitable water values '
315+
'replaced'):
303316
out = spectrum.spectral_factor_firstsolar(np.array([0, 3, 8]),
304317
np.array([1, 3, 5]),
305318
module_type='monosi')
306319
expected = np.array([0.96080878, 1.03055092, 1.04932727])
307320
assert_allclose(out, expected, atol=1e-3)
308-
with pytest.warns(UserWarning, match='Exceptionally low pw values'):
321+
with pytest.warns(UserWarning, match='Low precipitable water values '
322+
'replaced'):
309323
out = spectrum.spectral_factor_firstsolar(0.2, 1.5,
310324
min_precipitable_water=1,
311325
module_type='monosi')

0 commit comments

Comments
 (0)