Skip to content
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

Add PVSPEC spectral correction factor model #2072

Merged
merged 79 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
b006adb
Create test_pelland.py
RDaxini May 28, 2024
a5563e6
new spectral factor
RDaxini May 28, 2024
82d2892
Update __init__.py
RDaxini May 31, 2024
cb8a17a
Update mismatch.py
RDaxini May 31, 2024
fa555a7
Delete test_pelland.py
RDaxini May 31, 2024
6656fee
Update mismatch.py
RDaxini May 31, 2024
56c4628
Update mismatch.py
RDaxini May 31, 2024
acc3998
Update test_spectrum.py
RDaxini May 31, 2024
11b38bd
Update test_spectrum.py
RDaxini May 31, 2024
d36d3b0
correct function name
RDaxini May 31, 2024
486ff98
Update pvlib/spectrum/mismatch.py
RDaxini Jun 4, 2024
28e9339
Update pvlib/spectrum/mismatch.py
RDaxini Jun 4, 2024
b642d05
Update pvlib/spectrum/mismatch.py
RDaxini Jun 4, 2024
e430b27
Update pvlib/tests/test_spectrum.py
RDaxini Jun 4, 2024
83c1683
Update pvlib/spectrum/mismatch.py
RDaxini Jun 4, 2024
6812f51
Update spectrum.rst
RDaxini Jun 4, 2024
200aaed
Merge branch 'main' of https://github.com/RDaxini/pvlib-python
RDaxini Jun 4, 2024
b1ec78f
Update mismatch.py
RDaxini Jun 4, 2024
358a532
Update mismatch.py
RDaxini Jun 4, 2024
67299e3
Update mismatch.py
RDaxini Jun 6, 2024
d5c9725
Update mismatch.py
RDaxini Jun 7, 2024
0df61ed
Update mismatch.py
RDaxini Jun 7, 2024
0d41306
Update mismatch.py
RDaxini Jun 7, 2024
24e05b8
Update mismatch.py
RDaxini Jun 7, 2024
7b4f15d
Update mismatch.py
RDaxini Jun 7, 2024
3bd0b75
Update pvlib/spectrum/mismatch.py
RDaxini Jun 7, 2024
f0706de
Update pvlib/spectrum/mismatch.py
RDaxini Jun 7, 2024
6919364
Update pvlib/spectrum/mismatch.py
RDaxini Jun 7, 2024
ed13838
Update mismatch.py
RDaxini Jun 7, 2024
639fc75
Merge branch 'main' of https://github.com/RDaxini/pvlib-python
RDaxini Jun 7, 2024
7c50791
Update test_spectrum.py
RDaxini Jun 7, 2024
e7c032b
Update test_spectrum.py
RDaxini Jun 7, 2024
a7f9909
Update mismatch.py
RDaxini Jun 7, 2024
4616dc2
Update pvlib/spectrum/mismatch.py
RDaxini Jun 8, 2024
bd84537
Update mismatch.py
RDaxini Jun 8, 2024
f773ba7
Merge branch 'main' of https://github.com/RDaxini/pvlib-python
RDaxini Jun 8, 2024
b6a827b
Update mismatch.py
RDaxini Jun 8, 2024
3df83c3
Update pvlib/spectrum/mismatch.py
RDaxini Jun 8, 2024
f468db8
Update pvlib/tests/test_spectrum.py
RDaxini Jun 8, 2024
43727b7
Update pvlib/tests/test_spectrum.py
RDaxini Jun 8, 2024
9071dba
Merge branch 'main' of https://github.com/RDaxini/pvlib-python
RDaxini Jun 8, 2024
8e53d56
Update mismatch.py
RDaxini Jun 8, 2024
da1780f
Update pvlib/tests/test_spectrum.py
RDaxini Jun 8, 2024
15e328c
name change
RDaxini Jun 8, 2024
8925730
Update test_spectrum.py
RDaxini Jun 8, 2024
cc4a3d1
Update pvlib/spectrum/mismatch.py
RDaxini Jun 8, 2024
3d5eab6
Update test_spectrum.py
RDaxini Jun 8, 2024
f845adb
Merge branch 'main' of https://github.com/RDaxini/pvlib-python
RDaxini Jun 8, 2024
70f86c3
Update mismatch.py
RDaxini Jun 8, 2024
332fed5
Update test_spectrum.py
RDaxini Jun 8, 2024
974e098
Update test_spectrum.py
RDaxini Jun 8, 2024
cc05445
Update test_spectrum.py
RDaxini Jun 8, 2024
b36116d
Update test_spectrum.py
RDaxini Jun 8, 2024
c09e8b8
Update test_spectrum.py
RDaxini Jun 9, 2024
bd133ef
Update mismatch.py
RDaxini Jun 9, 2024
1a7416c
Update test_spectrum.py
RDaxini Jun 10, 2024
35b1411
Update pvlib/spectrum/mismatch.py
RDaxini Jun 11, 2024
b0385a1
Update mismatch.py
RDaxini Jun 11, 2024
e30694b
Update pvlib/spectrum/mismatch.py
RDaxini Jun 11, 2024
bcf8abc
Update mismatch.py
RDaxini Jun 11, 2024
ba7c34b
Update mismatch.py
RDaxini Jun 11, 2024
0f1bce0
Update mismatch.py
RDaxini Jun 11, 2024
7701d85
Update pvlib/spectrum/mismatch.py
RDaxini Jun 11, 2024
d5f0979
Update pvlib/spectrum/mismatch.py
RDaxini Jun 11, 2024
887faee
Update pvlib/spectrum/mismatch.py
RDaxini Jun 11, 2024
3c478d5
Update mismatch.py
RDaxini Jun 11, 2024
6203ca6
Merge remote-tracking branch 'upstream/main'
RDaxini Jun 11, 2024
9a4510c
Update mismatch.py
RDaxini Jun 11, 2024
8aeb8b5
Update mismatch.py
RDaxini Jun 11, 2024
7ba41ba
Update mismatch.py
RDaxini Jun 11, 2024
604e911
Update pvlib/spectrum/mismatch.py
RDaxini Jun 12, 2024
8897411
Update pvlib/spectrum/mismatch.py
RDaxini Jun 12, 2024
413cc04
Update pvlib/spectrum/mismatch.py
RDaxini Jun 12, 2024
4605d06
Update mismatch.py
RDaxini Jun 12, 2024
a772a94
test for output type
RDaxini Jun 13, 2024
55d34ed
Update mismatch.py
RDaxini Jun 13, 2024
38469cf
Update pvlib/tests/test_spectrum.py
RDaxini Jun 13, 2024
294eb8e
Update v0.11.0.rst
RDaxini Jun 14, 2024
bc37d49
Update docs/sphinx/source/whatsnew/v0.11.0.rst
RDaxini Jun 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ Spectrum
spectrum.spectral_factor_caballero
spectrum.spectral_factor_firstsolar
spectrum.spectral_factor_sapm
spectrum.spectral_factor_pvspec
spectrum.sr_to_qe
spectrum.qe_to_sr
5 changes: 4 additions & 1 deletion docs/sphinx/source/whatsnew/v0.11.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ Enhancements
efficiency ([unitless]) and vice versa. The conversion functions are
:py:func:`pvlib.spectrum.sr_to_qe` and :py:func:`pvlib.spectrum.qe_to_sr`
respectively. (:issue:`2040`, :pull:`2041`)

* Add function :py:func:`pvlib.spectrum.spectral_factor_pvspec`, which calculates the
spectral mismatch factor as a function of absolute airmass and clearsky index
using the PVSPEC model. (:issue:`1950`, :issue:`2065`, :pull:`2072`)

Bug fixes
~~~~~~~~~
Expand All @@ -56,3 +58,4 @@ Contributors
* Mark Mikofski (:ghuser:`mikofski`)
* Siddharth Kaul (:ghuser:`k10blogger`)
* Mark Campanelli (:ghuser:`markcampanelli`)
* Rajiv Daxini (:ghuser:`RDaxini`)
3 changes: 2 additions & 1 deletion pvlib/spectrum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
spectral_factor_caballero,
spectral_factor_firstsolar,
spectral_factor_sapm,
spectral_factor_pvspec,
sr_to_qe,
qe_to_sr,
qe_to_sr
)
126 changes: 116 additions & 10 deletions pvlib/spectrum/mismatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ def get_example_spectral_response(wavelength=None):
'''
# Contributed by Anton Driesse (@adriesse), PV Performance Labs. Aug. 2022

SR_DATA = np.array([[ 290, 0.00],
[ 350, 0.27],
[ 400, 0.37],
[ 500, 0.52],
[ 650, 0.71],
[ 800, 0.88],
[ 900, 0.97],
[ 950, 1.00],
SR_DATA = np.array([[290, 0.00],
[350, 0.27],
[400, 0.37],
[500, 0.52],
[650, 0.71],
[800, 0.88],
[900, 0.97],
[950, 1.00],
[1000, 0.93],
[1050, 0.58],
[1100, 0.21],
Expand Down Expand Up @@ -256,7 +256,7 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
max_precipitable_water=8):
r"""
Spectral mismatch modifier based on precipitable water and absolute
(pressure-adjusted) airmass.
(pressure-adjusted) air mass.

Estimates a spectral mismatch modifier :math:`M` representing the effect on
module short circuit current of variation in the spectral
Expand Down Expand Up @@ -294,7 +294,7 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
atmospheric precipitable water. [cm]

airmass_absolute : numeric
absolute (pressure-adjusted) airmass. [unitless]
absolute (pressure-adjusted) air mass. [unitless]

module_type : str, optional
a string specifying a cell type. Values of 'cdte', 'monosi', 'xsi',
Expand Down Expand Up @@ -583,6 +583,112 @@ def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500,
return modifier


def spectral_factor_pvspec(airmass_absolute, clearsky_index,
module_type=None, coefficients=None):
r"""
Estimate a technology-specific spectral mismatch modifier from absolute
airmass and clear sky index using the PVSPEC model.

The PVSPEC spectral mismatch model includes the effects of cloud cover on
the irradiance spectrum. Model coefficients are derived using spectral
irradiance and other meteorological data from eight locations. Coefficients
for six module types are available via the ``module_type`` parameter.
More details on the model can be found in [1]_.

Parameters
----------
airmass_absolute : numeric
absolute (pressure-adjusted) airmass. [unitless]
RDaxini marked this conversation as resolved.
Show resolved Hide resolved

clearsky_index: numeric
clear sky index. [unitless]

module_type : str, optional
One of the following PV technology strings from [1]_:

* ``'fs4-1'`` - First Solar series 4-1 and earlier CdTe module.
* ``'fs4-2'`` - First Solar 4-2 and later CdTe module.
* ``'monosi'``, - anonymous monocrystalline Si module.
* ``'multisi'``, - anonymous multicrystalline Si module.
* ``'cigs'`` - anonymous copper indium gallium selenide module.
* ``'asi'`` - anonymous amorphous silicon module.

coefficients : array-like, optional
user-defined coefficients, if not using one of the default coefficient
sets via the ``module_type`` parameter.

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

Notes
-----
The PVSPEC model parameterises the spectral mismatch factor as a function
of absolute air mass and the clear sky index as follows:

.. math::

M = a_1 k_c^{a_2} AM_a^{a_3},

where :math:`M` is the spectral mismatch factor, :math:`k_c` is the clear
sky index, :math:`AM_a` is the absolute air mass, and :math:`a_1, a_2, a_3`
are module-specific coefficients. In the PVSPEC model publication, absolute
air mass (denoted as :math:`AM`) is estimated starting from the Kasten and
Young relative air mass [2]_. The clear sky index, which is the ratio of
GHI to clear sky GHI, uses the ESRA model [3]_ to estimate the clear sky
GHI with monthly Linke turbidity values from [4]_ as inputs.

References
----------
.. [1] Pelland, S., Beswick, C., Thevenard, D., Côté, A., Pai, A. and
Poissant, Y., 2020. Development and testing of the PVSPEC model of
photovoltaic spectral mismatch factor. In 2020 47th IEEE Photovoltaic
Specialists Conference (PVSC) (pp. 1258-1264). IEEE.
:doi:`10.1109/PVSC45281.2020.9300932`
.. [2] Kasten, F. and Young, A.T., 1989. Revised optical air mass tables
and approximation formula. Applied Optics, 28(22), pp.4735-4738.
:doi:`10.1364/AO.28.004735`
.. [3] Rigollier, C., Bauer, O. and Wald, L., 2000. On the clear sky model
of the ESRA—European Solar Radiation Atlas—with respect to the Heliosat
method. Solar energy, 68(1), pp.33-48.
:doi:`10.1016/S0038-092X(99)00055-9`
.. [4] SoDa website monthly Linke turbidity values:
http://www.soda-pro.com/
"""

_coefficients = {}
_coefficients['multisi'] = (0.9847, -0.05237, 0.03034)
_coefficients['monosi'] = (0.9845, -0.05169, 0.03034)
_coefficients['fs-2'] = (1.002, -0.07108, 0.02465)
_coefficients['fs-4'] = (0.9981, -0.05776, 0.02336)
_coefficients['cigs'] = (0.9791, -0.03904, 0.03096)
_coefficients['asi'] = (1.051, -0.1033, 0.009838)

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 ValueError('No valid input provided, both module_type and ' +
'coefficients are None. module_type can be one of ' +
", ".join(_coefficients.keys()))
else:
raise ValueError('Cannot resolve input, must supply only one of ' +
'module_type and coefficients. module_type can be ' +
'one of' ", ".join(_coefficients.keys()))

coeff = coefficients
ama = airmass_absolute
kc = clearsky_index
mismatch = coeff[0]*np.power(kc, coeff[1])*np.power(ama, coeff[2])

return mismatch


def sr_to_qe(sr, wavelength=None, normalize=False):
"""
Convert spectral responsivities to quantum efficiencies.
Expand Down
87 changes: 72 additions & 15 deletions pvlib/tests/test_spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

SPECTRL2_TEST_DATA = DATA_DIR / 'spectrl2_example_spectra.csv'


@pytest.fixture
def spectrl2_data():
# reference spectra generated with solar_utils==0.3
Expand Down Expand Up @@ -175,25 +176,25 @@ def test_calc_spectral_mismatch_field(spectrl2_data):

@pytest.mark.parametrize("module_type,expect", [
('cdte', np.array(
[[ 0.99051020, 0.97640320, 0.93975028],
[ 1.02928735, 1.01881074, 0.98578821],
[ 1.04750335, 1.03814456, 1.00623986]])),
[[0.99051020, 0.97640320, 0.93975028],
[1.02928735, 1.01881074, 0.98578821],
[1.04750335, 1.03814456, 1.00623986]])),
('monosi', np.array(
[[ 0.97769770, 1.02043409, 1.03574032],
[ 0.98630905, 1.03055092, 1.04736262],
[ 0.98828494, 1.03299036, 1.05026561]])),
[[0.97769770, 1.02043409, 1.03574032],
[0.98630905, 1.03055092, 1.04736262],
[0.98828494, 1.03299036, 1.05026561]])),
('polysi', np.array(
[[ 0.97704080, 1.01705849, 1.02613202],
[ 0.98992828, 1.03173953, 1.04260662],
[ 0.99352435, 1.03588785, 1.04730718]])),
[[0.97704080, 1.01705849, 1.02613202],
[0.98992828, 1.03173953, 1.04260662],
[0.99352435, 1.03588785, 1.04730718]])),
('cigs', np.array(
[[ 0.97459190, 1.02821696, 1.05067895],
[ 0.97529378, 1.02967497, 1.05289307],
[ 0.97269159, 1.02730558, 1.05075651]])),
[[0.97459190, 1.02821696, 1.05067895],
[0.97529378, 1.02967497, 1.05289307],
[0.97269159, 1.02730558, 1.05075651]])),
('asi', np.array(
[[ 1.05552750, 0.87707583, 0.72243772],
[ 1.11225204, 0.93665901, 0.78487953],
[ 1.14555295, 0.97084011, 0.81994083]]))
[[1.05552750, 0.87707583, 0.72243772],
[1.11225204, 0.93665901, 0.78487953],
[1.14555295, 0.97084011, 0.81994083]]))
])
def test_spectral_factor_firstsolar(module_type, expect):
ams = np.array([1, 3, 5])
Expand Down Expand Up @@ -317,6 +318,62 @@ def test_spectral_factor_caballero_supplied_ambiguous():
coefficients=None)


@pytest.mark.parametrize("module_type,expected", [
('asi', np.array([1.15534029, 1.1123772, 1.08286684, 1.01915462])),
('fs-2', np.array([1.0694323, 1.04948777, 1.03556288, 0.9881471])),
('fs-4', np.array([1.05234725, 1.037771, 1.0275516, 0.98820533])),
('multisi', np.array([1.03310403, 1.02391703, 1.01744833, 0.97947605])),
('monosi', np.array([1.03225083, 1.02335353, 1.01708734, 0.97950110])),
('cigs', np.array([1.01475834, 1.01143927, 1.00909094, 0.97852966])),
])
def test_spectral_factor_pvspec(module_type, expected):
ams = np.array([1.0, 1.5, 2.0, 1.5])
kcs = np.array([0.4, 0.6, 0.8, 1.4])
out = spectrum.spectral_factor_pvspec(ams, kcs,
module_type=module_type)
assert np.allclose(expected, out, atol=1e-8)


@pytest.mark.parametrize("module_type,expected", [
('asi', pd.Series([1.15534029, 1.1123772, 1.08286684, 1.01915462])),
('fs-2', pd.Series([1.0694323, 1.04948777, 1.03556288, 0.9881471])),
('fs-4', pd.Series([1.05234725, 1.037771, 1.0275516, 0.98820533])),
('multisi', pd.Series([1.03310403, 1.02391703, 1.01744833, 0.97947605])),
('monosi', pd.Series([1.03225083, 1.02335353, 1.01708734, 0.97950110])),
('cigs', pd.Series([1.01475834, 1.01143927, 1.00909094, 0.97852966])),
])
def test_spectral_factor_pvspec_series(module_type, expected):
ams = pd.Series([1.0, 1.5, 2.0, 1.5])
kcs = pd.Series([0.4, 0.6, 0.8, 1.4])
out = spectrum.spectral_factor_pvspec(ams, kcs,
module_type=module_type)
assert isinstance(out, pd.Series)
assert np.allclose(expected, out, atol=1e-8)


def test_spectral_factor_pvspec_supplied():
# use the multisi coeffs
coeffs = (0.9847, -0.05237, 0.03034)
out = spectrum.spectral_factor_pvspec(1.5, 0.8, coefficients=coeffs)
expected = 1.00860641
assert_allclose(out, expected, atol=1e-8)


def test_spectral_factor_pvspec_supplied_redundant():
# Error when specifying both module_type and coefficients
coeffs = (0.9847, -0.05237, 0.03034)
with pytest.raises(ValueError, match='supply only one of'):
spectrum.spectral_factor_pvspec(1.5, 0.8, module_type='multisi',
coefficients=coeffs)


def test_spectral_factor_pvspec_supplied_ambiguous():
# Error when specifying neither module_type nor coefficients
with pytest.raises(ValueError, match='No valid input provided'):
spectrum.spectral_factor_pvspec(1.5, 0.8, module_type=None,
coefficients=None)


@pytest.fixture
def sr_and_eqe_fixture():
# Just some arbitrary data for testing the conversion functions
Expand Down
Loading