Skip to content

update spectral_factor_firstsolar #2100

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

Merged
merged 25 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions docs/sphinx/source/whatsnew/v0.11.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Enhancements
* Add new losses function that accounts for non-uniform irradiance on bifacial
modules, :py:func:`pvlib.bifacial.power_mismatch_deline`.
(:issue:`2045`, :pull:`2046`)
* Add new parameters for min/max absolute air mass to
:py:func:`pvlib.spectrum.spectral_factor_firstsolar`.
(:issue:`2086`, :pull:`2100`)


Bug fixes
Expand All @@ -34,3 +37,4 @@ Requirements
Contributors
~~~~~~~~~~~~
* Echedey Luis (:ghuser:`echedey-ls`)
* Rajiv Daxini (:ghuser:`RDaxini`)
89 changes: 47 additions & 42 deletions pvlib/spectrum/mismatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,21 +358,17 @@
def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
module_type=None, coefficients=None,
min_precipitable_water=0.1,
max_precipitable_water=8):
max_precipitable_water=8,
min_airmass_absolute=1.0,
max_airmass_absolute=10):
r"""
Spectral mismatch modifier based on precipitable water and absolute
(pressure-adjusted) air mass.

Estimates a spectral mismatch modifier :math:`M` representing the effect on
module short circuit current of variation in the spectral
irradiance. :math:`M` is estimated from absolute (pressure currected) air
mass, :math:`AM_a`, and precipitable water, :math:`Pw`, using the following
function:

.. math::

M = c_1 + c_2 AM_a + c_3 Pw + c_4 AM_a^{0.5}
+ c_5 Pw^{0.5} + c_6 \frac{AM_a} {Pw^{0.5}}
Estimates the spectral mismatch modifier, :math:`M`, representing the
effect of variation in the spectral irradiance on the module short circuit
current :math:`M` is estimated from absolute (pressure-corrected) air
mass, :math:`AM_a`, and precipitable water, :math:`Pw`.

Default coefficients are determined for several cell types with
known quantum efficiency curves, by using the Simple Model of the
Expand All @@ -386,12 +382,10 @@
* spectrum simulated on a plane normal to the sun
* All other parameters fixed at G173 standard

From these simulated spectra, M is calculated using the known
From these simulated spectra, :math:`M` is calculated using the known
quantum efficiency curves. Multiple linear regression is then
applied to fit Eq. 1 to determine the coefficients for each module.

Based on the PVLIB Matlab function ``pvl_FSspeccorr`` by Mitchell
Lee and Alex Panchula of First Solar, 2016 [2]_.
applied to fit Eq. 1 to determine the coefficients for each module. More
details on the model can be found in [2]_.

Parameters
----------
Expand Down Expand Up @@ -430,12 +424,20 @@
min_precipitable_water : float, default 0.1
minimum atmospheric precipitable water. Any ``precipitable_water``
value lower than ``min_precipitable_water``
is set to ``min_precipitable_water`` to avoid model divergence. [cm]
is set to ``min_precipitable_water``. [cm]

max_precipitable_water : float, default 8
maximum atmospheric precipitable water. Any ``precipitable_water``
value greater than ``max_precipitable_water``
is set to ``np.nan`` to avoid model divergence. [cm]
is set to ``np.nan``. [cm]

min_airmass_absolute : float, default 0.58
minimum absolute airmass. Any ``airmass_absolute`` value lower than
``min_airmass_absolute`` is set to ``min_airmass_absolute``. [unitless]

max_airmass_absolute : float, default 10
minimum absolute airmass. Any ``airmass_absolute`` value greater than
``max_airmass_absolute`` is set to ``max_airmass_absolute``. [unitless]

Returns
-------
Expand All @@ -445,6 +447,20 @@
effective irradiance, i.e., the irradiance that is converted to
electrical current.

Notes
----
The ``spectral_factor_firstsolar`` model takes the following form:

.. math::

M = c_1 + c_2 AM_a + c_3 Pw + c_4 AM_a^{0.5}
+ c_5 Pw^{0.5} + c_6 \frac{AM_a} {Pw^{0.5}}.

The default values for the limits applied to :math:`AM_a` and :math:`Pw`
via the ``min_precipitable_water``, ``max_precipitable_water``,
``min_airmass_absolute``, and ``max_airmass_absolute`` are set to prevent
divergence of the model presented above.

References
----------
.. [1] Gueymard, Christian. SMARTS2: a simple model of the atmospheric
Expand All @@ -461,35 +477,25 @@
MMF Approach, TUV Rheinland Energy GmbH report 21237296.003,
January 2017
"""

# --- Screen Input Data ---

# *** Pw ***
# Replace Pw Values below 0.1 cm with 0.1 cm to prevent model from
# diverging"
pw = np.atleast_1d(precipitable_water)
pw = pw.astype('float64')
if np.min(pw) < min_precipitable_water:
pw = np.maximum(pw, min_precipitable_water)
warn('Exceptionally low pw values replaced with '
f'{min_precipitable_water} cm to prevent model divergence')
warn('Low pw values replaced with 'f'{min_precipitable_water} cm in '
'the calculation of spectral mismatch.')

# Warn user about Pw data that is exceptionally high
if np.max(pw) > max_precipitable_water:
pw[pw > max_precipitable_water] = np.nan
warn('Exceptionally high pw values replaced by np.nan: '
'check input data.')

# *** AMa ***
# Replace Extremely High AM with AM 10 to prevent model divergence
# AM > 10 will only occur very close to sunset
if np.max(airmass_absolute) > 10:
airmass_absolute = np.minimum(airmass_absolute, 10)

# Warn user about AMa data that is exceptionally low
if np.min(airmass_absolute) < 0.58:
warn('Exceptionally low air mass: ' +
'model not intended for extra-terrestrial use')
warn('High pw values replaced with np.nan in '
'the calculation of spectral mismatch.')

if np.max(airmass_absolute) > max_airmass_absolute:
airmass_absolute = np.minimum(airmass_absolute, max_airmass_absolute)

Check warning on line 493 in pvlib/spectrum/mismatch.py

View check run for this annotation

Codecov / codecov/patch

pvlib/spectrum/mismatch.py#L493

Added line #L493 was not covered by tests

if np.min(airmass_absolute) < min_airmass_absolute:
airmass_absolute = np.maximum(airmass_absolute, min_airmass_absolute)
warn('Low AMa values replaced with 'f'{min_airmass_absolute} in the'
' calculation of spectral mismatch.')
# pvl_absoluteairmass(1,pvl_alt2pres(4340)) = 0.58 Elevation of
# Mina Pirquita, Argentian = 4340 m. Highest elevation city with
# population over 50,000.
Expand Down Expand Up @@ -519,7 +525,6 @@
raise TypeError('Cannot resolve input, must supply only one of ' +
'module_type and coefficients')

# Evaluate Spectral Shift
coeff = coefficients
ama = airmass_absolute
modifier = (
Expand Down Expand Up @@ -581,7 +586,7 @@
available here via the ``module_type`` parameter were determined
by fitting the model equations to spectral factors calculated from
global tilted spectral irradiance measurements taken in the city of
Jaén, Spain. See [1]_ for details.
Jaén, Spain. See [1]_ for details.

Parameters
----------
Expand Down
25 changes: 10 additions & 15 deletions pvlib/tests/test_spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,36 +276,31 @@ def test_spectral_factor_firstsolar_ambiguous_both():
spectrum.spectral_factor_firstsolar(1, 1, 'cdte', coefficients=coeffs)


def test_spectral_factor_firstsolar_large_airmass():
# test that airmass > 10 is treated same as airmass==10
m_eq10 = spectrum.spectral_factor_firstsolar(1, 10, 'monosi')
m_gt10 = spectrum.spectral_factor_firstsolar(1, 15, 'monosi')
assert_allclose(m_eq10, m_gt10)


def test_spectral_factor_firstsolar_low_airmass():
with pytest.warns(UserWarning, match='Exceptionally low air mass'):
m_eq58 = spectrum.spectral_factor_firstsolar(1, 0.58, 'monosi')
m_lt58 = spectrum.spectral_factor_firstsolar(1, 0.1, 'monosi')
assert_allclose(m_eq58, m_lt58)
with pytest.warns(UserWarning, match='Low AMa values replaced with'):
_ = spectrum.spectral_factor_firstsolar(1, 0.1, 'monosi')


def test_spectral_factor_firstsolar_range():
with pytest.warns(UserWarning, match='Exceptionally high pw values'):
out = spectrum.spectral_factor_firstsolar(np.array([.1, 3, 10]),
np.array([1, 3, 5]),
module_type='monosi')
out = spectrum.spectral_factor_firstsolar(np.array([.1, 3, 10]),
np.array([1, 3, 5]),
module_type='monosi')
expected = np.array([0.96080878, 1.03055092, np.nan])
assert_allclose(out, expected, atol=1e-3)
with pytest.warns(UserWarning, match='Exceptionally high pw values'):
with pytest.warns(UserWarning, match='High pw values replaced with'):
out = spectrum.spectral_factor_firstsolar(6, 1.5,
max_precipitable_water=5,
module_type='monosi')
with pytest.warns(UserWarning, match='Exceptionally low pw values'):
with pytest.warns(UserWarning, match='Low pw values replaced with'):
out = spectrum.spectral_factor_firstsolar(np.array([0, 3, 8]),
np.array([1, 3, 5]),
module_type='monosi')
expected = np.array([0.96080878, 1.03055092, 1.04932727])
assert_allclose(out, expected, atol=1e-3)
with pytest.warns(UserWarning, match='Exceptionally low pw values'):
with pytest.warns(UserWarning, match='Low pw values replaced with'):
out = spectrum.spectral_factor_firstsolar(0.2, 1.5,
min_precipitable_water=1,
module_type='monosi')
Expand Down
Loading