-
Notifications
You must be signed in to change notification settings - Fork 1.1k
ENH: add methods and tests for a explicit IV curve calculation of single-diode model #409
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
22d2e5c
bb7c49a
36d4f78
817a303
b17a8cd
0666d29
595e254
5ade588
7179096
4b66f87
664d7d8
09a9e14
06be522
1aba56a
f09be91
4905363
a3d2bb4
8ee1b94
c9a893a
c5248bb
68c65c3
41e0c83
d7b6e62
aa3d29a
9fc350d
54e8d18
16ad9b4
7aca302
086e73f
9f2b157
555e946
52a8e88
c0e18a5
09c6a75
ff8cc0b
446fa9e
f976f61
db88022
a509dd3
62010c2
5c939b8
66a801d
df1423b
eb20cf4
e3805ef
5f89578
ba84fcf
0f3893c
d6023b1
80b3352
ef20676
98d1c01
4ecd913
bf05d2d
edc445d
f542d15
cc6c976
22c53fc
442a3d4
6bcffe8
e6b60c6
0fc9c83
fc6cee1
28c8ffb
58361c1
5f9ed41
43475cc
c79ab97
1ad6031
f14ba04
8d86560
337d7b4
ce5b0f5
3e009f8
082dfd5
ceb69cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
""" | ||
testing way faster single-diode methods using JW Bishop 1988 | ||
""" | ||
|
||
from time import clock | ||
import logging | ||
import numpy as np | ||
from pvlib import pvsystem | ||
from pvlib.way_faster import faster_way | ||
|
||
logging.basicConfig() | ||
LOGGER = logging.getLogger(__name__) | ||
LOGGER.setLevel(logging.DEBUG) | ||
|
||
POA = 888 | ||
TCELL = 55 | ||
CECMOD = pvsystem.retrieve_sam('cecmod') | ||
|
||
|
||
def test_spr_e20_327(): | ||
spr_e20_327 = CECMOD.SunPower_SPR_E20_327 | ||
x = pvsystem.calcparams_desoto( | ||
poa_global=POA, temp_cell=TCELL, | ||
alpha_isc=spr_e20_327.alpha_sc, module_parameters=spr_e20_327, | ||
EgRef=1.121, dEgdT=-0.0002677) | ||
il, io, rs, rsh, nnsvt = x | ||
tstart = clock() | ||
pvs = pvsystem.singlediode(*x) | ||
tstop = clock() | ||
dt_slow = tstop - tstart | ||
LOGGER.debug('single diode elapsed time = %g[s]', dt_slow) | ||
tstart = clock() | ||
out = faster_way(*x, log=False, test=False) | ||
isc, voc, imp, vmp, pmp, _, _ = out.values() | ||
tstop = clock() | ||
dt_fast = tstop - tstart | ||
LOGGER.debug('way faster elapsed time = %g[s]', dt_fast) | ||
LOGGER.debug('spr_e20_327 speedup = %g', dt_slow / dt_fast) | ||
assert np.isclose(pvs['i_sc'], isc) | ||
assert np.isclose(pvs['v_oc'], voc) | ||
# the singlediode method doesn't actually get the MPP correct | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this change in this PR, because default method is no longer LambertW? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ |
||
pvs_imp = pvsystem.i_from_v(rsh, rs, nnsvt, vmp, io, il) | ||
pvs_vmp = pvsystem.v_from_i(rsh, rs, nnsvt, imp, io, il) | ||
assert np.isclose(pvs_imp, imp) | ||
assert np.isclose(pvs_vmp, vmp) | ||
assert np.isclose(pvs['p_mp'], pmp) | ||
return isc, voc, imp, vmp, pmp, pvs | ||
|
||
|
||
def test_fs_495(): | ||
fs_495 = CECMOD.First_Solar_FS_495 | ||
x = pvsystem.calcparams_desoto( | ||
poa_global=POA, temp_cell=TCELL, | ||
alpha_isc=fs_495.alpha_sc, module_parameters=fs_495, | ||
EgRef=1.475, dEgdT=-0.0003) | ||
il, io, rs, rsh, nnsvt = x | ||
x += (101, ) | ||
tstart = clock() | ||
pvs = pvsystem.singlediode(*x) | ||
tstop = clock() | ||
dt_slow = tstop - tstart | ||
LOGGER.debug('single diode elapsed time = %g[s]', dt_slow) | ||
tstart = clock() | ||
out = faster_way(*x, log=False, test=False) | ||
isc, voc, imp, vmp, pmp, _, _, i, v, p = out.values() | ||
tstop = clock() | ||
dt_fast = tstop - tstart | ||
LOGGER.debug('way faster elapsed time = %g[s]', dt_fast) | ||
LOGGER.debug('fs_495 speedup = %g', dt_slow / dt_fast) | ||
assert np.isclose(pvs['i_sc'], isc) | ||
assert np.isclose(pvs['v_oc'], voc) | ||
# the singlediode method doesn't actually get the MPP correct | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this kind of a bigish deal? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I calculate a ~0.02% (absolute) difference between my I_mp_A and V_mp_V values (pvfit) and the pvlib ones for the SunPower module. pvlib calculation: pvfit calculation: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this change in this PR, because default method is no longer LambertW? |
||
pvs_imp = pvsystem.i_from_v(rsh, rs, nnsvt, vmp, io, il) | ||
pvs_vmp = pvsystem.v_from_i(rsh, rs, nnsvt, imp, io, il) | ||
assert np.isclose(pvs_imp, imp) | ||
assert np.isclose(pvs_vmp, vmp) | ||
assert np.isclose(pvs['p_mp'], pmp) | ||
return isc, voc, imp, vmp, pmp, i, v, p, pvs | ||
|
||
if __name__ == '__main__': | ||
r_spr_e20_327 = test_spr_e20_327() | ||
r_fs_495 = test_fs_495() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
"""faster ways""" | ||
|
||
import logging | ||
from collections import OrderedDict | ||
import numpy as np | ||
|
||
logging.basicConfig() | ||
LOGGER = logging.getLogger(__name__) | ||
LOGGER.setLevel(logging.DEBUG) | ||
|
||
EPS = np.finfo(float).eps | ||
DAMP = 1.5 | ||
DELTA = EPS**0.33 | ||
|
||
|
||
def est_voc(il, io, nnsvt): | ||
# http://www.pveducation.org/pvcdrom/open-circuit-voltage | ||
return nnsvt * np.log(il / io + 1.0) | ||
|
||
|
||
def bishop88(vd, il, io, rs, rsh, nnsvt): | ||
"""bishop 1988""" | ||
a = np.exp(vd / nnsvt) | ||
b = 1.0 / rsh | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why I vote for moving to gsh := 1/rsh for easier handling of ideal devices. |
||
i = il - io * (a - 1.0) - vd * b | ||
v = vd - i * rs | ||
c = io * a / nnsvt | ||
grad_i = - c - b # di/dvd | ||
grad_v = 1.0 - grad_i * rs # dv/dvd | ||
# dp/dv = d(iv)/dv = v * di/dv + i | ||
grad = grad_i / grad_v | ||
grad_p = v * grad + i # dp/dv | ||
grad2i = -c / nnsvt | ||
grad2v = -grad2i * rs | ||
grad2p = (grad_v * grad + v * (grad2i/grad_v - grad_i*grad2v/grad_v**2) | ||
+ grad_i) | ||
return i, v, grad_i, grad_v, i*v, grad_p, grad2p | ||
|
||
|
||
def newton_solver(fjx, x0, x, tol=EPS, damp=DAMP, log=False, test=True): | ||
resnorm = np.inf # nor of residuals | ||
while resnorm > tol: | ||
f, j = fjx(x0, *x) | ||
newton_step = f / j | ||
# don't let step get crazy | ||
if np.abs(newton_step / x0) > damp: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change this to |
||
break | ||
x0 -= newton_step | ||
resnorm = f**2 | ||
if log: | ||
LOGGER.debug( | ||
'x0=%g, newton step=%g, f=%g, resnorm=%g', | ||
x0, newton_step, f, resnorm | ||
) | ||
if test: | ||
f2, _ = fjx(x0 * (1.0 + DELTA), *x) | ||
LOGGER.debug('test_grad=%g', (f2 - f) / x0 / DELTA) | ||
LOGGER.debug('grad=%g', j) | ||
return x0, f, j | ||
|
||
|
||
def faster_way(photocurrent, saturation_current, resistance_series, | ||
resistance_shunt, nNsVth, ivcurve_pnts=None, | ||
tol=EPS, damp=DAMP, log=True, test=True): | ||
"""a faster way""" | ||
# FIXME: everything is named the wrong thing! | ||
il = photocurrent | ||
io = saturation_current | ||
rs = resistance_series | ||
rsh = resistance_shunt | ||
nnsvt = nNsVth | ||
x = (il, io, rs, rsh, nnsvt) # collect args | ||
# first estimate Voc | ||
voc_est = est_voc(il, io, nnsvt) | ||
# find the real voc | ||
resnorm = np.inf # nor of residuals | ||
while resnorm > tol: | ||
i_test, v_test, grad, _, _, _, _ = bishop88(voc_est, *x) | ||
newton_step = i_test / grad | ||
# don't let step get crazy | ||
if np.abs(newton_step / voc_est) > damp: | ||
break | ||
voc_est -= newton_step | ||
resnorm = i_test**2 | ||
if log: | ||
LOGGER.debug( | ||
'voc_est=%g, step=%g, i_test=%g, v_test=%g, resnorm=%g', | ||
voc_est, newton_step, i_test, v_test, resnorm | ||
) | ||
if test: | ||
delta = EPS**0.3 | ||
i_test2, _, _, _, _, _, _ = bishop88(voc_est * (1.0 + delta), *x) | ||
LOGGER.debug('test_grad=%g', (i_test2 - i_test) / voc_est / delta) | ||
LOGGER.debug('grad=%g', grad) | ||
# find isc too | ||
vd_sc = 0.0 | ||
resnorm = np.inf # nor of residuals | ||
while resnorm > tol: | ||
isc_est, v_test, _, grad, _, _, _ = bishop88(vd_sc, *x) | ||
newton_step = v_test / grad | ||
# don't let step get crazy | ||
if np.abs(newton_step / voc_est) > damp: | ||
break | ||
vd_sc -= newton_step | ||
resnorm = v_test**2 | ||
if log: | ||
LOGGER.debug( | ||
'vd_sc=%g, step=%g, isc_est=%g, v_test=%g, resnorm=%g', | ||
vd_sc, newton_step, isc_est, v_test, resnorm | ||
) | ||
if test: | ||
delta = EPS**0.3 | ||
_, v_test2, _, _, _, _, _ = bishop88(vd_sc * (1.0 + delta), *x) | ||
LOGGER.debug('test_grad=%g', (v_test2 - v_test) / vd_sc / delta) | ||
LOGGER.debug('grad=%g', grad) | ||
# find the mpp | ||
vd_mp = voc_est | ||
resnorm = np.inf # nor of residuals | ||
while resnorm > tol: | ||
imp_est, vmp_est, _, _, pmp_est, grad_p, grad2p = bishop88(vd_mp, *x) | ||
newton_step = grad_p / grad2p | ||
# don't let step get crazy | ||
if np.abs(newton_step / voc_est) > damp: | ||
break | ||
vd_mp -= newton_step | ||
resnorm = grad_p**2 | ||
if log: | ||
LOGGER.debug( | ||
'vd_mp=%g, step=%g, pmp_est=%g, resnorm=%g', | ||
vd_mp, newton_step, pmp_est, resnorm | ||
) | ||
if test: | ||
delta = EPS**0.3 | ||
_, _, _, _, _, grad_p2, _ = bishop88(vd_mp * (1.0 + delta), *x) | ||
LOGGER.debug('test_grad=%g', (grad_p2 - grad_p) / vd_mp / delta) | ||
LOGGER.debug('grad=%g', grad2p) | ||
out = OrderedDict() | ||
out['i_sc'] = isc_est | ||
out['v_oc'] = voc_est | ||
out['i_mp'] = imp_est | ||
out['v_mp'] = vmp_est | ||
out['p_mp'] = pmp_est | ||
out['i_x'] = None | ||
out['i_xx'] = None | ||
# calculate the IV curve if requested using bishop88 | ||
if ivcurve_pnts: | ||
vd = voc_est * ( | ||
(11.0 - np.logspace(np.log10(11.0), 0.0, ivcurve_pnts)) / 10.0 | ||
) | ||
i, v, _, _, p, _, _ = bishop88(vd, *x) | ||
out['i'] = i | ||
out['v'] = v | ||
out['p'] = p | ||
return out |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
timeit
might be better here