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

NREL non-uniform irradiance mismatch loss for bifacial modules #2046

Merged
merged 87 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from 85 commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
9c68605
Preliminar implementation, tests, docs and gallery example
echedey-ls Apr 26, 2024
7c17712
Merge branch 'main' into nrel-mismatch-loss
echedey-ls May 6, 2024
23320db
Update versionadded
echedey-ls May 10, 2024
47fa80d
Add example
echedey-ls May 10, 2024
84346d2
docstring update
echedey-ls May 10, 2024
bdb9955
Update v0.11.0.rst
echedey-ls May 10, 2024
914d009
who would have guessed it's always the linter?
echedey-ls May 10, 2024
29f4c66
Update plot_irradiance_nonuniformity_loss.py
echedey-ls May 10, 2024
03011d3
equation fixes
echedey-ls May 10, 2024
de1744a
RMAD properties application typo
echedey-ls May 10, 2024
7f27c9f
does this fix the link?
echedey-ls May 10, 2024
2cdc95f
yet another link
echedey-ls May 10, 2024
0698839
These links will never work, I suppose
echedey-ls May 10, 2024
7f27f1c
Move some "Notes" equations to example and rearrange things there
echedey-ls May 11, 2024
58c05bd
give this equation it's space
echedey-ls May 11, 2024
b973959
eq rendering fixes?
echedey-ls May 11, 2024
448b647
maths are not mathing today
echedey-ls May 11, 2024
5768473
???
echedey-ls May 11, 2024
b1a7e4a
??? x2
echedey-ls May 11, 2024
d1b476d
??? x3
echedey-ls May 11, 2024
d037314
i feel stupid for this
echedey-ls May 11, 2024
4ec952b
Change preprint ref to published one
echedey-ls May 14, 2024
b0cadb5
Published paper coeffs
echedey-ls May 18, 2024
9f51b68
Lots of improvements
echedey-ls May 19, 2024
67d3737
Linter - also missing Eq (7)
echedey-ls May 19, 2024
10ac204
Test polynomial input
echedey-ls May 19, 2024
9290f41
Math
echedey-ls May 20, 2024
c7b94a0
Test exception
echedey-ls May 20, 2024
3599991
Update pvsystem.py
echedey-ls May 20, 2024
c965225
Update plot_irradiance_nonuniformity_loss.py
echedey-ls May 20, 2024
0c15443
Add fill factor ratio
echedey-ls May 20, 2024
d141189
ª
echedey-ls May 20, 2024
8a19a4d
Trying new things
echedey-ls May 21, 2024
c32f78a
Revert "Trying new things"
echedey-ls May 21, 2024
5f69b6d
Update pvsystem.py
echedey-ls May 21, 2024
4c354ea
:(
echedey-ls May 21, 2024
a2de166
:((
echedey-ls May 21, 2024
b572f45
I hope we dont miss that reference
echedey-ls May 22, 2024
1b0565b
Update pvsystem.py
echedey-ls May 22, 2024
87f46f0
Remove second section of the example
echedey-ls May 27, 2024
ae775ec
Minor text upgrade
echedey-ls May 27, 2024
4c75534
Merge branch 'main' into nrel-mismatch-loss
echedey-ls May 27, 2024
1906e20
Update pvsystem.py
echedey-ls May 27, 2024
f5863ce
Port to namespace `pvlib.bifacial`
echedey-ls May 28, 2024
a7d3f7e
Rename to ``power_mismatch_deline``, document in ``bifacial.rst``
echedey-ls May 28, 2024
84f702f
fillfactor only for predefined models
echedey-ls May 28, 2024
3ad18a6
Docstring rewording
echedey-ls May 28, 2024
b6c6fb2
Linter
echedey-ls May 28, 2024
18a397f
More docs rewording
echedey-ls May 28, 2024
1d7d86c
Merge branch 'main' into nrel-mismatch-loss
echedey-ls Jun 11, 2024
8bae370
Apply suggestions from Dax
echedey-ls Jun 12, 2024
75ae58b
Unneded image Dominguez_et_al_PVSC2023.png
echedey-ls Jun 12, 2024
b106ff8
Rename file
echedey-ls Jun 12, 2024
4c78987
Fix equations
echedey-ls Jun 12, 2024
56c7d2e
yup it didnt save
echedey-ls Jun 12, 2024
d225a6d
lintaaaaaaarrrr
echedey-ls Jun 12, 2024
36f3be0
Eq 7 numbering
echedey-ls Jun 12, 2024
0c99269
Revert unintended changes
echedey-ls Jun 12, 2024
a46245a
Adam code review
echedey-ls Jun 12, 2024
a4a32e0
Links?
echedey-ls Jun 12, 2024
88273ba
Referencing equations manually
echedey-ls Jun 12, 2024
f096a0a
Polynomial links
echedey-ls Jun 12, 2024
5945b74
Dont abuse math mode
echedey-ls Jun 12, 2024
adbcbeb
Update loss_models.py
echedey-ls Jun 12, 2024
6f5c69f
I forgot
echedey-ls Jun 12, 2024
770b4a5
Adam Code Review
echedey-ls Jun 12, 2024
329312a
Merge branch 'main' into nrel-mismatch-loss
echedey-ls Jun 20, 2024
9d0411b
Merge branch 'main' into nrel-mismatch-loss
echedey-ls Jun 21, 2024
a9d507f
whatsnews
echedey-ls Jun 21, 2024
243de31
Update v0.11.0.rst
echedey-ls Jun 21, 2024
204f8b7
Weird that some tests pass and others dont
echedey-ls Jun 21, 2024
a88cb4c
Update loss_models.py
echedey-ls Jun 21, 2024
0136b3a
document
echedey-ls Jun 21, 2024
c6ca308
Update loss_models.py
echedey-ls Jun 21, 2024
adad0c1
Kevin's review (I)
echedey-ls Jun 27, 2024
6fafcb4
Kevin's review (II)
echedey-ls Jun 27, 2024
436b0e2
Kevin's review (III)
echedey-ls Jun 28, 2024
7d91db6
Update v0.11.1.rst
echedey-ls Jun 28, 2024
b1b51bd
minor changes
echedey-ls Jun 28, 2024
3991800
Forgot to update tests
echedey-ls Jun 28, 2024
aeaabb4
Accordingly modify model, IO, docs to unitless model
echedey-ls Jun 28, 2024
9289412
Update loss_models.py
echedey-ls Jun 28, 2024
f025133
Update loss_models.py
echedey-ls Jun 28, 2024
69a5acf
Apply suggestions from code review
echedey-ls Jun 30, 2024
945d61a
Code review from Cliff
echedey-ls Jun 30, 2024
cecc337
Rendering issues, code review from Kevin
echedey-ls Jul 1, 2024
289dbeb
Little missing newline
echedey-ls Jul 1, 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
129 changes: 129 additions & 0 deletions docs/examples/bifacial/plot_irradiance_nonuniformity_loss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""
Plot Irradiance Non-uniformity Loss
===================================

Calculate the DC power lost to irradiance non-uniformity in a bifacial PV
array.
"""

# %%
# The incident irradiance on the backside of a bifacial PV module is
# not uniform due to neighboring rows, the ground albedo, and site conditions.
# When each cell works at different irradiance levels, the power produced by
# the module is less than the sum of the power produced by each cell since the
# maximum power point (MPP) of each cell is different, but cells connected in
# series will operate at the same current.
# This is known as irradiance non-uniformity loss.
#
# Calculating the IV curve of each cell and then matching the working point of
# the whole module is computationally expensive, so a simple model to account
# for this loss is of interest. Deline et al. [1]_ proposed a model based on
# the Relative Mean Absolute Difference (RMAD) of the irradiance of each cell.
# They considered the standard deviation of the cells' irradiances, but they
# found that the RMAD was a better predictor of the mismatch loss.
#
# This example demonstrates how to model the irradiance non-uniformity loss
# from the irradiance levels of each cell in a PV module.
#
# The function
# :py:func:`pvlib.bifacial.power_mismatch_deline` is
# used to transform the Relative Mean Absolute Difference (RMAD) of the
# irradiance into a power loss mismatch. Down below you will find a
# numpy-based implementation of the RMAD function.
#
# References
# ----------
# .. [1] C. Deline, S. Ayala Pelaez, S. MacAlpine, and C. Olalla, 'Estimating
# and parameterizing mismatch power loss in bifacial photovoltaic
# systems', Progress in Photovoltaics: Research and Applications, vol. 28,
# no. 7, pp. 691-703, 2020, :doi:`10.1002/pip.3259`.
#
# .. sectionauthor:: Echedey Luis <echelual (at) gmail.com>

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.cm import ScalarMappable
from matplotlib.colors import Normalize

from pvlib.bifacial import power_mismatch_deline

# %%
# Problem description
# -------------------
# Let's set a fixed irradiance to each cell row of the PV array with the values
# described in Figure 1 (A), [1]_. We will cover this case for educational
# purposes, although it can be achieved with the packages
# :ref:`solarfactors <https://github.com/pvlib/solarfactors/>` and
# :ref:`bifacial_radiance <https://github.com/NREL/bifacial_radiance>`.
#
# Here we set and plot the global irradiance level of each cell.

x = np.arange(12, 0, -1)
y = np.arange(6, 0, -1)
cells_irrad = np.repeat([1059, 976, 967, 986, 1034, 1128], len(x)).reshape(
len(y), len(x)
)

color_map = "gray"
color_norm = Normalize(930, 1150)

fig, ax = plt.subplots()
fig.suptitle("Global Irradiance Levels of Each Cell")
fig.colorbar(
ScalarMappable(cmap=color_map, norm=color_norm),
ax=ax,
orientation="vertical",
label="$[W/m^2]$",
)
ax.set_aspect("equal")
ax.pcolormesh(
x,
y,
cells_irrad,
shading="nearest",
edgecolors="black",
cmap=color_map,
norm=color_norm,
)

# %%
# Relative Mean Absolute Difference
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Calculate the Relative Mean Absolute Difference (RMAD) of the cells'
# irradiances with the following function, Eq. (4) of [1]_:
#
# .. math::
#
# \Delta \left[ unitless \right] = \frac{1}{n^2 \bar{G}_{total}}
# \sum_{i=1}^{n} \sum_{j=1}^{n} \lvert G_{total,i} - G_{total,j} \rvert
#


def rmad(data, axis=None):
"""
Relative Mean Absolute Difference. Output is [Unitless].
https://stackoverflow.com/a/19472336/19371110
"""
mean = np.mean(data, axis)
mad = np.mean(np.absolute(data - mean), axis)
return mad / mean


rmad_cells = rmad(cells_irrad)

# this is the same as a column's RMAD!
print(rmad_cells == rmad(cells_irrad[:, 0]))

# %%
# Mismatch Loss
# ^^^^^^^^^^^^^
# Calculate the power loss ratio due to the irradiance non-uniformity
# with :py:func:`pvlib.bifacial.power_mismatch_deline`.

mismatch_loss = power_mismatch_deline(rmad_cells)

print(f"RMAD of the cells' irradiance: {rmad_cells:.3} [unitless]")
print(
"Power loss due to the irradiance non-uniformity: "
+ f"{mismatch_loss:.3} [unitless]"
)
7 changes: 7 additions & 0 deletions docs/sphinx/source/reference/bifacial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ Functions for calculating front and back surface irradiance
bifacial.infinite_sheds.get_irradiance
bifacial.infinite_sheds.get_irradiance_poa

Loss models that are specific to bifacial PV systems

.. autosummary::
:toctree: generated/

bifacial.power_mismatch_deline

Utility functions for bifacial modeling

.. autosummary::
Expand Down
5 changes: 4 additions & 1 deletion docs/sphinx/source/whatsnew/v0.11.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Deprecations

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`)


Bug fixes
Expand All @@ -30,4 +33,4 @@ Requirements

Contributors
~~~~~~~~~~~~

* Echedey Luis (:ghuser:`echedey-ls`)
8 changes: 4 additions & 4 deletions pvlib/bifacial/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""
The ``bifacial`` module contains functions to model irradiance for bifacial
modules.

The ``bifacial`` submodule contains functions to model bifacial modules.
"""

from pvlib._deprecation import deprecated
from pvlib.bifacial import pvfactors, infinite_sheds, utils # noqa: F401
from pvlib.bifacial import pvfactors, infinite_sheds, utils # noqa: F401
from .loss_models import power_mismatch_deline # noqa: F401

pvfactors_timeseries = deprecated(
since='0.9.1',
Expand Down
153 changes: 153 additions & 0 deletions pvlib/bifacial/loss_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import numpy as np
import pandas as pd


def power_mismatch_deline(
rmad,
coefficients=(0, 0.142, 0.032 * 100),
fill_factor: float = None,
fill_factor_reference: float = 0.79,
):
r"""
Estimate DC power loss due to irradiance non-uniformity.

This model is described for bifacial modules in [1]_, where the backside
irradiance is less uniform due to mounting and site conditions.

The power loss is estimated by a polynomial model of the Relative Mean
Absolute Difference (RMAD) of the cell-by-cell total irradiance.

Use ``fill_factor`` to account for different fill factors between the
data used to fit the model and the module of interest. Specify the model's fill factor with
``fill_factor_reference``.

.. versionadded:: 0.11.1

Parameters
----------
rmad : numeric
The Relative Mean Absolute Difference of the cell-by-cell total
irradiance. [Unitless]

See the *Notes* section for the equation to calculate ``rmad`` from the
bifaciality and the front and back irradiances.

coefficients : float collection or numpy.polynomial.polynomial.Polynomial, default ``(0, 0.142, 0.032 * 100)``
The polynomial coefficients to use.

If a :external:class:`numpy.polynomial.polynomial.Polynomial`,
it is evaluated as is. If not a ``Polynomial``, it must be the
coefficients of a polynomial in ``rmad``, where the first element is
the constant term and the last element is the highest order term. A
:external:class:`~numpy.polynomial.polynomial.Polynomial`
will be created internally.

fill_factor : float, optional
Fill factor at standard test condition (STC) of the module.
Accounts for different fill factors between the trained model and the
module under non-uniform irradiance.
If not provided, the default ``fill_factor_reference`` of 0.79 is used.

fill_factor_reference : float, default 0.79
Fill factor at STC of the module used to train the model.

Returns
-------
loss : numeric
The fractional power loss. [Unitless]

Output will be a ``pandas.Series`` if ``rmad`` is a ``pandas.Series``.

Notes
-----
The default model implemented is equation (11) [1]_:

.. math::

M[\%] &= 0.142 \Delta[\%] + 0.032 \Delta^2[\%] \qquad \text{(11)}
M[-] &= 0.142 \Delta[-] + 0.032 \times 100 \Delta^2[-]
echedey-ls marked this conversation as resolved.
Show resolved Hide resolved

where the upper equation is in percentage (same as paper) and the lower
one is unitless. The implementation uses the unitless version, where
:math:`M[-]` is the mismatch power loss [unitless] and
:math:`\Delta[-]` is the Relative Mean Absolute Difference [unitless]
of the global irradiance, Eq. (4) of [1]_ and [2]_.
Note that the n-th power coefficient is multiplied by :math:`100^(n-1)` to
convert the percentage to unitless.
echedey-ls marked this conversation as resolved.
Show resolved Hide resolved

The losses definition is Eq. (1) of [1]_, and it's defined as a loss of the
output power:

.. math::

M = 1 - \frac{P_{Array}}{\sum P_{Cells}} \qquad \text{(1)}

To account for a module with a fill factor distinct from the one used to
train the model (``0.79`` by default), the output of the model can be
modified with Eq. (7):

.. math::

M_{FF_1} = M_{FF_0} \frac{FF_1}{FF_0} \qquad \text{(7)}

where parameter ``fill_factor`` is :math:`FF_1` and
``fill_factor_reference`` is :math:`FF_0`.
In the section *See Also*, you will find two packages that can be used to
calculate the irradiance at different points of the module.

.. note::
The global irradiance RMAD is different from the backside irradiance
RMAD.

In case the RMAD of the backside irradiance is known, the global RMAD can
be calculated as follows, assuming the front irradiance RMAD is
negligible [2]_:

.. math::

RMAD(k \cdot X + c) = RMAD(X) \cdot k \frac{k \bar{X}}{k \bar{X} + c}
= RMAD(X) \cdot \frac{k}{1 + \frac{c}{k \bar{X}}}

by similarity with equation (2) of [1]_:

.. math::

G_{total\,i} = G_{front\,i} + \phi_{Bifi} G_{rear\,i} \qquad \text{(2)}

which yields:

.. math::

RMAD_{total} = RMAD_{rear} \frac{\phi_{Bifi}}
{1 + \frac{G_{front}}{\phi_{Bifi} \bar{G}_{rear}}}

See Also
--------
`solarfactors <https://github.com/pvlib/solarfactors/>`_
Calculate the irradiance at different points of the module.
`bifacial_radiance <https://github.com/NREL/bifacial_radiance>`_
Calculate the irradiance at different points of the module.

References
----------
.. [1] C. Deline, S. Ayala Pelaez, S. MacAlpine, and C. Olalla, 'Estimating
and parameterizing mismatch power loss in bifacial photovoltaic
systems', Progress in Photovoltaics: Research and Applications, vol. 28,
no. 7, pp. 691-703, 2020, :doi:`10.1002/pip.3259`.
.. [2] “Mean absolute difference,” Wikipedia, Sep. 05, 2023.
https://en.wikipedia.org/wiki/Mean_absolute_difference#Relative_mean_absolute_difference
(accessed 2024-04-14).
""" # noqa: E501
if isinstance(coefficients, np.polynomial.Polynomial):
model_polynom = coefficients
else: # expect an iterable
model_polynom = np.polynomial.Polynomial(coef=coefficients)

if fill_factor: # Eq. (7), [1]
# Scale output of trained model to account for different fill factors
model_polynom = model_polynom * fill_factor / fill_factor_reference

mismatch = model_polynom(rmad)
if isinstance(rmad, pd.Series):
mismatch = pd.Series(mismatch, index=rmad.index)
return mismatch
54 changes: 54 additions & 0 deletions pvlib/tests/bifacial/test_losses_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from pvlib import bifacial

import pandas as pd
import numpy as np
from numpy.testing import assert_allclose


def test_power_mismatch_deline():
"""tests bifacial.power_mismatch_deline"""
premise_rmads = np.array([0.0, 0.05, 0.1, 0.15, 0.2, 0.25])
# test default model is for fixed tilt
expected_ft_mms = np.array([0.0, 0.0151, 0.0462, 0.0933, 0.1564, 0.2355])
result_def_mms = bifacial.power_mismatch_deline(premise_rmads)
assert_allclose(result_def_mms, expected_ft_mms, atol=1e-5)
assert np.all(np.diff(result_def_mms) > 0) # higher RMADs => higher losses

# test custom coefficients, set model to 1+1*RMAD
# as Polynomial class
polynomial = np.polynomial.Polynomial([1, 1, 0])
result_custom_mms = bifacial.power_mismatch_deline(
premise_rmads, coefficients=polynomial
)
assert_allclose(result_custom_mms, 1 + premise_rmads)
# as list
result_custom_mms = bifacial.power_mismatch_deline(
premise_rmads, coefficients=[1, 1, 0]
)
assert_allclose(result_custom_mms, 1 + premise_rmads)

# test datatypes IO with Series
result_mms = bifacial.power_mismatch_deline(pd.Series(premise_rmads))
assert isinstance(result_mms, pd.Series)

# test fill_factor, fill_factor_reference
# default model + default fill_factor_reference
ff_ref_default = 0.79
ff_of_interest = 0.65
result_mms = bifacial.power_mismatch_deline(
premise_rmads, fill_factor=ff_of_interest
)
assert_allclose(
result_mms,
expected_ft_mms * ff_of_interest / ff_ref_default,
atol=1e-5,
)
# default model + custom fill_factor_reference
ff_of_interest = 0.65
ff_ref = 0.75
result_mms = bifacial.power_mismatch_deline(
premise_rmads, fill_factor=ff_of_interest, fill_factor_reference=ff_ref
)
assert_allclose(
result_mms, expected_ft_mms * ff_of_interest / ff_ref, atol=1e-5
)
Loading