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.
Files changed (62) hide show
  1. pvlib/_deprecation.py +73 -0
  2. pvlib/atmosphere.py +236 -1
  3. pvlib/bifacial/__init__.py +4 -4
  4. pvlib/bifacial/loss_models.py +163 -0
  5. pvlib/clearsky.py +53 -51
  6. pvlib/data/pvgis_tmy_meta.json +32 -93
  7. pvlib/data/pvgis_tmy_test.csv +8761 -0
  8. pvlib/data/tmy_45.000_8.000_2005_2023.csv +8789 -0
  9. pvlib/data/tmy_45.000_8.000_2005_2023.epw +8768 -0
  10. pvlib/data/tmy_45.000_8.000_2005_2023.json +1 -0
  11. pvlib/data/tmy_45.000_8.000_2005_2023.txt +8761 -0
  12. pvlib/data/tmy_45.000_8.000_userhorizon.json +1 -1
  13. pvlib/iam.py +4 -4
  14. pvlib/iotools/midc.py +1 -1
  15. pvlib/iotools/pvgis.py +39 -13
  16. pvlib/irradiance.py +237 -173
  17. pvlib/ivtools/sdm.py +75 -52
  18. pvlib/location.py +5 -5
  19. pvlib/modelchain.py +1 -1
  20. pvlib/pvsystem.py +134 -86
  21. pvlib/shading.py +8 -8
  22. pvlib/singlediode.py +1 -1
  23. pvlib/solarposition.py +101 -80
  24. pvlib/spa.py +28 -24
  25. pvlib/spectrum/__init__.py +9 -4
  26. pvlib/spectrum/irradiance.py +273 -0
  27. pvlib/spectrum/mismatch.py +118 -508
  28. pvlib/spectrum/response.py +280 -0
  29. pvlib/spectrum/spectrl2.py +18 -17
  30. pvlib/temperature.py +49 -3
  31. pvlib/tests/bifacial/test_losses_models.py +54 -0
  32. pvlib/tests/iotools/test_pvgis.py +58 -12
  33. pvlib/tests/ivtools/test_sdm.py +23 -1
  34. pvlib/tests/spectrum/__init__.py +0 -0
  35. pvlib/tests/spectrum/conftest.py +40 -0
  36. pvlib/tests/spectrum/test_irradiance.py +138 -0
  37. pvlib/tests/{test_spectrum.py → spectrum/test_mismatch.py} +32 -306
  38. pvlib/tests/spectrum/test_response.py +124 -0
  39. pvlib/tests/spectrum/test_spectrl2.py +72 -0
  40. pvlib/tests/test__deprecation.py +97 -0
  41. pvlib/tests/test_atmosphere.py +218 -0
  42. pvlib/tests/test_clearsky.py +44 -26
  43. pvlib/tests/test_conftest.py +0 -44
  44. pvlib/tests/test_irradiance.py +62 -16
  45. pvlib/tests/test_pvsystem.py +17 -1
  46. pvlib/tests/test_solarposition.py +117 -36
  47. pvlib/tests/test_spa.py +30 -1
  48. pvlib/tools.py +26 -2
  49. pvlib/tracking.py +53 -47
  50. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/METADATA +34 -31
  51. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/RECORD +55 -47
  52. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/WHEEL +1 -1
  53. pvlib/data/aod550_tcwv_20121101_test.nc +0 -0
  54. pvlib/data/pvgis_tmy_test.dat +0 -8761
  55. pvlib/data/tmy_45.000_8.000_2005_2016.csv +0 -8789
  56. pvlib/data/tmy_45.000_8.000_2005_2016.epw +0 -8768
  57. pvlib/data/tmy_45.000_8.000_2005_2016.json +0 -1
  58. pvlib/data/tmy_45.000_8.000_2005_2016.txt +0 -8761
  59. pvlib/data/variables_style_rules.csv +0 -55
  60. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/AUTHORS.md +0 -0
  61. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/LICENSE +0 -0
  62. {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 and to determine pressure from altitude or vice versa.
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
@@ -1,10 +1,10 @@
1
1
  """
2
- The ``bifacial`` module contains functions to model irradiance for 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 # noqa: F401
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