Skip to content

Support ideal PV devices (#324) #340

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

Merged
merged 25 commits into from
Sep 29, 2017
Merged

Conversation

markcampanelli
Copy link
Contributor

@markcampanelli markcampanelli commented Jun 15, 2017

Based on feedback, I decided to wait on implementing my full algorithm that uses the absolute residual in the sum of currents at the diode node to determine when an explicit computation for an ideal device is a better approximation for a very near ideal device. This PR documents one really edgy case where this appears to have been a better numerical computation.

By reformulating some of the LambertW formulas, this PR does make many computations more stable, so it appears to be harder to get into a poorly behaved edge case.

In future work, I propose changing the shunt resistance parameter Rsh to a shunt conductance parameter Gsh=1./Rsh, which appears to be much better behaved numerically for ideal and near-ideal devices, i.e., Gsh is approaching/at zero. This should avoid a lot of logic bloat in considering yet another limiting case in pvsystem.i_from_v() where Rsh is so large that it is essentially infinite and the shunt term in the model must be dropped out. (Kudos to Paul Basore for recommending that I switch to Gsh in my early modeling days at NREL.)

  • Closes Ideal PV devices are not supported #324
  • Tests added / passed
  • Passes git diff upstream/master -- pvlib | flake8 --diff
  • Fully documented, including the appropriate docs/sphinx/source/whatsnew file for all changes and the docs/sphinx/source/api.rst for new API

@markcampanelli markcampanelli changed the title [WIP] Support ideal PV devices [WIP] Support ideal PV devices (#324) Jun 15, 2017
@markcampanelli
Copy link
Contributor Author

I updated pvsystem.i_from_v() to use a shunt conductance derived from the shunt resistance input, without changing the function interface. Rsh=np.inf with Rs>0 test case should now be passing. Note that computing short-circuit current with IL=0 (from zero irradiance) also appears to be improved.

I'm going to start on the analogous changes to pvsystem.v_from_i(), and hopefully get some stability tests written for parameters approaching the ideal cases.

@cwhanse
Copy link
Member

cwhanse commented Jun 15, 2017 via email

@wholmgren
Copy link
Member

Thanks @thunderfish24. I don't have time to comment on the substance at this point, but here are some more style-oriented things that I noticed with a quick glance:

  • functions need descriptive documentation
  • keep the line length to 79 characters or less
  • set your editor to trim extra white space from blank lines and the end of lines
  • use required arguments for required inputs (instead of keyword arguments)
  • more short tests are better than fewer long tests. pytest.parametrize can be useful here
  • it appears to me that the functions will work with any numeric input type (ndarrays, Series, DataFrames, scalars) so perhaps "numeric" is more appropriate. In any case, "numpy.float64 or python scalar" can probably be replaced with "scalar"

flake8 is a good tool for tracking down some style issues.

@markcampanelli
Copy link
Contributor Author

@wholmgren Thanks for the additional tips.

@markcampanelli
Copy link
Contributor Author

Here is an example of computational instability in the existing v_from_i() as Rsh->inf, and its correction in v_from_i_alt() . For this particular parameter set, the Rsh value has to be fairly large before the voltage computation goes off the rails.

import pvlib
import numpy as np

Rsh = 190.
Rs = 1.065
nNsVth = 2.89
I0 = 7.05196029e-08
IL = 10.491262

I = 0.
Voc_ideal = pvlib.pvsystem.v_from_i_alt(Rsh=np.inf, Rs=Rs, nNsVth=nNsVth, I=I, I0=I0, IL=IL)
print('Voc_ideal_V = ' + str(Voc_ideal))
print()

Rsh_pvlib_limit_vec = Rsh*np.logspace(0, 15, num=15)
for Rsh_mod in Rsh_pvlib_limit_vec:
    V_pvlib = pvlib.pvsystem.v_from_i(Rsh_mod, Rs, nNsVth, 0., I0, IL)
    print('Rsh_mod_Ohm = ' + str(Rsh_mod) + ', V_pvlib_V = ' + str(V_pvlib) + ': current_sum_A = ' + str(pvlib.pvsystem.current_sum_at_diode_node(V_pvlib, I, IL, I0, nNsVth, Rs, Rsh_mod)))

print()

for Rsh_mod in Rsh_pvlib_limit_vec:
    V_pvlib_alt, meta_dict = pvlib.pvsystem.v_from_i_alt(Rsh=Rsh_mod, Rs=Rs, nNsVth=nNsVth, I=I, I0=I0, IL=IL, return_meta_dict=True)
    print('Rsh_mod_Ohm = ' + str(Rsh_mod) + ', V_pvlib_alt_V = ' + str(V_pvlib_alt) + ': current_sum_A = ' + str(meta_dict['current_sum_at_diode_node']) + ', inf_Rsh_idx = ' + str(meta_dict['inf_Rsh_idx']))
    print('inf_Rsh_idx = ' + str(meta_dict['inf_Rsh_idx']))

print()
print()

V = 0.
Isc_ideal = pvlib.pvsystem.i_from_v_alt(Rsh=np.inf, Rs=Rs, nNsVth=nNsVth, V=V, I0=I0, IL=IL)
print('Isc_ideal_A = ' + str(Isc_ideal))
print()

Rsh_pvlib_limit_vec = Rsh*np.logspace(0, 15, num=15)
for Rsh_mod in Rsh_pvlib_limit_vec:
    V_pvlib = pvlib.pvsystem.v_from_i(Rsh_mod, Rs, nNsVth, Isc_ideal, I0, IL)
    print('Rsh_mod_Ohm = ' + str(Rsh_mod) + ', V_pvlib_V = ' + str(V_pvlib) + ': current_sum_A = ' + str(pvlib.pvsystem.current_sum_at_diode_node(V_pvlib, Isc_ideal, IL, I0, nNsVth, Rs, Rsh_mod)))

print()

for Rsh_mod in Rsh_pvlib_limit_vec:
    V_pvlib_alt, meta_dict = pvlib.pvsystem.v_from_i_alt(Rsh=Rsh_mod, Rs=Rs, nNsVth=nNsVth, I=Isc_ideal, I0=I0, IL=IL, return_meta_dict=True)
    print('Rsh_mod_Ohm = ' + str(Rsh_mod) + ', V_pvlib_alt_V = ' + str(V_pvlib_alt) + ': current_sum_A = ' + str(meta_dict['current_sum_at_diode_node']) + ', inf_Rsh_idx = ' + str(meta_dict['inf_Rsh_idx']))
    print('inf_Rsh_idx = ' + str(meta_dict['inf_Rsh_idx']))
Voc_ideal_V = 54.38378255010209

Rsh_mod_Ohm = 190.0, V_pvlib_V = 54.3039588338: current_sum_A = -1.09656728142e-12
Rsh_mod_Ohm = 2239.66040611, V_pvlib_V = 54.3770866821: current_sum_A = -1.35305239235e-11
Rsh_mod_Ohm = 26400.4143931, V_pvlib_V = 54.3832150488: current_sum_A = 6.02720963416e-11
Rsh_mod_Ohm = 311199.804321, V_pvlib_V = 54.3837344102: current_sum_A = 8.45458869689e-10
Rsh_mod_Ohm = 3668325.68488, V_pvlib_V = 54.3837784678: current_sum_A = -5.59872070431e-09
Rsh_mod_Ohm = 43241072.5954, V_pvlib_V = 54.383782208: current_sum_A = -1.56670771722e-08
Rsh_mod_Ohm = 509712201.103, V_pvlib_V = 54.3837823868: current_sum_A = 4.86196644805e-07
Rsh_mod_Ohm = 6008327554.32, V_pvlib_V = 54.3837890625: current_sum_A = -2.36503490364e-05
Rsh_mod_Ohm = 70824280686.0, V_pvlib_V = 54.3837890625: current_sum_A = -2.36420655035e-05
Rsh_mod_Ohm = 834854406545.0, V_pvlib_V = 54.3837890625: current_sum_A = -2.36413627759e-05
Rsh_mod_Ohm = 9.84100189054e+12, V_pvlib_V = 54.359375: current_sum_A = 0.0882310508952
Rsh_mod_Ohm = 1.16002643635e+14, V_pvlib_V = 54.25: current_sum_A = 0.474587242214
Rsh_mod_Ohm = 1.3674027787e+15, V_pvlib_V = 54.0: current_sum_A = 1.30466118631
Rsh_mod_Ohm = 1.61185150666e+16, V_pvlib_V = 64.0: current_sum_A = -281.859291829
Rsh_mod_Ohm = 1.9e+17, V_pvlib_V = 256.0: current_sum_A = -2.08298484277e+31

Rsh_mod_Ohm = 190.0, V_pvlib_alt_V = 54.303958833791285: current_sum_A = -1.0965672814222671e-12, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 2239.66040611, V_pvlib_alt_V = 54.377086682099616: current_sum_A = -1.3530523923499516e-11, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 26400.4143931, V_pvlib_alt_V = 54.38321504875785: current_sum_A = 6.027209634162922e-11, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 311199.804321, V_pvlib_alt_V = 54.383734410163015: current_sum_A = 8.454588696889601e-10, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 3668325.68488, V_pvlib_alt_V = 54.38377846777439: current_sum_A = -5.598720704305367e-09, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 43241072.5954, V_pvlib_alt_V = 54.38378220796585: current_sum_A = -1.5667077172235486e-08, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 509712201.103, V_pvlib_alt_V = 54.38378255010209: current_sum_A = -1.066950876690535e-07, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 6008327554.32, V_pvlib_alt_V = 54.38378255010209: current_sum_A = -9.051411744122236e-09, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 70824280686.0, V_pvlib_alt_V = 54.38378255010209: current_sum_A = -7.6787984118612e-10, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 834854406545.0, V_pvlib_alt_V = 54.38378255010209: current_sum_A = -6.515229496271385e-11, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 9.84100189054e+12, V_pvlib_alt_V = 54.38378255010209: current_sum_A = -5.536902638802866e-12, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 1.16002643635e+14, V_pvlib_alt_V = 54.38378255010209: current_sum_A = -4.794731684004577e-13, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 1.3674027787e+15, V_pvlib_alt_V = 54.38378255010209: current_sum_A = -5.0429730941841095e-14, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 1.61185150666e+16, V_pvlib_alt_V = 54.38378255010209: current_sum_A = -1.4032135621217036e-14, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 1.9e+17, V_pvlib_alt_V = 54.38378255010209: current_sum_A = -1.0944371470875724e-14, inf_Rsh_idx = True
inf_Rsh_idx = True


Isc_ideal_A = 10.491258702613099

Rsh_mod_Ohm = 190.0, V_pvlib_V = -11.1725640177: current_sum_A = 0.0
Rsh_mod_Ohm = 2239.66040611, V_pvlib_V = -11.1658058955: current_sum_A = 0.0
Rsh_mod_Ohm = 26400.4143931, V_pvlib_V = -11.0861950324: current_sum_A = 0.0
Rsh_mod_Ohm = 311199.804321, V_pvlib_V = -10.156299235: current_sum_A = 1.7763568394e-15
Rsh_mod_Ohm = 3668325.68488, V_pvlib_V = -3.07774074453: current_sum_A = 1.7763568394e-15
Rsh_mod_Ohm = 43241072.5954, V_pvlib_V = -0.225844280917: current_sum_A = 1.42108547152e-14
Rsh_mod_Ohm = 509712201.103, V_pvlib_V = -0.0188397606685: current_sum_A = 1.75859327101e-13
Rsh_mod_Ohm = 6008327554.32, V_pvlib_V = -0.00159774131316: current_sum_A = 2.0872192863e-12
Rsh_mod_Ohm = 70824280686.0, V_pvlib_V = -0.000156491063535: current_sum_A = 2.46078712962e-11
Rsh_mod_Ohm = 834854406545.0, V_pvlib_V = -0.000260397326201: current_sum_A = 2.90061308306e-10
Rsh_mod_Ohm = 9.84100189054e+12, V_pvlib_V = -0.00293508917093: current_sum_A = 3.41758088496e-09
Rsh_mod_Ohm = 1.16002643635e+14, V_pvlib_V = -0.0345864892006: current_sum_A = 4.00655846278e-08
Rsh_mod_Ohm = 1.3674027787e+15, V_pvlib_V = -0.407692909241: current_sum_A = 4.43121054516e-07
Rsh_mod_Ohm = 1.61185150666e+16, V_pvlib_V = -4.80576324463: current_sum_A = 2.72938665802e-06
Rsh_mod_Ohm = 1.9e+17, V_pvlib_V = -56.6489257812: current_sum_A = 3.36790649413e-06

Rsh_mod_Ohm = 190.0, V_pvlib_alt_V = -11.17256401767651: current_sum_A = 0.0, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 2239.66040611, V_pvlib_alt_V = -11.165805895484587: current_sum_A = 0.0, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 26400.4143931, V_pvlib_alt_V = -11.08619503244795: current_sum_A = 0.0, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 311199.804321, V_pvlib_alt_V = -10.156299235004479: current_sum_A = 1.7763568394002505e-15, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 3668325.68488, V_pvlib_alt_V = -3.077740744526781: current_sum_A = 1.7763568394002505e-15, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 43241072.5954, V_pvlib_alt_V = -0.2258442809169594: current_sum_A = 1.4210854715202004e-14, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 509712201.103, V_pvlib_alt_V = -0.018839760668470262: current_sum_A = 1.758593271006248e-13, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 6008327554.32, V_pvlib_alt_V = -0.0015977413131622598: current_sum_A = 2.0872192862952943e-12, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 70824280686.0, V_pvlib_alt_V = -0.00015649106353521347: current_sum_A = 2.460787129621167e-11, inf_Rsh_idx = False
inf_Rsh_idx = False
Rsh_mod_Ohm = 834854406545.0, V_pvlib_alt_V = 1.1329781557378737e-10: current_sum_A = -1.3383072428041487e-11, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 9.84100189054e+12, V_pvlib_alt_V = 1.1329781557378737e-10: current_sum_A = -1.13509202037676e-12, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 1.16002643635e+14, V_pvlib_alt_V = 1.1329781557378737e-10: current_sum_A = -9.592326932761353e-14, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 1.3674027787e+15, V_pvlib_alt_V = 1.1329781557378737e-10: current_sum_A = -8.881784197001252e-15, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 1.61185150666e+16, V_pvlib_alt_V = 1.1329781557378737e-10: current_sum_A = 0.0, inf_Rsh_idx = True
inf_Rsh_idx = True
Rsh_mod_Ohm = 1.9e+17, V_pvlib_alt_V = 1.1329781557378737e-10: current_sum_A = 0.0, inf_Rsh_idx = True
inf_Rsh_idx = True

@markcampanelli
Copy link
Contributor Author

markcampanelli commented Jun 23, 2017

@wholmgren @cwhanse I think this is in a state where a preliminary review would be helpful now. Some notes--

  • I used a sdm_ prefix (for single diode model) on my three new functions: sdm_current_sum(), sdm_v_from_i(), and sdm_i_from_v(). One migration strategy would be to introduce them as is, mark the old ones as deprecated, and then switch over after a grace period and after we get some mileage on the new functions. I do not really know the extent of the other code paths and user use cases (e.g., Monte Carlo simulation?) that I am touching here.
  • My functions call into v_from_i() and i_from_v() at this point to keep the code DRY. Note: I have code that avoids having to do log transforms for large LambertW inputs that overflow, but I don't think it is ready for pvlib yet, esp. because it should be at least as good as scipy.lambertw().
  • Because of a peculiarity in numpy.where(), sdm_v_from_i() and sdm_i_from_v() always return an numpy.ndarray, which will be rank-0 for scalar-only input. I actually like this consistency of output.
  • I did not have luck passing pandas.DataFrame to the sdm_current_sum() unit test. It seems to behave differently than numpy.ndarray and pandas.Series, which concerns me, but I am unfamiliar with pandas. I don't really see the typical user needing this lower-level function anyhow.
  • I'm not sure how much pandas-input unit testing to do in sdm_v_from_i() and sdm_i_from_v(). Can I just assume/hope that the numpy.asarray calls will do their job? Perhaps I should not just in case someone tries to remove the casting.
  • Remaining TODO's for this PR concern numerical stability testing and benchmarks. The new code is indeed perhaps ~10x slower with better accuracy in some really edgy cases, but I cannot say if/how this would affect current consumers.

@wholmgren
Copy link
Member

I don't think a grace period is needed if we bump from version 0.4.x to 0.5.0. We would want to make sure that the new functions pass all of the old tests, though.

Is the 10x performance degradation important? From only looking at the code I am guessing that it's still plenty fast. For example, 10x slower is probably acceptable if it doesn't significantly impact the runtime of singlediode.

Is return_meta_dict helpful for anything besides debugging? It seems to me that the few cases where someone would want that data could be better handled by manually computing it or using the debugger. Or did I miss something?

I don't think you need to worry about the pandas testing for these functions.

It would help if you could handle most of the errors reported when you run

git diff upstream/master | flake8 --diff --ignore N806

It makes things easier in the long run. Some of your longer lines could be shortened by using positional arguments instead of keyword arguments.

@markcampanelli
Copy link
Contributor Author

@wholmgren Thanks for the feedback. I have had to put this on hold for a bit while I finish up a paper.

@markcampanelli
Copy link
Contributor Author

markcampanelli commented Jul 27, 2017

I have decided that I would rather forgo including benchmarks and stability tests into the code, and hopefully use this PR as documentation of sufficient testing. It looks like the new code is roughly 2x slower:

Rsh = 20.
Rs = 0.1
nNsVth = 0.5
I = 3.
I0 = 6.e-7
IL = 7.

N = 10000

t_start = time.time()
for k in range(N):
    pvlib.pvsystem.v_from_i(Rsh, Rs, nNsVth, I, I0, IL)
t_end = time.time()
reg_delta_t = t_end - t_start
print('pvlib.pvsystem.v_from_i: average seconds per computation ' + str(reg_delta_t/N))

t_start = time.time()
for k in range(N):
    pvlib.pvsystem.sdm_v_from_i(Rsh=Rsh, Rs=Rs, nNsVth=nNsVth, I=I, I0=I0, IL=IL)
t_end = time.time()
alt_delta_t = t_end - t_start
print('pvlib.pvsystem.sdm_v_from_i: average seconds per computation ' + str(alt_delta_t/N))

print('pvlib.pvsystem.sdm_v_from_i is {} times slower'.format(alt_delta_t/reg_delta_t))

gives

pvlib.pvsystem.v_from_i: average seconds per computation 7.457981109619141e-05
pvlib.pvsystem.sdm_v_from_i: average seconds per computation 0.00014008252620697023
pvlib.pvsystem.sdm_v_from_i is 1.8782901719380174 times slower

and

Rsh = 20.
Rs = 0.1
nNsVth = 0.5
V = 0.
I0 = 6.e-7
IL = 7.

N = 10000

t_start = time.time()
for k in range(N):
    pvlib.pvsystem.i_from_v(Rsh, Rs, nNsVth, V, I0, IL)
t_end = time.time()
reg_delta_t = t_end - t_start
print('pvlib.pvsystem.i_from_v: average seconds per computation ' + str(reg_delta_t/N))

t_start = time.time()
for k in range(N):
    pvlib.pvsystem.sdm_i_from_v(Rsh=Rsh, Rs=Rs, nNsVth=nNsVth, V=V, I0=I0, IL=IL)
t_end = time.time()
alt_delta_t = t_end - t_start
print('pvlib.pvsystem.sdm_i_from_v: average seconds per computation ' + str(alt_delta_t/N))

print('pvlib.pvsystem.sdm_i_from_v is {} times slower'.format(alt_delta_t/reg_delta_t))

gives

pvlib.pvsystem.i_from_v: average seconds per computation 5.51548957824707e-05
pvlib.pvsystem.sdm_i_from_v: average seconds per computation 0.00012906100749969482
pvlib.pvsystem.sdm_i_from_v is 2.3399737352184955 times slower

@markcampanelli
Copy link
Contributor Author

@wholmgren "Is return_meta_dict helpful for anything besides debugging? It seems to me that the few cases where someone would want that data could be better handled by manually computing it or using the debugger. Or did I miss something?"

The meta_dict would probably be used largely for debugging, but I claim that reconstructing this info is not so easily done outside the function call. The default behavior is also the behavior the vast majority of users would want/expect.

@markcampanelli
Copy link
Contributor Author

markcampanelli commented Jul 27, 2017

@wholmgren @cwhanse Assuming this passes CI, it is ready for review for merging into master. I am strongly in favor of having a deprecation period for the existing pvsystem.i_from_v and pvsystem.v_from_i functions, which will no longer be used internally after this PR (except where these functions' code still needs to be ported to the new functions).

@markcampanelli markcampanelli changed the title [WIP] Support ideal PV devices (#324) Support ideal PV devices (#324) Jul 27, 2017
[3.01079860e+00, 2.88414114e+00,
3.10862447e-14],
[6.00726296e+00, 5.74622046e+00,
0.00000000e+00]]))])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Be gone nan's from zero irradiance calculations!

Copy link
Member

Choose a reason for hiding this comment

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

I suspect the NaNs arise from this line in calcparams_desoto:

Rsh = Rsh_ref * (irrad_ref / poa_global)

We should change to use np.divide here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is "fixed" in the sense that Rsh is computed as +inf when poa_global is zero, and now the i_from_v and v_from_i code can properly handle this infinity.

@markcampanelli
Copy link
Contributor Author

markcampanelli commented Jul 27, 2017

Looking into these failures on only some python configurations.

image

Update: Looks like the same failure might happen in master ATM.

@markcampanelli
Copy link
Contributor Author

@wholmgren Cliff had concerns about documenting my changes to the published algorithm. Any suggestions here? I'd like to get a thorough review after posting a description somewhere good.

w = logargW
for _ in range(0, 3):
w = w * (1 - np.log(w) + logargW) / (1 + w)
lambertwterm[idx_w] = w
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking at the coverage report, we never hit lines 1937-1949, even though one of the pre-existing unit tests (copied to the fixture in this PR) is supposed to cover this branch of the code. It may be that recasting in terms of Gsh helps avoid the overflow. @cwhanse Any ideas here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The unit test was from #226, I think.

Copy link
Member

Choose a reason for hiding this comment

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

Not sure why the parameters from #225 don't cause entry into that code block. They cause overflow on the np.exp() call.

def test_v_from_i_bigger():
    # 1000 W/m^2 on a Canadian Solar 220M with 20 C ambient temp
    # github issue 225
    output = pvsystem.v_from_i(190, 1.065, 2.89, 0, 7.05196029e-08, 10.491262)
'''

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So it appears that the Gsh recast helps the existing test's computation stay finite:

In[5]: pvlib.pvsystem.v_from_i(190, 1.065, 2.89, 0, 7.05196029e-08, 10.491262)
Using LambertW with argW=[ 1.64103617e+294] and lambertwterm=[ 670.94665556]
Out[5]: 54.303958833791285

However, the original parameters from #225 serve as a good test, I think:

In[5]: pvlib.pvsystem.v_from_i(381.68, 1.065, 2.681527737715915, 0, 1.8739027472625636e-09, 5.1366949999999996)
Using LambertW with argW=[ inf] and lambertwterm=[ inf]
/Users/markcampanelli/Documents/Programming/pvlib-python/pvlib/pvsystem.py:1926: RuntimeWarning: overflow encountered in exp
np.exp((-I[idx] + IL[idx] + I0[idx]) / (Gsh[idx]*nNsVth[idx]))
Out[4]: 58.19323124611128

Note that in followup work I might be interested in checking how accurate lambertw() is for such large inputs.

I will add this parameter set as a unit test and verify the coverage

@markcampanelli
Copy link
Contributor Author

@cwhanse @wholmgren Ready again for review. Looking for some feedback about testing LambertW input overflow in v_from_i(). Does anyone have any other realistic parameter combinations for unit testing this?

Copy link
Member

@cwhanse cwhanse left a comment

Choose a reason for hiding this comment

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

As far as the numerics, it looks good to me. Thanks @thunderfish24

'I': 0.,
'I0': 1.8739027472625636e-09,
'IL': 5.1366949999999996,
'V_expected': 58.19323124611128
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cwhanse Would you mind running this parameter set though the Matlab version to "independently" verify my V_expected value?

Copy link
Member

Choose a reason for hiding this comment

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

58.193231246111300

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Awesome! Thanks.

@markcampanelli
Copy link
Contributor Author

Note: Some informal followup performance testing suggests that the additional computation and indexing logic causes these new computations to take slightly more than 2x time.

@markcampanelli
Copy link
Contributor Author

@wholmgren Still waiting on the Landscape result to verify increased code coverage with the new test. Other than that, I think it good to go except for any further reviewers you might try to muster.

@cwhanse
Copy link
Member

cwhanse commented Sep 11, 2017 via email

@markcampanelli
Copy link
Contributor Author

So I currently use the ideal computation first as an "easy" way to get the correct shape of the output. It's probably worth investigating if a bunch of "input shape logic" is ultimately faster here.

@markcampanelli
Copy link
Contributor Author

Coverage of my changes is good now.

@markcampanelli
Copy link
Contributor Author

@cwhanse It looks like I may be able to use numpy.broadcast to some advantage here.

@markcampanelli
Copy link
Contributor Author

markcampanelli commented Sep 12, 2017

@cwhanse I did not see the speedup that I hoped for (still about 2-2.5x slower), but I the code is cleaner now. I think the improved computational range and stability are worth it at this point.

@markcampanelli
Copy link
Contributor Author

@wholmgren Note that the test failure before the last build appears to be unrelated to my changes.

@markcampanelli
Copy link
Contributor Author

@mikofski Would you be able to do a quick review of this PR?

@mikofski
Copy link
Member

@thunderfish24 I'll try to take a quick look next week

@markcampanelli
Copy link
Contributor Author

@wholmgren Can you provide a second code review so that perhaps we can get this merged?

@wholmgren
Copy link
Member

wholmgren commented Sep 27, 2017 via email

@wholmgren wholmgren added this to the 0.5.1 milestone Sep 29, 2017
@wholmgren
Copy link
Member

@thunderfish24 this looks great! Thank you for your patience and flexibility as we worked this out.

@wholmgren wholmgren merged commit d924446 into pvlib:master Sep 29, 2017
@markcampanelli
Copy link
Contributor Author

Thanks @wholmgren.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants