Skip to content

Commit

Permalink
NREL non-uniform irradiance mismatch loss for bifacial modules (#2046)
Browse files Browse the repository at this point in the history
* Preliminar implementation, tests, docs and gallery example

I expect to extend the gallery example for a day's RMAD profiling.

* Update versionadded

* Add example

* docstring update

* Update v0.11.0.rst

* who would have guessed it's always the linter?

* Update plot_irradiance_nonuniformity_loss.py

* equation fixes

* RMAD properties application typo

* does this fix the link?

* yet another link

* These links will never work, I suppose

* Move some "Notes" equations to example and rearrange things there

* give this equation it's space

* eq rendering fixes?

* maths are not mathing today

* ???

* ??? x2

* ??? x3

* i feel stupid for this

* Change preprint ref to published one

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Published paper coeffs

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Lots of improvements

Pending second section of example, and checking docs

Co-Authored-By: César Domínguez <48208196+cesardd@users.noreply.github.com>
Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Linter - also missing Eq (7)

Co-Authored-By: César Domínguez <48208196+cesardd@users.noreply.github.com>
Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Test polynomial input

Co-Authored-By: César Domínguez <48208196+cesardd@users.noreply.github.com>

* Math

* Test exception

* Update pvsystem.py

* Update plot_irradiance_nonuniformity_loss.py

* Add fill factor ratio

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* ª

* Trying new things

* Revert "Trying new things"

This reverts commit 8a19a4d.

* Update pvsystem.py

* :(

* :((

* I hope we dont miss that reference

* Update pvsystem.py

* Remove second section of the example

* Minor text upgrade

* Update pvsystem.py

* Port to namespace `pvlib.bifacial`

Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com>

* Rename to ``power_mismatch_deline``, document in ``bifacial.rst``

Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com>

* fillfactor only for predefined models

Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com>

* Docstring rewording

Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com>

* Linter

* More docs rewording

Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com>

* Apply suggestions from Dax

Co-authored-by: RDaxini <143435106+RDaxini@users.noreply.github.com>

* Unneded image Dominguez_et_al_PVSC2023.png

* Rename file

* Fix equations

* yup it didnt save

* lintaaaaaaarrrr

* Eq 7 numbering

* Revert unintended changes

* Adam code review

Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com>

* Links?

Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com>

* Referencing equations manually

Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com>

* Polynomial links

Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com>

* Dont abuse math mode

* Update loss_models.py

* I forgot

* Adam Code Review

Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com>

* whatsnews

* Update v0.11.0.rst

* Weird that some tests pass and others dont

* Update loss_models.py

* document

* Update loss_models.py

* Kevin's review (I)

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Kevin's review (II)

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Kevin's review (III)

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Update v0.11.1.rst

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* minor changes

* Forgot to update tests

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Accordingly modify model, IO, docs to unitless model

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Update loss_models.py

* Update loss_models.py

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Apply suggestions from code review

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

* Code review from Cliff

Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com>

* Rendering issues, code review from Kevin

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Little missing newline

---------

Co-authored-by: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>
Co-authored-by: César Domínguez <48208196+cesardd@users.noreply.github.com>
Co-authored-by: Cliff Hansen <5393711+cwhanse@users.noreply.github.com>
Co-authored-by: RDaxini <143435106+RDaxini@users.noreply.github.com>
Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com>
Co-authored-by: Cliff Hansen <cwhanse@sandia.gov>
  • Loading branch information
7 people authored Jul 1, 2024
1 parent c7b8b00 commit 8d172c4
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 5 deletions.
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
155 changes: 155 additions & 0 deletions pvlib/bifacial/loss_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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[-]
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.
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
)

0 comments on commit 8d172c4

Please sign in to comment.