-
Notifications
You must be signed in to change notification settings - Fork 997
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NREL non-uniform irradiance mismatch loss for bifacial modules (#2046)
* 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
1 parent
c7b8b00
commit 8d172c4
Showing
6 changed files
with
353 additions
and
5 deletions.
There are no files selected for viewing
129 changes: 129 additions & 0 deletions
129
docs/examples/bifacial/plot_irradiance_nonuniformity_loss.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |