Skip to content

Commit 8efe6c4

Browse files
cwhansekandersolarAdamRJensenadriesse
authored
Add Huld PV model (#1940)
* implement Huld * documents * is not, not not is * Apply suggestions from code review Co-authored-by: Kevin Anderson <[email protected]> Co-authored-by: Adam R. Jensen <[email protected]> * edit docstring * move to pvarray, address review comments * formatting * fix look-up parameters * fix test * add Note to docstring * edit the Note * edit the Note some more * edit the Note a third time * Update pvlib/pvarray.py Co-authored-by: Kevin Anderson <[email protected]> * Update pvlib/pvarray.py * Update pvlib/pvarray.py Co-authored-by: Anton Driesse <[email protected]> * Apply suggestions from code review * Update pvlib/pvarray.py * add note about negative power at low irradiance * Update docs/sphinx/source/whatsnew/v0.10.4.rst Co-authored-by: Adam R. Jensen <[email protected]> * Update pvlib/pvarray.py * tweak the note --------- Co-authored-by: Kevin Anderson <[email protected]> Co-authored-by: Adam R. Jensen <[email protected]> Co-authored-by: Anton Driesse <[email protected]>
1 parent 859a7ce commit 8efe6c4

File tree

5 files changed

+160
-1
lines changed

5 files changed

+160
-1
lines changed

docs/sphinx/source/reference/pv_modeling/system_models.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,11 @@ ADR model
4747

4848
pvarray.pvefficiency_adr
4949
pvarray.fit_pvefficiency_adr
50+
51+
PVGIS model
52+
^^^^^^^^^^^
53+
54+
.. autosummary::
55+
:toctree: ../generated/
56+
57+
pvarray.huld

docs/sphinx/source/whatsnew/v0.10.4.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ v0.10.4 (Anticipated March, 2024)
77

88
Enhancements
99
~~~~~~~~~~~~
10+
* Added the Huld PV model used by PVGIS (:pull:`1940`)
1011

1112

1213
Bug fixes
@@ -29,5 +30,6 @@ Requirements
2930

3031
Contributors
3132
~~~~~~~~~~~~
33+
* Cliff Hansen (:ghuser:`cwhanse`)
3234
* :ghuser:`matsuobasho`
3335
* Adam R. Jensen (:ghuser:`AdamRJensen`)

pvlib/pvarray.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,128 @@ def adr_wrapper(xdata, *params):
223223
return dict(zip(P_NAMES, popt))
224224
else:
225225
return popt
226+
227+
228+
def _infer_k_huld(cell_type, pdc0):
229+
# from PVGIS documentation, "PVGIS data sources & calculation methods",
230+
# Section 5.2.3, accessed 12/22/2023
231+
# The parameters in PVGIS' documentation are for a version of Huld's
232+
# equation that has factored Pdc0 out of the polynomial:
233+
# P = G/1000 * Pdc0 * (1 + k1 log(Geff) + ...) so these parameters are
234+
# multiplied by pdc0
235+
huld_params = {'csi': (-0.017237, -0.040465, -0.004702, 0.000149,
236+
0.000170, 0.000005),
237+
'cis': (-0.005554, -0.038724, -0.003723, -0.000905,
238+
-0.001256, 0.000001),
239+
'cdte': (-0.046689, -0.072844, -0.002262, 0.000276,
240+
0.000159, -0.000006)}
241+
k = tuple([x*pdc0 for x in huld_params[cell_type.lower()]])
242+
return k
243+
244+
245+
def huld(effective_irradiance, temp_mod, pdc0, k=None, cell_type=None):
246+
r"""
247+
Power (DC) using the Huld model.
248+
249+
The Huld model [1]_ is used by PVGIS and is given by
250+
251+
252+
.. math::
253+
254+
P_{dc} &= G' ( P_{dc0} + k_1 \log(G') + k_2 \log^2 (G') + k_3 T' +
255+
k_4 T' \log(G') + k_5 T' \log^2 (G') + k_6 T'^2)
256+
257+
G' &= \frac{G_{poa eff}}{1000}
258+
259+
T' &= T_{mod} - 25^{\circ}C
260+
261+
262+
Parameters
263+
----------
264+
effective_irradiance : numeric
265+
The irradiance that is converted to photocurrent. [:math:`W/m^2`]
266+
temp_mod: numeric
267+
Module back-surface temperature. [C]
268+
pdc0: numeric
269+
Power of the modules at reference conditions 1000 :math:`W/m^2`
270+
and :math:`25^{\circ}C`. [W]
271+
k : tuple, optional
272+
Empirical coefficients used in the power model. Length 6. If ``k`` is
273+
not provided, ``cell_type`` must be specified.
274+
cell_type : str, optional
275+
If provided, must be one of ``'cSi'``, ``'CIS'``, or ``'CdTe'``.
276+
Used to look up default values for ``k`` if ``k`` is not specified.
277+
278+
Returns
279+
-------
280+
pdc: numeric
281+
DC power. [W]
282+
283+
Raises
284+
------
285+
ValueError
286+
If neither ``k`` nor ``cell_type`` are specified.
287+
288+
Notes
289+
-----
290+
The equation for :math:`P_{dc}` is from [1]_. The expression used in PVGIS
291+
documentation differs by factoring :math:`P_{dc0}` out of the
292+
polynomial:
293+
294+
.. math::
295+
296+
P_{dc} = G' P_{dc0} (1 + k'_1 \log(G') + k'_2 \log^2 (G') + k'_3 T' +
297+
k'_4 T' \log(G') + k'_5 T' \log^2 (G') + k'_6 T'^2)
298+
299+
300+
PVGIS documentation shows a table of default parameters :math:`k'` for
301+
different cell types. The parameters :math:`k'` differ from the parameters
302+
:math:`k` for :py:func:`huld` by the factor ``pdc0``, that is,
303+
304+
.. math::
305+
306+
k = P_{dc0} k'
307+
308+
With default values for :math:`k`, at very low irradiance, i.e.,
309+
:math:`G' < 20 W/m^2`, :math:`P_{dc}` may be negative
310+
due to the terms involving :math:`\log(G')`.
311+
312+
:py:func:`huld` is a component of the PV performance model implemented in
313+
PVGIS. Among other components, the full PVGIS model includes:
314+
- the Faiman model for module temperature
315+
:py:func:`pvlib.temperature.faiman`
316+
- the Martin and Ruiz model for the incidence angle modifier (IAM)
317+
:py:func:`pvlib.iam.martin_ruiz`
318+
- a custom model for a spectral adjustment factor
319+
The PVGIS API (see :py:func:`pvlib.iotools.get_pvgis_hourly`) returns
320+
broadband plane-of-array irradiance (``poa_global``) and DC power (``P``).
321+
``poa_global`` is irradiance before applying the IAM and spectral
322+
adjustments. In contrast the ``effective_irradiance`` for :py:func:`huld`
323+
should have the IAM and spectral adjustments. Users comparing output of
324+
:py:func:`huld` to PVGIS' ``P`` values should expect differences unless
325+
``effective_irradiance`` is computed in the same way as done by PVGIS.
326+
327+
References
328+
----------
329+
.. [1] T. Huld, G. Friesen, A. Skoczek, R. Kenny, T. Sample, M. Field,
330+
E. Dunlop. A power-rating model for crystalline silicon PV modules.
331+
Solar Energy Materials and Solar Cells 95, (2011), pp. 3359-3369.
332+
:doi:`10.1016/j.solmat.2011.07.026`.
333+
"""
334+
if k is None:
335+
if cell_type is not None:
336+
k = _infer_k_huld(cell_type, pdc0)
337+
else:
338+
raise ValueError('Either k or cell_type must be specified')
339+
340+
gprime = effective_irradiance / 1000
341+
tprime = temp_mod - 25
342+
# accomodate gprime<=0
343+
with np.errstate(divide='ignore'):
344+
logGprime = np.log(gprime, out=np.zeros_like(gprime),
345+
where=np.array(gprime > 0))
346+
# Eq. 1 in [1]
347+
pdc = gprime * (pdc0 + k[0] * logGprime + k[1] * logGprime**2 +
348+
k[2] * tprime + k[3] * tprime * logGprime +
349+
k[4] * tprime * logGprime**2 + k[5] * tprime**2)
350+
return pdc

pvlib/tests/test_pvarray.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import numpy as np
2+
import pandas as pd
23
from numpy.testing import assert_allclose
4+
from .conftest import assert_series_equal
5+
import pytest
36

47
from pvlib import pvarray
58

@@ -44,3 +47,25 @@ def test_pvefficiency_adr_round_trip():
4447
params = pvarray.fit_pvefficiency_adr(g, t, eta, dict_output=False)
4548
result = pvarray.pvefficiency_adr(g, t, *params)
4649
assert_allclose(result, eta, atol=1e-6)
50+
51+
52+
def test_huld():
53+
pdc0 = 100
54+
res = pvarray.huld(1000, 25, pdc0, cell_type='cSi')
55+
assert np.isclose(res, pdc0)
56+
exp_sum = np.exp(1) * (np.sum(pvarray._infer_k_huld('cSi', pdc0)) + pdc0)
57+
res = pvarray.huld(1000*np.exp(1), 26, pdc0, cell_type='cSi')
58+
assert np.isclose(res, exp_sum)
59+
res = pvarray.huld(100, 30, pdc0, k=(1, 1, 1, 1, 1, 1))
60+
exp_100 = 0.1 * (pdc0 + np.log(0.1) + np.log(0.1)**2 + 5 + 5*np.log(0.1)
61+
+ 5*np.log(0.1)**2 + 25)
62+
assert np.isclose(res, exp_100)
63+
# Series input, and irradiance = 0
64+
eff_irr = pd.Series([1000, 100, 0])
65+
tm = pd.Series([25, 30, 30])
66+
expected = pd.Series([pdc0, exp_100, 0])
67+
res = pvarray.huld(eff_irr, tm, pdc0, k=(1, 1, 1, 1, 1, 1))
68+
assert_series_equal(res, expected)
69+
with pytest.raises(ValueError,
70+
match='Either k or cell_type must be specified'):
71+
res = pvarray.huld(1000, 25, 100)

pvlib/tests/test_pvsystem.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import unittest.mock as mock
1313

1414
from pvlib import inverter, pvsystem
15-
from pvlib import atmosphere
1615
from pvlib import iam as _iam
1716
from pvlib import irradiance
1817
from pvlib import spectrum

0 commit comments

Comments
 (0)