Skip to content

Commit 19c9598

Browse files
RDaxiniechedey-lskandersolarIoannisSifnaioscwhanse
authored
Add PVSPEC spectral correction factor model (#2072)
* Create test_pelland.py * new spectral factor first attempt, PVSPEC model for the spectral mismatch factor based on air mass and clearness index * Update __init__.py * Update mismatch.py add new spectral factor (to be tested) * Delete test_pelland.py delete new py file (and add new model into mismatch.py) * Update mismatch.py * Update mismatch.py correct errors raised * Update test_spectrum.py create new tests for pelland air mass / clearness index spectral correction * Update test_spectrum.py Correct an expected value * correct function name * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update pvlib/tests/test_spectrum.py Co-authored-by: Echedey Luis <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update spectrum.rst * Update mismatch.py update eqn in docs * Update mismatch.py * Update mismatch.py * Update mismatch.py * Update mismatch.py * Update mismatch.py * Update mismatch.py * Update mismatch.py * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update mismatch.py * Update test_spectrum.py fix indentation and white spaces, change clearness to clearsky * Update test_spectrum.py remove data screens no longer required * Update mismatch.py trailing white space * Update pvlib/spectrum/mismatch.py Co-authored-by: Kevin Anderson <[email protected]> * Update mismatch.py * Update mismatch.py * Update pvlib/spectrum/mismatch.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/tests/test_spectrum.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/tests/test_spectrum.py Co-authored-by: Kevin Anderson <[email protected]> * Update mismatch.py * Update pvlib/tests/test_spectrum.py Co-authored-by: Kevin Anderson <[email protected]> * name change * Update pvlib/spectrum/mismatch.py Co-authored-by: Kevin Anderson <[email protected]> * Update test_spectrum.py * Update mismatch.py * Update test_spectrum.py * Update test_spectrum.py * Update test_spectrum.py * Update test_spectrum.py * Update test_spectrum.py add series test * Update mismatch.py * Update test_spectrum.py * Update pvlib/spectrum/mismatch.py Co-authored-by: Ioannis Sifnaios <[email protected]> * Update mismatch.py * Update pvlib/spectrum/mismatch.py Co-authored-by: Ioannis Sifnaios <[email protected]> * Update mismatch.py * Update mismatch.py * Update mismatch.py * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Echedey Luis <[email protected]> * Update mismatch.py tried typesetting the url differently, will fix line character length after * Update mismatch.py * Update mismatch.py * Update mismatch.py * Update pvlib/spectrum/mismatch.py Co-authored-by: Cliff Hansen <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Cliff Hansen <[email protected]> * Update pvlib/spectrum/mismatch.py Co-authored-by: Cliff Hansen <[email protected]> * Update mismatch.py * test for output type * Update mismatch.py specific data resource via extended url is either behind a paywall or moved; linking the main site homepage instead * Update pvlib/tests/test_spectrum.py Co-authored-by: Echedey Luis <[email protected]> * Update v0.11.0.rst * Update docs/sphinx/source/whatsnew/v0.11.0.rst Co-authored-by: Kevin Anderson <[email protected]> --------- Co-authored-by: Echedey Luis <[email protected]> Co-authored-by: Kevin Anderson <[email protected]> Co-authored-by: Ioannis Sifnaios <[email protected]> Co-authored-by: Cliff Hansen <[email protected]>
1 parent 734ac82 commit 19c9598

File tree

5 files changed

+195
-27
lines changed

5 files changed

+195
-27
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ Spectrum
1313
spectrum.spectral_factor_caballero
1414
spectrum.spectral_factor_firstsolar
1515
spectrum.spectral_factor_sapm
16+
spectrum.spectral_factor_pvspec
1617
spectrum.sr_to_qe
1718
spectrum.qe_to_sr

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ Enhancements
3535
efficiency ([unitless]) and vice versa. The conversion functions are
3636
:py:func:`pvlib.spectrum.sr_to_qe` and :py:func:`pvlib.spectrum.qe_to_sr`
3737
respectively. (:issue:`2040`, :pull:`2041`)
38-
38+
* Add function :py:func:`pvlib.spectrum.spectral_factor_pvspec`, which calculates the
39+
spectral mismatch factor as a function of absolute airmass and clearsky index
40+
using the PVSPEC model. (:issue:`1950`, :issue:`2065`, :pull:`2072`)
3941

4042
Bug fixes
4143
~~~~~~~~~
@@ -60,3 +62,4 @@ Contributors
6062
* Siddharth Kaul (:ghuser:`k10blogger`)
6163
* Ioannis Sifnaios (:ghuser:`IoannisSifnaios`)
6264
* Mark Campanelli (:ghuser:`markcampanelli`)
65+
* Rajiv Daxini (:ghuser:`RDaxini`)

pvlib/spectrum/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
spectral_factor_caballero,
77
spectral_factor_firstsolar,
88
spectral_factor_sapm,
9+
spectral_factor_pvspec,
910
sr_to_qe,
10-
qe_to_sr,
11+
qe_to_sr
1112
)

pvlib/spectrum/mismatch.py

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,14 @@ def get_example_spectral_response(wavelength=None):
5454
'''
5555
# Contributed by Anton Driesse (@adriesse), PV Performance Labs. Aug. 2022
5656

57-
SR_DATA = np.array([[ 290, 0.00],
58-
[ 350, 0.27],
59-
[ 400, 0.37],
60-
[ 500, 0.52],
61-
[ 650, 0.71],
62-
[ 800, 0.88],
63-
[ 900, 0.97],
64-
[ 950, 1.00],
57+
SR_DATA = np.array([[290, 0.00],
58+
[350, 0.27],
59+
[400, 0.37],
60+
[500, 0.52],
61+
[650, 0.71],
62+
[800, 0.88],
63+
[900, 0.97],
64+
[950, 1.00],
6565
[1000, 0.93],
6666
[1050, 0.58],
6767
[1100, 0.21],
@@ -256,7 +256,7 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
256256
max_precipitable_water=8):
257257
r"""
258258
Spectral mismatch modifier based on precipitable water and absolute
259-
(pressure-adjusted) airmass.
259+
(pressure-adjusted) air mass.
260260
261261
Estimates a spectral mismatch modifier :math:`M` representing the effect on
262262
module short circuit current of variation in the spectral
@@ -294,7 +294,7 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
294294
atmospheric precipitable water. [cm]
295295
296296
airmass_absolute : numeric
297-
absolute (pressure-adjusted) airmass. [unitless]
297+
absolute (pressure-adjusted) air mass. [unitless]
298298
299299
module_type : str, optional
300300
a string specifying a cell type. Values of 'cdte', 'monosi', 'xsi',
@@ -583,6 +583,112 @@ def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500,
583583
return modifier
584584

585585

586+
def spectral_factor_pvspec(airmass_absolute, clearsky_index,
587+
module_type=None, coefficients=None):
588+
r"""
589+
Estimate a technology-specific spectral mismatch modifier from absolute
590+
airmass and clear sky index using the PVSPEC model.
591+
592+
The PVSPEC spectral mismatch model includes the effects of cloud cover on
593+
the irradiance spectrum. Model coefficients are derived using spectral
594+
irradiance and other meteorological data from eight locations. Coefficients
595+
for six module types are available via the ``module_type`` parameter.
596+
More details on the model can be found in [1]_.
597+
598+
Parameters
599+
----------
600+
airmass_absolute : numeric
601+
absolute (pressure-adjusted) airmass. [unitless]
602+
603+
clearsky_index: numeric
604+
clear sky index. [unitless]
605+
606+
module_type : str, optional
607+
One of the following PV technology strings from [1]_:
608+
609+
* ``'fs4-1'`` - First Solar series 4-1 and earlier CdTe module.
610+
* ``'fs4-2'`` - First Solar 4-2 and later CdTe module.
611+
* ``'monosi'``, - anonymous monocrystalline Si module.
612+
* ``'multisi'``, - anonymous multicrystalline Si module.
613+
* ``'cigs'`` - anonymous copper indium gallium selenide module.
614+
* ``'asi'`` - anonymous amorphous silicon module.
615+
616+
coefficients : array-like, optional
617+
user-defined coefficients, if not using one of the default coefficient
618+
sets via the ``module_type`` parameter.
619+
620+
Returns
621+
-------
622+
mismatch: numeric
623+
spectral mismatch factor (unitless) which is multiplied
624+
with broadband irradiance reaching a module's cells to estimate
625+
effective irradiance, i.e., the irradiance that is converted to
626+
electrical current.
627+
628+
Notes
629+
-----
630+
The PVSPEC model parameterises the spectral mismatch factor as a function
631+
of absolute air mass and the clear sky index as follows:
632+
633+
.. math::
634+
635+
M = a_1 k_c^{a_2} AM_a^{a_3},
636+
637+
where :math:`M` is the spectral mismatch factor, :math:`k_c` is the clear
638+
sky index, :math:`AM_a` is the absolute air mass, and :math:`a_1, a_2, a_3`
639+
are module-specific coefficients. In the PVSPEC model publication, absolute
640+
air mass (denoted as :math:`AM`) is estimated starting from the Kasten and
641+
Young relative air mass [2]_. The clear sky index, which is the ratio of
642+
GHI to clear sky GHI, uses the ESRA model [3]_ to estimate the clear sky
643+
GHI with monthly Linke turbidity values from [4]_ as inputs.
644+
645+
References
646+
----------
647+
.. [1] Pelland, S., Beswick, C., Thevenard, D., Côté, A., Pai, A. and
648+
Poissant, Y., 2020. Development and testing of the PVSPEC model of
649+
photovoltaic spectral mismatch factor. In 2020 47th IEEE Photovoltaic
650+
Specialists Conference (PVSC) (pp. 1258-1264). IEEE.
651+
:doi:`10.1109/PVSC45281.2020.9300932`
652+
.. [2] Kasten, F. and Young, A.T., 1989. Revised optical air mass tables
653+
and approximation formula. Applied Optics, 28(22), pp.4735-4738.
654+
:doi:`10.1364/AO.28.004735`
655+
.. [3] Rigollier, C., Bauer, O. and Wald, L., 2000. On the clear sky model
656+
of the ESRA—European Solar Radiation Atlas—with respect to the Heliosat
657+
method. Solar energy, 68(1), pp.33-48.
658+
:doi:`10.1016/S0038-092X(99)00055-9`
659+
.. [4] SoDa website monthly Linke turbidity values:
660+
http://www.soda-pro.com/
661+
"""
662+
663+
_coefficients = {}
664+
_coefficients['multisi'] = (0.9847, -0.05237, 0.03034)
665+
_coefficients['monosi'] = (0.9845, -0.05169, 0.03034)
666+
_coefficients['fs-2'] = (1.002, -0.07108, 0.02465)
667+
_coefficients['fs-4'] = (0.9981, -0.05776, 0.02336)
668+
_coefficients['cigs'] = (0.9791, -0.03904, 0.03096)
669+
_coefficients['asi'] = (1.051, -0.1033, 0.009838)
670+
671+
if module_type is not None and coefficients is None:
672+
coefficients = _coefficients[module_type.lower()]
673+
elif module_type is None and coefficients is not None:
674+
pass
675+
elif module_type is None and coefficients is None:
676+
raise ValueError('No valid input provided, both module_type and ' +
677+
'coefficients are None. module_type can be one of ' +
678+
", ".join(_coefficients.keys()))
679+
else:
680+
raise ValueError('Cannot resolve input, must supply only one of ' +
681+
'module_type and coefficients. module_type can be ' +
682+
'one of' ", ".join(_coefficients.keys()))
683+
684+
coeff = coefficients
685+
ama = airmass_absolute
686+
kc = clearsky_index
687+
mismatch = coeff[0]*np.power(kc, coeff[1])*np.power(ama, coeff[2])
688+
689+
return mismatch
690+
691+
586692
def sr_to_qe(sr, wavelength=None, normalize=False):
587693
"""
588694
Convert spectral responsivities to quantum efficiencies.

pvlib/tests/test_spectrum.py

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
SPECTRL2_TEST_DATA = DATA_DIR / 'spectrl2_example_spectra.csv'
1010

11+
1112
@pytest.fixture
1213
def spectrl2_data():
1314
# reference spectra generated with solar_utils==0.3
@@ -175,25 +176,25 @@ def test_calc_spectral_mismatch_field(spectrl2_data):
175176

176177
@pytest.mark.parametrize("module_type,expect", [
177178
('cdte', np.array(
178-
[[ 0.99051020, 0.97640320, 0.93975028],
179-
[ 1.02928735, 1.01881074, 0.98578821],
180-
[ 1.04750335, 1.03814456, 1.00623986]])),
179+
[[0.99051020, 0.97640320, 0.93975028],
180+
[1.02928735, 1.01881074, 0.98578821],
181+
[1.04750335, 1.03814456, 1.00623986]])),
181182
('monosi', np.array(
182-
[[ 0.97769770, 1.02043409, 1.03574032],
183-
[ 0.98630905, 1.03055092, 1.04736262],
184-
[ 0.98828494, 1.03299036, 1.05026561]])),
183+
[[0.97769770, 1.02043409, 1.03574032],
184+
[0.98630905, 1.03055092, 1.04736262],
185+
[0.98828494, 1.03299036, 1.05026561]])),
185186
('polysi', np.array(
186-
[[ 0.97704080, 1.01705849, 1.02613202],
187-
[ 0.98992828, 1.03173953, 1.04260662],
188-
[ 0.99352435, 1.03588785, 1.04730718]])),
187+
[[0.97704080, 1.01705849, 1.02613202],
188+
[0.98992828, 1.03173953, 1.04260662],
189+
[0.99352435, 1.03588785, 1.04730718]])),
189190
('cigs', np.array(
190-
[[ 0.97459190, 1.02821696, 1.05067895],
191-
[ 0.97529378, 1.02967497, 1.05289307],
192-
[ 0.97269159, 1.02730558, 1.05075651]])),
191+
[[0.97459190, 1.02821696, 1.05067895],
192+
[0.97529378, 1.02967497, 1.05289307],
193+
[0.97269159, 1.02730558, 1.05075651]])),
193194
('asi', np.array(
194-
[[ 1.05552750, 0.87707583, 0.72243772],
195-
[ 1.11225204, 0.93665901, 0.78487953],
196-
[ 1.14555295, 0.97084011, 0.81994083]]))
195+
[[1.05552750, 0.87707583, 0.72243772],
196+
[1.11225204, 0.93665901, 0.78487953],
197+
[1.14555295, 0.97084011, 0.81994083]]))
197198
])
198199
def test_spectral_factor_firstsolar(module_type, expect):
199200
ams = np.array([1, 3, 5])
@@ -317,6 +318,62 @@ def test_spectral_factor_caballero_supplied_ambiguous():
317318
coefficients=None)
318319

319320

321+
@pytest.mark.parametrize("module_type,expected", [
322+
('asi', np.array([1.15534029, 1.1123772, 1.08286684, 1.01915462])),
323+
('fs-2', np.array([1.0694323, 1.04948777, 1.03556288, 0.9881471])),
324+
('fs-4', np.array([1.05234725, 1.037771, 1.0275516, 0.98820533])),
325+
('multisi', np.array([1.03310403, 1.02391703, 1.01744833, 0.97947605])),
326+
('monosi', np.array([1.03225083, 1.02335353, 1.01708734, 0.97950110])),
327+
('cigs', np.array([1.01475834, 1.01143927, 1.00909094, 0.97852966])),
328+
])
329+
def test_spectral_factor_pvspec(module_type, expected):
330+
ams = np.array([1.0, 1.5, 2.0, 1.5])
331+
kcs = np.array([0.4, 0.6, 0.8, 1.4])
332+
out = spectrum.spectral_factor_pvspec(ams, kcs,
333+
module_type=module_type)
334+
assert np.allclose(expected, out, atol=1e-8)
335+
336+
337+
@pytest.mark.parametrize("module_type,expected", [
338+
('asi', pd.Series([1.15534029, 1.1123772, 1.08286684, 1.01915462])),
339+
('fs-2', pd.Series([1.0694323, 1.04948777, 1.03556288, 0.9881471])),
340+
('fs-4', pd.Series([1.05234725, 1.037771, 1.0275516, 0.98820533])),
341+
('multisi', pd.Series([1.03310403, 1.02391703, 1.01744833, 0.97947605])),
342+
('monosi', pd.Series([1.03225083, 1.02335353, 1.01708734, 0.97950110])),
343+
('cigs', pd.Series([1.01475834, 1.01143927, 1.00909094, 0.97852966])),
344+
])
345+
def test_spectral_factor_pvspec_series(module_type, expected):
346+
ams = pd.Series([1.0, 1.5, 2.0, 1.5])
347+
kcs = pd.Series([0.4, 0.6, 0.8, 1.4])
348+
out = spectrum.spectral_factor_pvspec(ams, kcs,
349+
module_type=module_type)
350+
assert isinstance(out, pd.Series)
351+
assert np.allclose(expected, out, atol=1e-8)
352+
353+
354+
def test_spectral_factor_pvspec_supplied():
355+
# use the multisi coeffs
356+
coeffs = (0.9847, -0.05237, 0.03034)
357+
out = spectrum.spectral_factor_pvspec(1.5, 0.8, coefficients=coeffs)
358+
expected = 1.00860641
359+
assert_allclose(out, expected, atol=1e-8)
360+
361+
362+
def test_spectral_factor_pvspec_supplied_redundant():
363+
# Error when specifying both module_type and coefficients
364+
coeffs = (0.9847, -0.05237, 0.03034)
365+
with pytest.raises(ValueError, match='supply only one of'):
366+
spectrum.spectral_factor_pvspec(1.5, 0.8, module_type='multisi',
367+
coefficients=coeffs)
368+
369+
370+
def test_spectral_factor_pvspec_supplied_ambiguous():
371+
# Error when specifying neither module_type nor coefficients
372+
with pytest.raises(ValueError, match='No valid input provided'):
373+
spectrum.spectral_factor_pvspec(1.5, 0.8, module_type=None,
374+
coefficients=None)
375+
376+
320377
@pytest.fixture
321378
def sr_and_eqe_fixture():
322379
# Just some arbitrary data for testing the conversion functions

0 commit comments

Comments
 (0)