pvlib 0.11.0a1__py3-none-any.whl → 0.11.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pvlib/_deprecation.py +73 -0
- pvlib/atmosphere.py +236 -1
- pvlib/bifacial/__init__.py +4 -4
- pvlib/bifacial/loss_models.py +163 -0
- pvlib/clearsky.py +53 -51
- pvlib/data/pvgis_tmy_meta.json +32 -93
- pvlib/data/pvgis_tmy_test.csv +8761 -0
- pvlib/data/tmy_45.000_8.000_2005_2023.csv +8789 -0
- pvlib/data/tmy_45.000_8.000_2005_2023.epw +8768 -0
- pvlib/data/tmy_45.000_8.000_2005_2023.json +1 -0
- pvlib/data/tmy_45.000_8.000_2005_2023.txt +8761 -0
- pvlib/data/tmy_45.000_8.000_userhorizon.json +1 -1
- pvlib/iam.py +4 -4
- pvlib/iotools/midc.py +1 -1
- pvlib/iotools/pvgis.py +39 -13
- pvlib/irradiance.py +237 -173
- pvlib/ivtools/sdm.py +75 -52
- pvlib/location.py +5 -5
- pvlib/modelchain.py +1 -1
- pvlib/pvsystem.py +134 -86
- pvlib/shading.py +8 -8
- pvlib/singlediode.py +1 -1
- pvlib/solarposition.py +101 -80
- pvlib/spa.py +28 -24
- pvlib/spectrum/__init__.py +9 -4
- pvlib/spectrum/irradiance.py +273 -0
- pvlib/spectrum/mismatch.py +118 -508
- pvlib/spectrum/response.py +280 -0
- pvlib/spectrum/spectrl2.py +18 -17
- pvlib/temperature.py +49 -3
- pvlib/tests/bifacial/test_losses_models.py +54 -0
- pvlib/tests/iotools/test_pvgis.py +58 -12
- pvlib/tests/ivtools/test_sdm.py +23 -1
- pvlib/tests/spectrum/__init__.py +0 -0
- pvlib/tests/spectrum/conftest.py +40 -0
- pvlib/tests/spectrum/test_irradiance.py +138 -0
- pvlib/tests/{test_spectrum.py → spectrum/test_mismatch.py} +32 -306
- pvlib/tests/spectrum/test_response.py +124 -0
- pvlib/tests/spectrum/test_spectrl2.py +72 -0
- pvlib/tests/test__deprecation.py +97 -0
- pvlib/tests/test_atmosphere.py +218 -0
- pvlib/tests/test_clearsky.py +44 -26
- pvlib/tests/test_conftest.py +0 -44
- pvlib/tests/test_irradiance.py +62 -16
- pvlib/tests/test_pvsystem.py +17 -1
- pvlib/tests/test_solarposition.py +117 -36
- pvlib/tests/test_spa.py +30 -1
- pvlib/tools.py +26 -2
- pvlib/tracking.py +53 -47
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/METADATA +34 -31
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/RECORD +55 -47
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/WHEEL +1 -1
- pvlib/data/aod550_tcwv_20121101_test.nc +0 -0
- pvlib/data/pvgis_tmy_test.dat +0 -8761
- pvlib/data/tmy_45.000_8.000_2005_2016.csv +0 -8789
- pvlib/data/tmy_45.000_8.000_2005_2016.epw +0 -8768
- pvlib/data/tmy_45.000_8.000_2005_2016.json +0 -1
- pvlib/data/tmy_45.000_8.000_2005_2016.txt +0 -8761
- pvlib/data/variables_style_rules.csv +0 -55
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/LICENSE +0 -0
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/top_level.txt +0 -0
pvlib/_deprecation.py
CHANGED
|
@@ -316,3 +316,76 @@ def deprecated(since, message='', name='', alternative='', pending=False,
|
|
|
316
316
|
return finalize(wrapper, new_doc)
|
|
317
317
|
|
|
318
318
|
return deprecate
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def renamed_kwarg_warning(since, old_param_name, new_param_name, removal=""):
|
|
322
|
+
"""
|
|
323
|
+
Decorator to mark a possible keyword argument as deprecated and replaced
|
|
324
|
+
with other name.
|
|
325
|
+
|
|
326
|
+
Raises a warning when the deprecated argument is used, and replaces the
|
|
327
|
+
call with the new argument name. Does not modify the function signature.
|
|
328
|
+
|
|
329
|
+
.. warning::
|
|
330
|
+
Ensure ``removal`` date with a ``fail_on_pvlib_version`` decorator in
|
|
331
|
+
the test suite.
|
|
332
|
+
|
|
333
|
+
.. note::
|
|
334
|
+
Not compatible with positional-only arguments.
|
|
335
|
+
|
|
336
|
+
.. note::
|
|
337
|
+
Documentation for the function may updated to reflect the new parameter
|
|
338
|
+
name; it is suggested to add a |.. versionchanged::| directive.
|
|
339
|
+
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
since : str
|
|
343
|
+
The release at which this API became deprecated.
|
|
344
|
+
old_param_name : str
|
|
345
|
+
The name of the deprecated parameter.
|
|
346
|
+
new_param_name : str
|
|
347
|
+
The name of the new parameter.
|
|
348
|
+
removal : str, optional
|
|
349
|
+
The expected removal version, in order to compose the Warning message.
|
|
350
|
+
|
|
351
|
+
Examples
|
|
352
|
+
--------
|
|
353
|
+
>>> @renamed_kwarg_warning("1.4.0", "old_name", "new_name", "1.6.0")
|
|
354
|
+
>>> def some_function(new_name=None):
|
|
355
|
+
>>> pass
|
|
356
|
+
>>> some_function(old_name=1)
|
|
357
|
+
Parameter 'old_name' has been renamed since 1.4.0. and
|
|
358
|
+
will be removed in 1.6.0. Please use 'new_name' instead.
|
|
359
|
+
|
|
360
|
+
>>> @renamed_kwarg_warning("1.4.0", "old_name", "new_name")
|
|
361
|
+
>>> def some_function(new_name=None):
|
|
362
|
+
>>> pass
|
|
363
|
+
>>> some_function(old_name=1)
|
|
364
|
+
Parameter 'old_name' has been renamed since 1.4.0. and
|
|
365
|
+
will be removed soon. Please use 'new_name' instead.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
def deprecate(func, old=old_param_name, new=new_param_name, since=since):
|
|
369
|
+
def wrapper(*args, **kwargs):
|
|
370
|
+
if old in kwargs:
|
|
371
|
+
if new in kwargs:
|
|
372
|
+
raise ValueError(
|
|
373
|
+
f"{func.__name__} received both '{old}' and '{new}', "
|
|
374
|
+
"which are mutually exclusive since they refer to the "
|
|
375
|
+
f"same parameter. Please remove deprecated '{old}'."
|
|
376
|
+
)
|
|
377
|
+
warnings.warn(
|
|
378
|
+
f"Parameter '{old}' has been renamed since {since}. "
|
|
379
|
+
f"and will be removed "
|
|
380
|
+
+ (f"in {removal}" if removal else "soon")
|
|
381
|
+
+ f". Please use '{new}' instead.",
|
|
382
|
+
_projectWarning,
|
|
383
|
+
stacklevel=2,
|
|
384
|
+
)
|
|
385
|
+
kwargs[new] = kwargs.pop(old)
|
|
386
|
+
return func(*args, **kwargs)
|
|
387
|
+
|
|
388
|
+
wrapper = functools.wraps(func)(wrapper)
|
|
389
|
+
return wrapper
|
|
390
|
+
|
|
391
|
+
return deprecate
|
pvlib/atmosphere.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The ``atmosphere`` module contains methods to calculate relative and
|
|
3
|
-
absolute airmass
|
|
3
|
+
absolute airmass, determine pressure from altitude or vice versa, and wind
|
|
4
|
+
speed at different heights.
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
@@ -336,6 +337,85 @@ def gueymard94_pw(temp_air, relative_humidity):
|
|
|
336
337
|
return pw
|
|
337
338
|
|
|
338
339
|
|
|
340
|
+
def rh_from_tdew(temp_air, temp_dew, coeff=(6.112, 17.62, 243.12)):
|
|
341
|
+
"""
|
|
342
|
+
Calculate relative humidity from dewpoint temperature using the Magnus
|
|
343
|
+
equation.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
temp_air : numeric
|
|
348
|
+
Air temperature (dry-bulb temperature). [°C]
|
|
349
|
+
temp_dew : numeric
|
|
350
|
+
Dew-point temperature. [°C]
|
|
351
|
+
coeff : tuple, default (6.112, 17.62, 243.12)
|
|
352
|
+
Magnus equation coefficients (A, B, C). The default values are those
|
|
353
|
+
recommended by the WMO [1]_.
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
numeric
|
|
358
|
+
Relative humidity (0.0-100.0). [%]
|
|
359
|
+
|
|
360
|
+
References
|
|
361
|
+
----------
|
|
362
|
+
.. [1] "Guide to Instruments and Methods of Observation",
|
|
363
|
+
World Meteorological Organization, WMO-No. 8, 2023.
|
|
364
|
+
https://library.wmo.int/idurl/4/68695
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
# Calculate vapor pressure (e) and saturation vapor pressure (es)
|
|
368
|
+
e = coeff[0] * np.exp((coeff[1] * temp_air) / (coeff[2] + temp_air))
|
|
369
|
+
es = coeff[0] * np.exp((coeff[1] * temp_dew) / (coeff[2] + temp_dew))
|
|
370
|
+
|
|
371
|
+
# Calculate relative humidity as percentage
|
|
372
|
+
relative_humidity = 100 * (es / e)
|
|
373
|
+
|
|
374
|
+
return relative_humidity
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def tdew_from_rh(temp_air, relative_humidity, coeff=(6.112, 17.62, 243.12)):
|
|
378
|
+
"""
|
|
379
|
+
Calculate dewpoint temperature using the Magnus equation.
|
|
380
|
+
This is a reversal of the calculation in :py:func:`rh_from_tdew`.
|
|
381
|
+
|
|
382
|
+
Parameters
|
|
383
|
+
----------
|
|
384
|
+
temp_air : numeric
|
|
385
|
+
Air temperature (dry-bulb temperature). [°C]
|
|
386
|
+
relative_humidity : numeric
|
|
387
|
+
Relative humidity (0-100). [%]
|
|
388
|
+
coeff: tuple, default (6.112, 17.62, 243.12)
|
|
389
|
+
Magnus equation coefficients (A, B, C). The default values are those
|
|
390
|
+
recommended by the WMO [1]_.
|
|
391
|
+
|
|
392
|
+
Returns
|
|
393
|
+
-------
|
|
394
|
+
numeric
|
|
395
|
+
Dewpoint temperature. [°C]
|
|
396
|
+
|
|
397
|
+
References
|
|
398
|
+
----------
|
|
399
|
+
.. [1] "Guide to Instruments and Methods of Observation",
|
|
400
|
+
World Meteorological Organization, WMO-No. 8, 2023.
|
|
401
|
+
https://library.wmo.int/idurl/4/68695
|
|
402
|
+
"""
|
|
403
|
+
# Calculate the term inside the log
|
|
404
|
+
# From RH = 100 * (es/e), we get es = (RH/100) * e
|
|
405
|
+
# Substituting the Magnus equation and solving for dewpoint
|
|
406
|
+
|
|
407
|
+
# First calculate ln(es/A)
|
|
408
|
+
ln_term = (
|
|
409
|
+
(coeff[1] * temp_air) / (coeff[2] + temp_air)
|
|
410
|
+
+ np.log(relative_humidity/100)
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Then solve for dewpoint
|
|
414
|
+
dewpoint = coeff[2] * ln_term / (coeff[1] - ln_term)
|
|
415
|
+
|
|
416
|
+
return dewpoint
|
|
417
|
+
|
|
418
|
+
|
|
339
419
|
first_solar_spectral_correction = deprecated(
|
|
340
420
|
since='0.10.0',
|
|
341
421
|
alternative='pvlib.spectrum.spectral_factor_firstsolar'
|
|
@@ -533,3 +613,158 @@ def angstrom_alpha(aod1, lambda1, aod2, lambda2):
|
|
|
533
613
|
pvlib.atmosphere.angstrom_aod_at_lambda
|
|
534
614
|
"""
|
|
535
615
|
return - np.log(aod1 / aod2) / np.log(lambda1 / lambda2)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
# Values of the Hellmann exponent
|
|
619
|
+
HELLMANN_SURFACE_EXPONENTS = {
|
|
620
|
+
'unstable_air_above_open_water_surface': 0.06,
|
|
621
|
+
'neutral_air_above_open_water_surface': 0.10,
|
|
622
|
+
'stable_air_above_open_water_surface': 0.27,
|
|
623
|
+
'unstable_air_above_flat_open_coast': 0.11,
|
|
624
|
+
'neutral_air_above_flat_open_coast': 0.16,
|
|
625
|
+
'stable_air_above_flat_open_coast': 0.40,
|
|
626
|
+
'unstable_air_above_human_inhabited_areas': 0.27,
|
|
627
|
+
'neutral_air_above_human_inhabited_areas': 0.34,
|
|
628
|
+
'stable_air_above_human_inhabited_areas': 0.60,
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def windspeed_powerlaw(wind_speed_reference, height_reference,
|
|
633
|
+
height_desired, exponent=None,
|
|
634
|
+
surface_type=None):
|
|
635
|
+
r"""
|
|
636
|
+
Estimate wind speed for different heights.
|
|
637
|
+
|
|
638
|
+
The model is based on the power law equation by Hellmann [1]_ [2]_.
|
|
639
|
+
|
|
640
|
+
Parameters
|
|
641
|
+
----------
|
|
642
|
+
wind_speed_reference : numeric
|
|
643
|
+
Measured wind speed. [m/s]
|
|
644
|
+
|
|
645
|
+
height_reference : float
|
|
646
|
+
The height above ground at which the wind speed is measured. [m]
|
|
647
|
+
|
|
648
|
+
height_desired : float
|
|
649
|
+
The height above ground at which the wind speed will be estimated. [m]
|
|
650
|
+
|
|
651
|
+
exponent : float, optional
|
|
652
|
+
Exponent based on the surface type. [unitless]
|
|
653
|
+
|
|
654
|
+
surface_type : string, optional
|
|
655
|
+
If supplied, overrides ``exponent``. Can be one of the following
|
|
656
|
+
(see [1]_):
|
|
657
|
+
|
|
658
|
+
* ``'unstable_air_above_open_water_surface'``
|
|
659
|
+
* ``'neutral_air_above_open_water_surface'``
|
|
660
|
+
* ``'stable_air_above_open_water_surface'``
|
|
661
|
+
* ``'unstable_air_above_flat_open_coast'``
|
|
662
|
+
* ``'neutral_air_above_flat_open_coast'``
|
|
663
|
+
* ``'stable_air_above_flat_open_coast'``
|
|
664
|
+
* ``'unstable_air_above_human_inhabited_areas'``
|
|
665
|
+
* ``'neutral_air_above_human_inhabited_areas'``
|
|
666
|
+
* ``'stable_air_above_human_inhabited_areas'``
|
|
667
|
+
|
|
668
|
+
Returns
|
|
669
|
+
-------
|
|
670
|
+
wind_speed : numeric
|
|
671
|
+
Adjusted wind speed for the desired height. [m/s]
|
|
672
|
+
|
|
673
|
+
Raises
|
|
674
|
+
------
|
|
675
|
+
ValueError
|
|
676
|
+
If neither of ``exponent`` nor a ``surface_type`` is given.
|
|
677
|
+
If both ``exponent`` and a ``surface_type`` is given. These parameters
|
|
678
|
+
are mutually exclusive.
|
|
679
|
+
|
|
680
|
+
KeyError
|
|
681
|
+
If the specified ``surface_type`` is invalid.
|
|
682
|
+
|
|
683
|
+
Notes
|
|
684
|
+
-----
|
|
685
|
+
Module temperature functions often require wind speeds at a height of 10 m
|
|
686
|
+
and not the wind speed at the module height.
|
|
687
|
+
|
|
688
|
+
For example, the following temperature functions require the input wind
|
|
689
|
+
speed to be 10 m: :py:func:`~pvlib.temperature.sapm_cell`, and
|
|
690
|
+
:py:func:`~pvlib.temperature.sapm_module` whereas the
|
|
691
|
+
:py:func:`~pvlib.temperature.fuentes` model requires wind speed at 9.144 m.
|
|
692
|
+
|
|
693
|
+
Additionally, the heat loss coefficients of some models have been developed
|
|
694
|
+
for wind speed measurements at 10 m (e.g.,
|
|
695
|
+
:py:func:`~pvlib.temperature.pvsyst_cell`,
|
|
696
|
+
:py:func:`~pvlib.temperature.faiman`, and
|
|
697
|
+
:py:func:`~pvlib.temperature.faiman_rad`).
|
|
698
|
+
|
|
699
|
+
The equation for calculating the wind speed at a height of :math:`h` is
|
|
700
|
+
given by the following power law equation [1]_ [2]_:
|
|
701
|
+
|
|
702
|
+
.. math::
|
|
703
|
+
:label: wind speed
|
|
704
|
+
|
|
705
|
+
WS_{h} = WS_{ref} \cdot \left( \frac{h}{h_{ref}} \right)^a
|
|
706
|
+
|
|
707
|
+
where :math:`h` [m] is the height at which we would like to calculate the
|
|
708
|
+
wind speed, :math:`h_{ref}` [m] is the reference height at which the wind
|
|
709
|
+
speed is known, and :math:`WS_{h}` [m/s] and :math:`WS_{ref}`
|
|
710
|
+
[m/s] are the corresponding wind speeds at these heights. The exponent
|
|
711
|
+
:math:`a` [unitless] depends on the surface type. Some values found in the
|
|
712
|
+
literature [1]_ for :math:`a` are:
|
|
713
|
+
|
|
714
|
+
.. table:: Values for the Hellmann-exponent
|
|
715
|
+
|
|
716
|
+
+-----------+--------------------+------------------+------------------+
|
|
717
|
+
| Stability | Open water surface | Flat, open coast | Cities, villages |
|
|
718
|
+
+===========+====================+==================+==================+
|
|
719
|
+
| Unstable | 0.06 | 0.10 | 0.27 |
|
|
720
|
+
+-----------+--------------------+------------------+------------------+
|
|
721
|
+
| Neutral | 0.11 | 0.16 | 0.40 |
|
|
722
|
+
+-----------+--------------------+------------------+------------------+
|
|
723
|
+
| Stable | 0.27 | 0.34 | 0.60 |
|
|
724
|
+
+-----------+--------------------+------------------+------------------+
|
|
725
|
+
|
|
726
|
+
In a report by Sandia [3]_, the equation was experimentally tested for a
|
|
727
|
+
height of 30 ft (:math:`h_{ref} = 9.144` [m]) at their test site in
|
|
728
|
+
Albuquerque for a period of six weeks where a coefficient of
|
|
729
|
+
:math:`a = 0.219` was calculated.
|
|
730
|
+
|
|
731
|
+
It should be noted that the equation returns a value of NaN if the
|
|
732
|
+
reference heights or wind speed are negative.
|
|
733
|
+
|
|
734
|
+
References
|
|
735
|
+
----------
|
|
736
|
+
.. [1] Kaltschmitt M., Streicher W., Wiese A. (2007). "Renewable Energy:
|
|
737
|
+
Technology, Economics and Environment." Springer,
|
|
738
|
+
:doi:`10.1007/3-540-70949-5`.
|
|
739
|
+
|
|
740
|
+
.. [2] Hellmann G. (1915). "Über die Bewegung der Luft in den untersten
|
|
741
|
+
Schichten der Atmosphäre." Meteorologische Zeitschrift, 32
|
|
742
|
+
|
|
743
|
+
.. [3] Menicucci D.F., Hall I.J. (1985). "Estimating wind speed as a
|
|
744
|
+
function of height above ground: An analysis of data obtained at the
|
|
745
|
+
southwest residential experiment station, Las Cruses, New Mexico."
|
|
746
|
+
SAND84-2530, Sandia National Laboratories.
|
|
747
|
+
Accessed at:
|
|
748
|
+
https://web.archive.org/web/20230418202422/https://www2.jpl.nasa.gov/adv_tech/photovol/2016CTR/SNL%20-%20Est%20Wind%20Speed%20vs%20Height_1985.pdf
|
|
749
|
+
""" # noqa:E501
|
|
750
|
+
if surface_type is not None and exponent is None:
|
|
751
|
+
# use the Hellmann exponent from dictionary
|
|
752
|
+
exponent = HELLMANN_SURFACE_EXPONENTS[surface_type]
|
|
753
|
+
elif surface_type is None and exponent is not None:
|
|
754
|
+
# use the provided exponent
|
|
755
|
+
pass
|
|
756
|
+
else:
|
|
757
|
+
raise ValueError(
|
|
758
|
+
"Either a 'surface_type' or an 'exponent' parameter must be given")
|
|
759
|
+
|
|
760
|
+
wind_speed = wind_speed_reference * (
|
|
761
|
+
(height_desired / height_reference) ** exponent)
|
|
762
|
+
|
|
763
|
+
# if wind speed is negative or complex return NaN
|
|
764
|
+
wind_speed = np.where(np.iscomplex(wind_speed) | (wind_speed < 0),
|
|
765
|
+
np.nan, wind_speed)
|
|
766
|
+
|
|
767
|
+
if isinstance(wind_speed_reference, pd.Series):
|
|
768
|
+
wind_speed = pd.Series(wind_speed, index=wind_speed_reference.index)
|
|
769
|
+
|
|
770
|
+
return wind_speed
|
pvlib/bifacial/__init__.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
|
-
The ``bifacial``
|
|
3
|
-
modules.
|
|
4
|
-
|
|
2
|
+
The ``bifacial`` submodule contains functions to model bifacial modules.
|
|
5
3
|
"""
|
|
4
|
+
|
|
6
5
|
from pvlib._deprecation import deprecated
|
|
7
|
-
from pvlib.bifacial import pvfactors, infinite_sheds, utils
|
|
6
|
+
from pvlib.bifacial import pvfactors, infinite_sheds, utils # noqa: F401
|
|
7
|
+
from .loss_models import power_mismatch_deline # noqa: F401
|
|
8
8
|
|
|
9
9
|
pvfactors_timeseries = deprecated(
|
|
10
10
|
since='0.9.1',
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def power_mismatch_deline(
|
|
6
|
+
rmad,
|
|
7
|
+
coefficients=(0, 0.142, 0.032 * 100),
|
|
8
|
+
fill_factor: float = None,
|
|
9
|
+
fill_factor_reference: float = 0.79,
|
|
10
|
+
):
|
|
11
|
+
r"""
|
|
12
|
+
Estimate DC power loss due to irradiance non-uniformity.
|
|
13
|
+
|
|
14
|
+
This model is described for bifacial modules in [1]_, where the backside
|
|
15
|
+
irradiance is less uniform due to mounting and site conditions.
|
|
16
|
+
|
|
17
|
+
The power loss is estimated by a polynomial model of the Relative Mean
|
|
18
|
+
Absolute Difference (RMAD) of the cell-by-cell total irradiance.
|
|
19
|
+
|
|
20
|
+
Use ``fill_factor`` to account for different fill factors between the
|
|
21
|
+
data used to fit the model and the module of interest. Specify the model's fill factor with
|
|
22
|
+
``fill_factor_reference``.
|
|
23
|
+
|
|
24
|
+
.. versionadded:: 0.11.1
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
rmad : numeric
|
|
29
|
+
The Relative Mean Absolute Difference of the cell-by-cell total
|
|
30
|
+
irradiance. [Unitless]
|
|
31
|
+
|
|
32
|
+
See the *Notes* section for the equation to calculate ``rmad`` from the
|
|
33
|
+
bifaciality and the front and back irradiances.
|
|
34
|
+
|
|
35
|
+
coefficients : float collection or numpy.polynomial.polynomial.Polynomial, default ``(0, 0.142, 0.032 * 100)``
|
|
36
|
+
The polynomial coefficients to use.
|
|
37
|
+
|
|
38
|
+
If a :external:class:`numpy.polynomial.polynomial.Polynomial`,
|
|
39
|
+
it is evaluated as is. If not a ``Polynomial``, it must be the
|
|
40
|
+
coefficients of a polynomial in ``rmad``, where the first element is
|
|
41
|
+
the constant term and the last element is the highest order term. A
|
|
42
|
+
:external:class:`~numpy.polynomial.polynomial.Polynomial`
|
|
43
|
+
will be created internally.
|
|
44
|
+
|
|
45
|
+
fill_factor : float, optional
|
|
46
|
+
Fill factor at standard test condition (STC) of the module.
|
|
47
|
+
Accounts for different fill factors between the trained model and the
|
|
48
|
+
module under non-uniform irradiance.
|
|
49
|
+
If not provided, the default ``fill_factor_reference`` of 0.79 is used.
|
|
50
|
+
|
|
51
|
+
fill_factor_reference : float, default 0.79
|
|
52
|
+
Fill factor at STC of the module used to train the model.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
loss : numeric
|
|
57
|
+
The fractional power loss. [Unitless]
|
|
58
|
+
|
|
59
|
+
Output will be a ``pandas.Series`` if ``rmad`` is a ``pandas.Series``.
|
|
60
|
+
|
|
61
|
+
Notes
|
|
62
|
+
-----
|
|
63
|
+
The default model implemented is equation (11) [1]_:
|
|
64
|
+
|
|
65
|
+
.. math::
|
|
66
|
+
|
|
67
|
+
M[\%] &= 0.142 \Delta[\%] + 0.032 \Delta^2[\%] \qquad \text{(11)}
|
|
68
|
+
|
|
69
|
+
M[-] &= 0.142 \Delta[-] + 0.032 \times 100 \Delta^2[-]
|
|
70
|
+
|
|
71
|
+
where the upper equation is in percentage (same as paper) and the lower
|
|
72
|
+
one is unitless. The implementation uses the unitless version, where
|
|
73
|
+
:math:`M[-]` is the mismatch power loss [unitless] and
|
|
74
|
+
:math:`\Delta[-]` is the Relative Mean Absolute Difference (RMAD) [unitless]
|
|
75
|
+
of the global irradiance, Eq. (4) of [1]_ and [2]_.
|
|
76
|
+
Note that the n-th power coefficient is multiplied by :math:`100^{n-1}`
|
|
77
|
+
to convert the percentage to unitless.
|
|
78
|
+
|
|
79
|
+
The losses definition is Eq. (1) of [1]_, and it's defined as a loss of the
|
|
80
|
+
output power:
|
|
81
|
+
|
|
82
|
+
.. math::
|
|
83
|
+
|
|
84
|
+
M = 1 - \frac{P_{Array}}{\sum P_{Cells}} \qquad \text{(1)}
|
|
85
|
+
|
|
86
|
+
To account for a module with a fill factor distinct from the one used to
|
|
87
|
+
train the model (``0.79`` by default), the output of the model can be
|
|
88
|
+
modified with Eq. (7):
|
|
89
|
+
|
|
90
|
+
.. math::
|
|
91
|
+
|
|
92
|
+
M_{FF_1} = M_{FF_0} \frac{FF_1}{FF_0} \qquad \text{(7)}
|
|
93
|
+
|
|
94
|
+
where parameter ``fill_factor`` is :math:`FF_1` and
|
|
95
|
+
``fill_factor_reference`` is :math:`FF_0`.
|
|
96
|
+
|
|
97
|
+
In the section *See Also*, you will find two packages that can be used to
|
|
98
|
+
calculate the irradiance at different points of the module.
|
|
99
|
+
|
|
100
|
+
.. note::
|
|
101
|
+
The global irradiance RMAD is different from the backside irradiance
|
|
102
|
+
RMAD.
|
|
103
|
+
|
|
104
|
+
RMAD of a variable :math:`G_{total}` is defined as:
|
|
105
|
+
|
|
106
|
+
.. math::
|
|
107
|
+
|
|
108
|
+
RMAD \left[ unitless \right] = \Delta \left[ unitless \right] =
|
|
109
|
+
\frac{1}{n^2 \bar{G}_{total}} \sum_{i=1}^{n} \sum_{j=1}^{n}
|
|
110
|
+
\lvert G_{total,i} - G_{total,j} \rvert
|
|
111
|
+
|
|
112
|
+
In case the RMAD of the backside irradiance is known, the global RMAD can
|
|
113
|
+
be calculated as follows, assuming the front irradiance RMAD is
|
|
114
|
+
negligible [2]_:
|
|
115
|
+
|
|
116
|
+
.. math::
|
|
117
|
+
|
|
118
|
+
RMAD(k \cdot X + c) = RMAD(X) \cdot k \frac{k \bar{X}}{k \bar{X} + c}
|
|
119
|
+
= RMAD(X) \cdot \frac{k}{1 + \frac{c}{k \bar{X}}}
|
|
120
|
+
|
|
121
|
+
by similarity with equation (2) of [1]_:
|
|
122
|
+
|
|
123
|
+
.. math::
|
|
124
|
+
|
|
125
|
+
G_{total\,i} = G_{front\,i} + \phi_{Bifi} G_{rear\,i} \qquad \text{(2)}
|
|
126
|
+
|
|
127
|
+
which yields:
|
|
128
|
+
|
|
129
|
+
.. math::
|
|
130
|
+
|
|
131
|
+
RMAD_{total} = RMAD_{rear} \frac{\phi_{Bifi}}
|
|
132
|
+
{1 + \frac{G_{front}}{\phi_{Bifi} \bar{G}_{rear}}}
|
|
133
|
+
|
|
134
|
+
See Also
|
|
135
|
+
--------
|
|
136
|
+
`solarfactors <https://github.com/pvlib/solarfactors/>`_
|
|
137
|
+
Calculate the irradiance at different points of the module.
|
|
138
|
+
`bifacial_radiance <https://github.com/NREL/bifacial_radiance>`_
|
|
139
|
+
Calculate the irradiance at different points of the module.
|
|
140
|
+
|
|
141
|
+
References
|
|
142
|
+
----------
|
|
143
|
+
.. [1] C. Deline, S. Ayala Pelaez, S. MacAlpine, and C. Olalla, 'Estimating
|
|
144
|
+
and parameterizing mismatch power loss in bifacial photovoltaic
|
|
145
|
+
systems', Progress in Photovoltaics: Research and Applications, vol. 28,
|
|
146
|
+
no. 7, pp. 691-703, 2020, :doi:`10.1002/pip.3259`.
|
|
147
|
+
.. [2] “Mean absolute difference,” Wikipedia, Sep. 05, 2023.
|
|
148
|
+
https://en.wikipedia.org/wiki/Mean_absolute_difference#Relative_mean_absolute_difference
|
|
149
|
+
(accessed 2024-04-14).
|
|
150
|
+
""" # noqa: E501
|
|
151
|
+
if isinstance(coefficients, np.polynomial.Polynomial):
|
|
152
|
+
model_polynom = coefficients
|
|
153
|
+
else: # expect an iterable
|
|
154
|
+
model_polynom = np.polynomial.Polynomial(coef=coefficients)
|
|
155
|
+
|
|
156
|
+
if fill_factor: # Eq. (7), [1]
|
|
157
|
+
# Scale output of trained model to account for different fill factors
|
|
158
|
+
model_polynom = model_polynom * fill_factor / fill_factor_reference
|
|
159
|
+
|
|
160
|
+
mismatch = model_polynom(rmad)
|
|
161
|
+
if isinstance(rmad, pd.Series):
|
|
162
|
+
mismatch = pd.Series(mismatch, index=rmad.index)
|
|
163
|
+
return mismatch
|