Skip to content
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

ENH: add methods and tests for a explicit IV curve calculation of single-diode model #409

Merged
merged 76 commits into from
Jul 10, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
22d2e5c
add methods and tests for a faster way to calculate single-diode model
mikofski Jan 27, 2018
bb7c49a
log estimate of speedup in test
mikofski Jan 27, 2018
36d4f78
use ordered dict for output, match inputs and outputs, add iv-curve
mikofski Jan 27, 2018
817a303
add "slow" but reliable ways to calculate i_from_v and vv
mikofski Jan 28, 2018
b17a8cd
add check for numerical errors
mikofski Jan 30, 2018
0666d29
use float64, output symbols for fun, make executable
mikofski Jan 30, 2018
595e254
add tests for slower_way using fminbound
mikofski Jan 30, 2018
5ade588
change voc to v_oc
mikofski Jan 30, 2018
7179096
add numeric type to args in docstrings
mikofski Jan 30, 2018
4b66f87
replace custom newton method with scipy.optimize.newton
mikofski Jan 31, 2018
664d7d8
get lambdas working in fast_i_from_v
mikofski Jan 31, 2018
09a9e14
add fast_v_from_i using newton
mikofski Jan 31, 2018
06be522
add fast_mppt
mikofski Jan 31, 2018
1aba56a
calculate i_x and i_xx too for both fast and slow
mikofski Jan 31, 2018
f09be91
test i_x and i_xx too
mikofski Jan 31, 2018
4905363
use brentq instead of fminbound
mikofski Jan 31, 2018
a3d2bb4
change test for numerical errors to use data file
mikofski Jan 31, 2018
8ee1b94
working on #410
mikofski Jan 31, 2018
c9a893a
helping test_singlediode pass tests
mikofski Jan 31, 2018
c5248bb
add the requires scipy decorator
mikofski Jan 31, 2018
68c65c3
fixes #410
mikofski Jan 31, 2018
41e0c83
add i, v, and p to out
mikofski Jan 31, 2018
d7b6e62
need to calculate i-v curve points to compare
mikofski Jan 31, 2018
aa3d29a
add mppt method, update docs
mikofski Jan 31, 2018
9fc350d
fix latex and other sphinx formatting issue sand typos
mikofski Jan 31, 2018
54e8d18
fix circular import issues?
mikofski Feb 1, 2018
16ad9b4
try something remove redundant package imports
mikofski Feb 1, 2018
7aca302
don't raise import error at module level if no scipy
mikofski Feb 1, 2018
086e73f
okay I think I fixed it, now return pvlib module objects
mikofski Feb 1, 2018
9f2b157
try to modify i_from_v
mikofski Feb 1, 2018
555e946
implement v_from_i wrapper
mikofski Feb 1, 2018
52a8e88
respond to @thunderfish24 review items:
mikofski Feb 1, 2018
c0e18a5
change default argument for method to "gold"
mikofski Feb 1, 2018
09c6a75
update test_singlediode_methods to use lambertw in comparison
mikofski Feb 1, 2018
ff8cc0b
change name from mppt -> mpp
mikofski Feb 1, 2018
446fa9e
add test to check for verbose yet graceful failure of v_from_i
mikofski Feb 1, 2018
f976f61
fix pytest.raise context, add test_v_from_i
mikofski Feb 1, 2018
db88022
change mppt->mpp in api.rst docs
mikofski Feb 1, 2018
a509dd3
fix what's new to point to mpp in docs, also add links to bishop88 an…
mikofski Feb 1, 2018
62010c2
Merge branch 'master' into faster_way
mikofski Jun 20, 2018
5c939b8
TST: fix precision test to use new calcparams* API
mikofski Jun 20, 2018
66a801d
TST: update singlediode test for updated atol in #415
mikofski Jun 20, 2018
df1423b
DOC: update what's new for 0.6 with proposed explicit SDM solution
mikofski Jun 20, 2018
eb20cf4
DOC: update docstring to conform to numpydoc style in sdm methods
mikofski Jun 20, 2018
e3805ef
Merge branch 'master' into faster_way
mikofski Jun 26, 2018
5f89578
ENH: refactor est_voc -> estimate_voc
mikofski Jun 26, 2018
ba84fcf
ENH refactor pvsystem.estimate_voc too, also update docs to numpy style
mikofski Jun 26, 2018
0f3893c
ENH refactor vd->diode_voltage in bishop88
mikofski Jun 26, 2018
d6023b1
ENH: TST: refactor reshaping conditions for _array_newton
mikofski Jun 27, 2018
80b3352
ENH: TST: make brentq vectorized always
mikofski Jun 27, 2018
ef20676
ENH: TST: can't vectorize brentq because it treats args as array
mikofski Jun 27, 2018
98d1c01
BUG: TST: use "brentq" instead of "gold"
mikofski Jun 27, 2018
4ecd913
TST: change test fixtures with nonsensical values in quadrant four
mikofski Jun 27, 2018
bf05d2d
TST: fix mpp tests
mikofski Jun 28, 2018
edc445d
ENH: clean up last little bit
mikofski Jun 28, 2018
f542d15
ENH: BUG: TST: DOC: remove all traces for "fast" or "slow"
mikofski Jun 28, 2018
cc6c976
TST: BUG: fix the boolean mask numpy<1.14 bug in tests
mikofski Jun 28, 2018
22c53fc
ENH: TST: ignore .pytest_cache/ folder
mikofski Jun 29, 2018
442a3d4
DOC: MAINT: respond to review by @cwhanse , clean up docstrings
mikofski Jun 29, 2018
6bcffe8
MAINT: update comment about mpp search algorithm
mikofski Jun 29, 2018
e6b60c6
MAINT: update what's new for v0.6
mikofski Jun 29, 2018
0fc9c83
DOC: make sure docs render well
mikofski Jun 29, 2018
fc6cee1
DOC: MAINT: rewording what's new with @cwhanse comments to make it clear
mikofski Jun 29, 2018
28c8ffb
API: change bishop88 and estimate_voc to be used from singlediode_met…
mikofski Jun 29, 2018
58361c1
DOC: link to new singlediode_methods subfunctions in what's new
mikofski Jun 29, 2018
5f9ed41
API: DOC: ENH: change pvsystem.mpp() -> max_power_point()
mikofski Jun 29, 2018
43475cc
MAINT: address comments by @wholmgren
mikofski Jun 30, 2018
c79ab97
Merge branch 'master' into faster_way
mikofski Jul 10, 2018
1ad6031
MAINT: add comment to explain why we import brentq in try-except
mikofski Jul 10, 2018
f14ba04
MAINT: move lambertw methods to singlediode_methods.py
mikofski Jul 10, 2018
8d86560
MAINT: add docstring elaborating the numerical precision test
mikofski Jul 10, 2018
337d7b4
MAINT: use np.expm1(x) for exp(x) - 1 in bishop88 single diode method
mikofski Jul 10, 2018
ce5b0f5
MAINT: replace boilerplate code for broadcasting newton array args
mikofski Jul 10, 2018
3e009f8
MAINT: TEST: parametrize i_from_v and v_from_i for methods, atol
mikofski Jul 10, 2018
082dfd5
MAINT: remove import of functools.partial in pvsystem.py
mikofski Jul 10, 2018
ceb69cd
MAINT: wrap lines longer than 79 characters
mikofski Jul 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions pvlib/test/test_way_faster.py
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
Copy link
Contributor

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

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this kind of a bigish deal?

Copy link
Contributor

@markcampanelli markcampanelli Jan 27, 2018

Choose a reason for hiding this comment

The 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:
OrderedDict([('i_sc', 5.857594583158265), ('v_oc', 57.779106188588685), ('i_mp', 5.374898118822274), ('v_mp', 47.473396494199655), ('p_mp', 255.16466951077766), ('i_x', 5.770970666689348), ('i_xx', 4.048918484984828)])

pvfit calculation:
ff=0.7539291803981164, i_sc_A=5.857594583158266, i_mp_A=5.375969556815134, p_mp_W=255.16475223820953, v_mp_V=47.46395037054039, v_oc_V=57.77910618858893

Copy link
Contributor

Choose a reason for hiding this comment

The 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()
276 changes: 276 additions & 0 deletions pvlib/way_faster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
"""
Faster ways to calculate single diode model currents and voltages using
methods from J.W. Bishop (Solar Cells, 1988).
"""

import logging
from collections import OrderedDict
import numpy as np
from scipy.optimize import fminbound

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):
"""
Rough estimate of open circuit voltage useful for bounding searches for
``i`` of ``v`` when using :func:`~pvlib.way_faster`.

:param il: photo-generated current [A]
:param io: diode one reverse saturation or "dark" current [A]
:param nnsvt" product of thermal voltage ``Vt`` [V], ideality factor ``n``,
and number of series cells ``Ns``
:returns: rough estimate of open circuit voltage [V]
"""
# http://www.pveducation.org/pvcdrom/open-circuit-voltage
return nnsvt * np.log(il / io + 1.0)


def bishop88(vd, il, io, rs, rsh, nnsvt):
"""
Explicit calculation single-diode-model (SDM) currents and voltages using
diode junction voltages [1].

[1] "Computer simulation of the effects of electrical mismatches in
photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
https://doi.org/10.1016/0379-6787(88)90059-2

:param vd: diode voltages [V}]
:param il: photo-generated current [A]
:param io: diode one reverse saturation or "dark" current [A]
:param rs: series resitance [ohms]
:param rsh: shunt resitance [ohms]
:param nnsvt" product of thermal voltage ``Vt`` [V], ideality factor ``n``,
and number of series cells ``Ns``
:returns: tuple containing currents [A], voltages [V], gradient ``di/dvd``,
gradient ``dv/dvd``, power [W], gradient ``dp/dv``, and gradient
``d2p/dv/dvd``
"""
a = np.exp(vd / nnsvt)
b = 1.0 / rsh
Copy link
Contributor

Choose a reason for hiding this comment

The 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 # di/dv
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:
Copy link
Member Author

@mikofski mikofski Jan 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to (x0 + 1.0) in case x0 is zero.

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 slow_i_from_v(v, photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth):
"""
This is a slow but reliable way to find current given any voltage.
"""
# 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 bound the search using voc
voc_est = est_voc(il, io, nnsvt)
vd = fminbound(lambda vd: (v - bishop88(vd, *x)[1])**2, 0.0, voc_est)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the most straightforward option for current computation at specific terminal voltages might be to directly implement a vectorized, derivative-free brent algorithm with a sensible root bracket that will "guarantee" convergence. I am investigating better ways to vectorize, say, scipy's brentq for this purpose. It would be a shame to have to rewrite it from scratch just to get performant vectorization.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't reviewed all of this, but just want to suggest that simplicity should be a higher objective than speed for the reference implementation.

Copy link
Member Author

@mikofski mikofski Jan 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe what I'm suggesting is the simplest method guaranteed to give the most accurate answer with the governing equations:

  1. Use an explicit method
  2. Estimate open circuit voltage by assuming shunt resistance is infinite
  3. Bound the solution between vd = 0 and vd = voc_est
  4. Use bisection, a "slow but stable" method, guaranteed to find the desired operating condition between the bounds

Further ideological simplifications:

  • use off-the-shelf methods from a mature, familiar, stable library ie: scipy.optimize.fminbound
  • use the SDM equations without algebraic manipulation ie: no Lambert-W
  • methods (with the exception of the faster_way and newton_solver()) are short and simple.

And I'm working on simplifying both of those methods too. E.g. I think we can use the canned scipy.optimize.minimize_scalar() helper function instead of the custom newton_solver in faster_way. Also of course, these are temporary names assuming #410 is implemented.

I am very open to changes. I want to make this as simple and robust as possible. Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, @mikofski , I was not commenting on your code at all but rather referring to @thunderfish24 suggestion to vectorize things.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I understand now. Thanks @adriesse I'm sorry too. @thunderfish24 May I suggest we move proposals for customized improvements to scipy.optimize subroutines to another issue?

Copy link
Contributor

@markcampanelli markcampanelli Jan 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Bishop method is a sufficient and extremely simple "ground truth" algorithm at the "uncontrolled" terminal voltages that it produces. However, I am sufficiently skeptical that I would want to see a consensus of 2+ algorithms produce the same result (up to some tolerance) at any other voltage values that require an iterative solver, be it scipy.optimize's fminbnd, newton, or brentq, or lambertw with argument overflow protection logic. If this is not established by algorithm consensus, then something like interval analysis on the model residuals would be convincing. I received push back for doing something even less daring with model residual computations in an early commit to #340. At the very least, for the "slow but stable" method, we should make sure all convergence flags are true, and maybe even make assertions on the absolute residuals for the substituted solution (unless the solver has already verified that, as some solvers use other stopping criteria).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To illustrate brentq() approach more concretely: #412

return bishop88(vd, il, io, rs, rsh, nnsvt)[0]


def slow_v_from_i(i, photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth):
"""
This is a slow but reliable way to find voltage given any current.
"""
# 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 bound the search using voc
voc_est = est_voc(il, io, nnsvt)
vd = fminbound(lambda vd: (i - bishop88(vd, *x)[0])**2, 0.0, voc_est)
return bishop88(vd, il, io, rs, rsh, nnsvt)[1]

def slow_mppt(photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth):
"""
This is a slow but reliable way to find mpp.
"""
# 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 bound the search using voc
voc_est = est_voc(il, io, nnsvt)
vd = fminbound(lambda vd: -(bishop88(vd, *x)[4])**2, 0.0, voc_est)
i, v, _, _, p, _, _ = bishop88(vd, il, io, rs, rsh, nnsvt)
return i, v, p


def slower_way(photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth, ivcurve_pnts=None):
"""
This is the slow but reliable 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
voc = slow_v_from_i(0, *x)
i_sc = slow_i_from_v(0, *x)
i_mp, v_mp, p_mp = slow_mppt(*x)
out = OrderedDict()
out['i_sc'] = i_sc
out['v_oc'] = voc
out['i_mp'] = i_mp
out['v_mp'] = v_mp
out['p_mp'] = p_mp
out['i_x'] = None
out['i_xx'] = None
# calculate the IV curve if requested using bishop88
if ivcurve_pnts:
vd = voc * (
(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


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