Skip to content

Commit 90af1f2

Browse files
kandersolarcwhanse
andauthored
Add Batzelis 2017 simple nonlinear PV model (#2563)
* add function * docs * tests * add SDM parameter estimation and key point functions * lint * tests for the two additional functions * whatsnew * lint * Apply suggestions from code review Co-authored-by: Cliff Hansen <[email protected]> * lint * better handling of Rsh=np.inf * rename parameters * switch to non-normalized temp coeffs * fix whatsnew * changes from review --------- Co-authored-by: Cliff Hansen <[email protected]>
1 parent 5953f82 commit 90af1f2

File tree

10 files changed

+421
-4
lines changed

10 files changed

+421
-4
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Functions relevant for single diode models.
1717
pvsystem.v_from_i
1818
pvsystem.max_power_point
1919
ivtools.sdm.pvsyst_temperature_coeff
20+
singlediode.batzelis
2021

2122
Low-level functions for solving the single diode equation.
2223

@@ -37,3 +38,4 @@ Functions for fitting diode models
3738
ivtools.sde.fit_sandia_simple
3839
ivtools.sdm.fit_cec_sam
3940
ivtools.sdm.fit_desoto
41+
ivtools.sdm.fit_desoto_batzelis

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,11 @@ PVGIS model
5555
:toctree: ../generated/
5656

5757
pvarray.huld
58+
59+
Other
60+
^^^^^
61+
62+
.. autosummary::
63+
:toctree: ../generated/
64+
65+
pvarray.batzelis

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ Bug fixes
2121

2222
Enhancements
2323
~~~~~~~~~~~~
24+
* Add :py:func:`~pvlib.ivtools.sdm.fit_desoto_batzelis`, a function to estimate
25+
parameters for the De Soto single-diode model from datasheet values. (:pull:`2563`)
26+
* Add :py:func:`~pvlib.singlediode.batzelis`, a function to estimate
27+
maximum power, open circuit, and short circuit points using parameters for
28+
the single-diode equation. (:pull:`2563`)
29+
* Add :py:func:`~pvlib.pvarray.batzelis`, a function to estimate maximum power
30+
open circuit, and short circuit points from datasheet values. (:pull:`2563`)
2431
* Add ``method='chandrupatla'`` (faster than ``brentq`` and slower than ``newton``,
2532
but convergence is guaranteed) as an option for
2633
:py:func:`pvlib.pvsystem.singlediode`,

pvlib/ivtools/sdm/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
from pvlib.ivtools.sdm.desoto import ( # noqa: F401
1212
fit_desoto,
13-
fit_desoto_sandia
13+
fit_desoto_batzelis,
14+
fit_desoto_sandia,
1415
)
1516

1617
from pvlib.ivtools.sdm.pvsyst import ( # noqa: F401

pvlib/ivtools/sdm/desoto.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from scipy import constants
44
from scipy import optimize
5+
from scipy.special import lambertw
56

67
from pvlib.ivtools.utils import rectify_iv_curve
78
from pvlib.ivtools.sde import _fit_sandia_cocontent
@@ -399,3 +400,74 @@ def _fit_desoto_sandia_diode(ee, voc, vth, tc, specs, const):
399400
new_x = sm.add_constant(x)
400401
res = sm.RLM(y, new_x).fit()
401402
return np.array(res.params)[1]
403+
404+
405+
def fit_desoto_batzelis(v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc):
406+
"""
407+
Determine De Soto single-diode model parameters from datasheet values
408+
using Batzelis's method.
409+
410+
This method is described in Section II.C of [1]_ and fully documented
411+
in [2]_.
412+
413+
Parameters
414+
----------
415+
v_mp : float
416+
Maximum power point voltage at STC. [V]
417+
i_mp : float
418+
Maximum power point current at STC. [A]
419+
v_oc : float
420+
Open-circuit voltage at STC. [V]
421+
i_sc : float
422+
Short-circuit current at STC. [A]
423+
alpha_sc : float
424+
Short-circuit current temperature coefficient at STC. [A/K]
425+
beta_voc : float
426+
Open-circuit voltage temperature coefficient at STC. [V/K]
427+
428+
Returns
429+
-------
430+
dict
431+
The returned dict contains the keys:
432+
433+
* ``alpha_sc`` [A/K]
434+
* ``a_ref`` [V]
435+
* ``I_L_ref`` [A]
436+
* ``I_o_ref`` [A]
437+
* ``R_sh_ref`` [Ohm]
438+
* ``R_s`` [Ohm]
439+
440+
References
441+
----------
442+
.. [1] E. I. Batzelis, "Simple PV Performance Equations Theoretically Well
443+
Founded on the Single-Diode Model," Journal of Photovoltaics vol. 7,
444+
no. 5, pp. 1400-1409, Sep 2017, :doi:`10.1109/JPHOTOV.2017.2711431`
445+
.. [2] E. I. Batzelis and S. A. Papathanassiou, "A method for the
446+
analytical extraction of the single-diode PV model parameters,"
447+
IEEE Trans. Sustain. Energy, vol. 7, no. 2, pp. 504-512, Apr 2016.
448+
:doi:`10.1109/TSTE.2015.2503435`
449+
"""
450+
# convert temp coeffs from A/K and V/K to 1/K
451+
alpha_sc = alpha_sc / i_sc
452+
beta_voc = beta_voc / v_oc
453+
454+
# Equation numbers refer to [1]
455+
t0 = 298.15 # K
456+
del0 = (1 - beta_voc * t0) / (50.1 - alpha_sc * t0) # Eq 9
457+
w0 = np.real(lambertw(np.exp(1/del0 + 1)))
458+
459+
# Eqs 11-15
460+
a0 = del0 * v_oc
461+
Rs0 = (a0 * (w0 - 1) - v_mp) / i_mp
462+
Rsh0 = a0 * (w0 - 1) / (i_sc * (1 - 1/w0) - i_mp)
463+
Iph0 = (1 + Rs0 / Rsh0) * i_sc
464+
Isat0 = Iph0 * np.exp(-1/del0)
465+
466+
return {
467+
'alpha_sc': alpha_sc * i_sc, # convert 1/K to A/K
468+
'a_ref': a0,
469+
'I_L_ref': Iph0,
470+
'I_o_ref': Isat0,
471+
'R_sh_ref': Rsh0,
472+
'R_s': Rs0,
473+
}

pvlib/pvarray.py

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
"""
1010

1111
import numpy as np
12+
import pandas as pd
1213
from scipy.optimize import curve_fit
13-
from scipy.special import exp10
14+
from scipy.special import exp10, lambertw
1415

1516

1617
def pvefficiency_adr(effective_irradiance, temp_cell,
@@ -394,3 +395,131 @@ def huld(effective_irradiance, temp_mod, pdc0, k=None, cell_type=None,
394395
k[5] * tprime**2
395396
)
396397
return pdc
398+
399+
400+
def batzelis(effective_irradiance, temp_cell,
401+
v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc):
402+
"""
403+
Compute maximum power point, open circuit, and short circuit
404+
values using Batzelis's method.
405+
406+
Batzelis's method (described in Section III of [1]_) is a fast method
407+
of computing the maximum power current and voltage. The calculations
408+
are rooted in the De Soto single-diode model, but require only typical
409+
datasheet information.
410+
411+
Parameters
412+
----------
413+
effective_irradiance : numeric, non-negative
414+
Effective irradiance incident on the PV module. [Wm⁻²]
415+
temp_cell : numeric
416+
PV module operating temperature. [°C]
417+
v_mp : float
418+
Maximum power point voltage at STC. [V]
419+
i_mp : float
420+
Maximum power point current at STC. [A]
421+
v_oc : float
422+
Open-circuit voltage at STC. [V]
423+
i_sc : float
424+
Short-circuit current at STC. [A]
425+
alpha_sc : float
426+
Short-circuit current temperature coefficient at STC. [A/K]
427+
beta_voc : float
428+
Open-circuit voltage temperature coefficient at STC. [V/K]
429+
430+
Returns
431+
-------
432+
dict
433+
The returned dict-like object contains the keys/columns:
434+
435+
* ``p_mp`` - power at maximum power point. [W]
436+
* ``i_mp`` - current at maximum power point. [A]
437+
* ``v_mp`` - voltage at maximum power point. [V]
438+
* ``i_sc`` - short circuit current. [A]
439+
* ``v_oc`` - open circuit voltage. [V]
440+
441+
Notes
442+
-----
443+
This method is the combination of three sub-methods for:
444+
445+
1. estimating single-diode model parameters from datasheet information
446+
2. translating SDM parameters from STC to operating conditions
447+
(taken from the De Soto model)
448+
3. estimating the MPP, OC, and SC points on the resulting I-V curve.
449+
450+
At extremely low irradiance (e.g. 1e-10 Wm⁻²), this model can produce
451+
negative voltages. This function clips any negative voltages to zero.
452+
453+
References
454+
----------
455+
.. [1] E. I. Batzelis, "Simple PV Performance Equations Theoretically Well
456+
Founded on the Single-Diode Model," Journal of Photovoltaics vol. 7,
457+
no. 5, pp. 1400-1409, Sep 2017, :doi:`10.1109/JPHOTOV.2017.2711431`
458+
459+
Examples
460+
--------
461+
>>> params = {'i_sc': 15.98, 'v_oc': 50.26, 'i_mp': 15.27, 'v_mp': 42.57,
462+
... 'alpha_sc': 0.007351, 'beta_voc': -0.120624}
463+
>>> batzelis(np.array([1000, 800]), np.array([25, 30]), **params)
464+
{'p_mp': array([650.0439 , 512.99199048]),
465+
'i_mp': array([15.27 , 12.23049303]),
466+
'v_mp': array([42.57 , 41.94368856]),
467+
'i_sc': array([15.98 , 12.813404]),
468+
'v_oc': array([50.26 , 49.26532902])}
469+
"""
470+
# convert temp coeffs from A/K and V/K to 1/K
471+
alpha_sc = alpha_sc / i_sc
472+
beta_voc = beta_voc / v_oc
473+
474+
t0 = 298.15
475+
delT = temp_cell - (t0 - 273.15)
476+
lamT = (temp_cell + 273.15) / t0
477+
g = effective_irradiance / 1000
478+
# for zero/negative irradiance, use lnG=large negative number so that
479+
# computed voltages are negative and then clipped to zero
480+
with np.errstate(divide='ignore'): # needed for pandas for some reason
481+
lnG = np.log(g, out=np.full_like(g, -9e9), where=(g > 0))
482+
lnG = np.where(np.isfinite(g), lnG, np.nan) # also preserve nans
483+
484+
# Eq 9-10
485+
del0 = (1 - beta_voc * t0) / (50.1 - alpha_sc * t0)
486+
w0 = np.real(lambertw(np.exp(1/del0 + 1)))
487+
488+
# Eqs 27-28
489+
alpha_imp = alpha_sc + (beta_voc - 1/t0) / (w0 - 1)
490+
beta_vmp = (v_oc / v_mp) * (
491+
beta_voc / (1 + del0) +
492+
(del0 * (w0 - 1) - 1/(1 + del0)) / t0
493+
)
494+
495+
# Eq 26
496+
eps0 = (del0 / (1 + del0)) * (v_oc / v_mp)
497+
eps1 = del0 * (w0 - 1) * (v_oc / v_mp) - 1
498+
499+
# Eqs 22-25
500+
isc = g * i_sc * (1 + alpha_sc * delT)
501+
voc = v_oc * (1 + del0 * lamT * lnG + beta_voc * delT)
502+
imp = g * i_mp * (1 + alpha_imp * delT)
503+
vmp = v_mp * (1 + eps0 * lamT * lnG + eps1 * (1 - g) + beta_vmp * delT)
504+
505+
# handle negative voltages from zero and extremely small irradiance
506+
vmp = np.clip(vmp, a_min=0, a_max=None)
507+
voc = np.clip(voc, a_min=0, a_max=None)
508+
509+
out = {
510+
'p_mp': vmp * imp,
511+
'i_mp': imp,
512+
'v_mp': vmp,
513+
'i_sc': isc,
514+
'v_oc': voc,
515+
}
516+
517+
# if pandas in, ensure pandas out
518+
pandas_inputs = [
519+
x for x in [effective_irradiance, temp_cell]
520+
if isinstance(x, pd.Series)
521+
]
522+
if pandas_inputs:
523+
out = pd.DataFrame(out, index=pandas_inputs[0].index)
524+
525+
return out

pvlib/singlediode.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import numpy as np
6+
import pandas as pd
67
from pvlib.tools import _golden_sect_DataFrame
78

89
from scipy.optimize import brentq, newton
@@ -975,3 +976,85 @@ def _pwr_optfcn(df, loc):
975976
df['resistance_shunt'], df['nNsVth'])
976977

977978
return current * df[loc]
979+
980+
981+
def batzelis(photocurrent, saturation_current, resistance_series,
982+
resistance_shunt, nNsVth):
983+
"""
984+
Estimate maximum power, open-circuit, and short-circuit points from
985+
single-diode equation parameters using Batzelis's method.
986+
987+
This method is described in Section II.B of [1]_.
988+
989+
Parameters
990+
----------
991+
photocurrent : numeric
992+
Light-generated current. [A]
993+
saturation_current : numeric
994+
Diode saturation current. [A]
995+
resistance_series : numeric
996+
Series resistance. [Ohm]
997+
resistance_shunt : numeric
998+
Shunt resistance. [Ohm]
999+
nNsVth : numeric
1000+
The product of the usual diode ideality factor (n, unitless),
1001+
number of cells in series (Ns), and cell thermal voltage at
1002+
specified effective irradiance and cell temperature. [V]
1003+
1004+
Returns
1005+
-------
1006+
dict
1007+
The returned dict-like object contains the keys/columns:
1008+
1009+
* ``p_mp`` - power at maximum power point. [W]
1010+
* ``i_mp`` - current at maximum power point. [A]
1011+
* ``v_mp`` - voltage at maximum power point. [V]
1012+
* ``i_sc`` - short circuit current. [A]
1013+
* ``v_oc`` - open circuit voltage. [V]
1014+
1015+
References
1016+
----------
1017+
.. [1] E. I. Batzelis, "Simple PV Performance Equations Theoretically Well
1018+
Founded on the Single-Diode Model," Journal of Photovoltaics vol. 7,
1019+
no. 5, pp. 1400-1409, Sep 2017, :doi:`10.1109/JPHOTOV.2017.2711431`
1020+
"""
1021+
# convenience variables
1022+
Iph = photocurrent
1023+
Is = saturation_current
1024+
Rsh = resistance_shunt
1025+
Rs = resistance_series
1026+
a = nNsVth
1027+
1028+
# Eqs 3-4
1029+
isc = Iph / (Rs / Rsh + 1) # manipulated to handle Rsh=np.inf correctly
1030+
with np.errstate(divide='ignore'): # zero Iph
1031+
voc = a * np.log(Iph / Is)
1032+
1033+
# Eqs 5-8
1034+
w = np.real(lambertw(np.e * Iph / Is))
1035+
# vmp = (1 + Rs/Rsh) * a * (w - 1) - Rs * Iph * (1 - 1/w) # not needed
1036+
with np.errstate(divide='ignore', invalid='ignore'): # zero Iph -> zero w
1037+
imp = Iph * (1 - 1/w) - a * (w - 1) / Rsh
1038+
1039+
vmp = a * (w - 1) - Rs * imp
1040+
1041+
vmp = np.where(Iph > 0, vmp, 0)
1042+
voc = np.where(Iph > 0, voc, 0)
1043+
imp = np.where(Iph > 0, imp, 0)
1044+
isc = np.where(Iph > 0, isc, 0)
1045+
1046+
out = {
1047+
'p_mp': imp * vmp,
1048+
'i_mp': imp,
1049+
'v_mp': vmp,
1050+
'i_sc': isc,
1051+
'v_oc': voc,
1052+
}
1053+
1054+
# if pandas in, ensure pandas out
1055+
pandas_inputs = [
1056+
x for x in [Iph, Is, Rsh, Rs, a] if isinstance(x, pd.Series)]
1057+
if pandas_inputs:
1058+
out = pd.DataFrame(out, index=pandas_inputs[0].index)
1059+
1060+
return out

tests/ivtools/sdm/test_desoto.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,27 @@ def test_fit_desoto_sandia(cec_params_cansol_cs5p_220p):
9292
assert_allclose(result['dEgdT'], -0.0002677)
9393
assert_allclose(result['EgRef'], 1.3112547292120638)
9494
assert_allclose(result['cells_in_series'], specs['cells_in_series'])
95+
96+
97+
def test_fit_desoto_batzelis():
98+
params = {'i_sc': 15.98, 'v_oc': 50.26, 'i_mp': 15.27, 'v_mp': 42.57,
99+
'alpha_sc': 0.007351, 'beta_voc': -0.120624}
100+
expected = { # calculated with the function itself
101+
'alpha_sc': 0.007351,
102+
'a_ref': 1.7257632483733483,
103+
'I_L_ref': 15.985408866796396,
104+
'I_o_ref': 3.594308384705643e-12,
105+
'R_sh_ref': 389.4379947026243,
106+
'R_s': 0.13181590981241956,
107+
}
108+
out = sdm.fit_desoto_batzelis(**params)
109+
for k in expected:
110+
assert out[k] == pytest.approx(expected[k])
111+
112+
# ensure the STC values are reproduced
113+
iv = pvsystem.singlediode(out['I_L_ref'], out['I_o_ref'], out['R_s'],
114+
out['R_sh_ref'], out['a_ref'])
115+
assert iv['i_sc'] == pytest.approx(params['i_sc'])
116+
assert iv['i_mp'] == pytest.approx(params['i_mp'], rel=3e-3)
117+
assert iv['v_oc'] == pytest.approx(params['v_oc'], rel=3e-4)
118+
assert iv['v_mp'] == pytest.approx(params['v_mp'], rel=4e-3)

0 commit comments

Comments
 (0)