Skip to content

Commit

Permalink
Add PVSPEC spectral correction factor model (#2072)
Browse files Browse the repository at this point in the history
* 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 <80125792+echedey-ls@users.noreply.github.com>

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>

* Update pvlib/tests/test_spectrum.py

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>

* 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 <80125792+echedey-ls@users.noreply.github.com>

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>

* 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 <kevin.anderso@gmail.com>

* Update mismatch.py

* Update mismatch.py

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>

* Update pvlib/tests/test_spectrum.py

Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>

* Update pvlib/tests/test_spectrum.py

Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>

* Update mismatch.py

* Update pvlib/tests/test_spectrum.py

Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>

* name change

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>

* 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 <88548539+IoannisSifnaios@users.noreply.github.com>

* Update mismatch.py

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Ioannis Sifnaios <88548539+IoannisSifnaios@users.noreply.github.com>

* Update mismatch.py

* Update mismatch.py

* Update mismatch.py

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>

* 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 <cwhanse@sandia.gov>

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Cliff Hansen <cwhanse@sandia.gov>

* Update pvlib/spectrum/mismatch.py

Co-authored-by: Cliff Hansen <cwhanse@sandia.gov>

* 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 <80125792+echedey-ls@users.noreply.github.com>

* Update v0.11.0.rst

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

Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>

---------

Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com>
Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>
Co-authored-by: Ioannis Sifnaios <88548539+IoannisSifnaios@users.noreply.github.com>
Co-authored-by: Cliff Hansen <cwhanse@sandia.gov>
  • Loading branch information
5 people committed Jun 14, 2024
1 parent 734ac82 commit 19c9598
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 27 deletions.
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 @@ -35,7 +35,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 @@ -60,3 +62,4 @@ Contributors
* Siddharth Kaul (:ghuser:`k10blogger`)
* Ioannis Sifnaios (:ghuser:`IoannisSifnaios`)
* 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]
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

0 comments on commit 19c9598

Please sign in to comment.