Skip to content

Commit 3230fea

Browse files
cwhansekandersolar
andauthored
Modify initial Voc for bishop88 functions (#2032)
* modify bishop88_mpp * lint * extend coverage to v_from_i, i_from_v * lint * whatsnew --------- Co-authored-by: Kevin Anderson <[email protected]>
1 parent 0387b25 commit 3230fea

File tree

3 files changed

+69
-5
lines changed

3 files changed

+69
-5
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Enhancements
1515

1616
Bug fixes
1717
~~~~~~~~~
18+
* Improved reliability of :py:func:`pvlib.singlediode.bishop88_mpp`,
19+
:py:func:`pvlib.singlediode.bishop88_i_from_v` and
20+
:py:func:`pvlib.singlediode.bishop88_v_from_i` by improving the initial
21+
guess for the newton and brentq algorithms. (:issue:`2013`, :pull:`2032`)
1822
* Corrected equation for Ixx0 in :py:func:`pvlib.pvsystem.sapm` (:issue:`2016`, :pull:`2019`)
1923
* Fixed :py:func:`pvlib.pvsystem.retrieve_sam` silently ignoring the `path` parameter
2024
when `name` was provided. Now an exception is raised requesting to only provide one

pvlib/singlediode.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ def fv(x, v, *a):
310310
if method == 'brentq':
311311
# first bound the search using voc
312312
voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)
313+
# start iteration slightly less than NsVbi when voc_est > NsVbi, to
314+
# avoid the asymptote at NsVbi
315+
xp = np.where(voc_est < NsVbi, voc_est, 0.9999*NsVbi)
313316

314317
# brentq only works with scalar inputs, so we need a set up function
315318
# and np.vectorize to repeatedly call the optimizer with the right
@@ -323,7 +326,7 @@ def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
323326
**method_kwargs)
324327

325328
vd_from_brent_vectorized = np.vectorize(vd_from_brent)
326-
vd = vd_from_brent_vectorized(voc_est, voltage, *args)
329+
vd = vd_from_brent_vectorized(xp, voltage, *args)
327330
elif method == 'newton':
328331
x0, (voltage, *args), method_kwargs = \
329332
_prepare_newton_inputs(voltage, (voltage, *args), method_kwargs)
@@ -443,6 +446,9 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
443446

444447
# first bound the search using voc
445448
voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)
449+
# start iteration slightly less than NsVbi when voc_est > NsVbi, to avoid
450+
# the asymptote at NsVbi
451+
xp = np.where(voc_est < NsVbi, voc_est, 0.9999*NsVbi)
446452

447453
def fi(x, i, *a):
448454
# calculate current residual given diode voltage "x"
@@ -461,10 +467,10 @@ def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
461467
**method_kwargs)
462468

463469
vd_from_brent_vectorized = np.vectorize(vd_from_brent)
464-
vd = vd_from_brent_vectorized(voc_est, current, *args)
470+
vd = vd_from_brent_vectorized(xp, current, *args)
465471
elif method == 'newton':
466472
x0, (current, *args), method_kwargs = \
467-
_prepare_newton_inputs(voc_est, (current, *args), method_kwargs)
473+
_prepare_newton_inputs(xp, (current, *args), method_kwargs)
468474
vd = newton(func=lambda x, *a: fi(x, current, *a), x0=x0,
469475
fprime=lambda x, *a: bishop88(x, *a, gradients=True)[3],
470476
args=args, **method_kwargs)
@@ -579,6 +585,9 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series,
579585

580586
# first bound the search using voc
581587
voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)
588+
# start iteration slightly less than NsVbi when voc_est > NsVbi, to avoid
589+
# the asymptote at NsVbi
590+
xp = np.where(voc_est < NsVbi, voc_est, 0.9999*NsVbi)
582591

583592
def fmpp(x, *a):
584593
return bishop88(x, *a, gradients=True)[6]
@@ -592,12 +601,13 @@ def fmpp(x, *a):
592601
vbr_a, vbr, vbr_exp),
593602
**method_kwargs)
594603
)
595-
vd = vec_fun(voc_est, *args)
604+
vd = vec_fun(xp, *args)
596605
elif method == 'newton':
597606
# make sure all args are numpy arrays if max size > 1
598607
# if voc_est is an array, then make a copy to use for initial guess, v0
608+
599609
x0, args, method_kwargs = \
600-
_prepare_newton_inputs(voc_est, args, method_kwargs)
610+
_prepare_newton_inputs(xp, args, method_kwargs)
601611
vd = newton(func=fmpp, x0=x0,
602612
fprime=lambda x, *a: bishop88(x, *a, gradients=True)[7],
603613
args=args, **method_kwargs)

pvlib/tests/test_singlediode.py

+50
Original file line numberDiff line numberDiff line change
@@ -575,3 +575,53 @@ def test_bishop88_pdSeries_len_one(method, bishop88_arguments):
575575
bishop88_i_from_v(pd.Series([0]), **bishop88_arguments, method=method)
576576
bishop88_v_from_i(pd.Series([0]), **bishop88_arguments, method=method)
577577
bishop88_mpp(**bishop88_arguments, method=method)
578+
579+
580+
def _sde_check_solution(i, v, il, io, rs, rsh, a, d2mutau=0., NsVbi=np.inf):
581+
vd = v + rs * i
582+
return il - io*np.expm1(vd/a) - vd/rsh - il*d2mutau/(NsVbi - vd) - i
583+
584+
585+
@pytest.mark.parametrize('method', ['newton', 'brentq'])
586+
def test_bishop88_init_cond(method):
587+
# GH 2013
588+
p = {'alpha_sc': 0.0012256,
589+
'gamma_ref': 1.2916241612804187,
590+
'mu_gamma': 0.00047308959960937403,
591+
'I_L_ref': 3.068717040806731,
592+
'I_o_ref': 2.2691248021217617e-11,
593+
'R_sh_ref': 7000,
594+
'R_sh_0': 7000,
595+
'R_s': 4.602,
596+
'cells_in_series': 268,
597+
'R_sh_exp': 5.5,
598+
'EgRef': 1.5}
599+
NsVbi = 268 * 0.9
600+
d2mutau = 1.4
601+
irrad = np.arange(20, 1100, 20)
602+
tc = np.arange(-25, 74, 1)
603+
weather = np.array(np.meshgrid(irrad, tc)).T.reshape(-1, 2)
604+
# with the above parameters and weather conditions, a few combinations
605+
# result in voc_est > NsVbi, which causes failure of brentq and newton
606+
# when the recombination parameters NsVbi and d2mutau are used.
607+
sde_params = pvsystem.calcparams_pvsyst(weather[:, 0], weather[:, 1], **p)
608+
# test _mpp
609+
result = bishop88_mpp(*sde_params, d2mutau=d2mutau, NsVbi=NsVbi)
610+
imp, vmp, pmp = result
611+
err = np.abs(_sde_check_solution(
612+
imp, vmp, sde_params[0], sde_params[1], sde_params[2], sde_params[3],
613+
sde_params[4], d2mutau=d2mutau, NsVbi=NsVbi))
614+
bad_results = np.isnan(pmp) | (pmp < 0) | (err > 0.00001) # 0.01mA error
615+
assert not bad_results.any()
616+
# test v_from_i
617+
vmp2 = bishop88_v_from_i(imp, *sde_params, d2mutau=d2mutau, NsVbi=NsVbi)
618+
err = np.abs(_sde_check_solution(imp, vmp2, *sde_params, d2mutau=d2mutau,
619+
NsVbi=NsVbi))
620+
bad_results = np.isnan(vmp2) | (vmp2 < 0) | (err > 0.00001)
621+
assert not bad_results.any()
622+
# test v_from_i
623+
imp2 = bishop88_i_from_v(vmp, *sde_params, d2mutau=d2mutau, NsVbi=NsVbi)
624+
err = np.abs(_sde_check_solution(imp2, vmp, *sde_params, d2mutau=d2mutau,
625+
NsVbi=NsVbi))
626+
bad_results = np.isnan(imp2) | (imp2 < 0) | (err > 0.00001)
627+
assert not bad_results.any()

0 commit comments

Comments
 (0)