Skip to content

Commit 44cb79f

Browse files
authored
use _lambertw_pvlib etc. (#2732)
* use _lambertw_pvlib etc. * whatsnew, lint * use _lambertw_pvlib in ivtools * Update pvlib/singlediode.py * avoid warning * pacify flake8 * another instance replaced
1 parent 9cf6cc2 commit 44cb79f

File tree

5 files changed

+26
-34
lines changed

5 files changed

+26
-34
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Enhancements
3636
* Include `ross` and `faiman_rad` in the allowed models within
3737
:py:meth:`pvlib.pvsystem.PVSystem.get_cell_temperature` (:issue:`2625`, :pull:`2631`)
3838
* Accelerate the internals of :py:func:`~pvlib.solarpostion.ephemeris`. (:pull:`2626`)
39+
* Accelerate the intervals of :py:func:`~pvlib.pvsystem.singlediode` when
40+
`method='lambertw'`. (:pull:`2732`, :pull:`2723`)
3941

4042
Documentation
4143
~~~~~~~~~~~~~

pvlib/ivtools/sdm/_fit_desoto_pvsyst_sandia.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
import numpy as np
66

77
from scipy import optimize
8-
from scipy.special import lambertw
98

109
from pvlib.pvsystem import singlediode, v_from_i
11-
from pvlib.ivtools.utils import rectify_iv_curve, _numdiff
10+
from pvlib.ivtools.utils import rectify_iv_curve, _numdiff, _lambertw_pvlib
1211
from pvlib.pvsystem import _pvsyst_Rsh
1312

1413

@@ -535,7 +534,7 @@ def _calc_theta_phi_exact(vmp, imp, iph, io, rs, rsh, nnsvth):
535534
nnsvth == 0,
536535
np.nan,
537536
rsh * io / nnsvth * np.exp(rsh * (iph + io - imp) / nnsvth))
538-
phi = np.where(argw > 0, lambertw(argw).real, np.nan)
537+
phi = np.where(argw > 0, _lambertw_pvlib(argw), np.nan)
539538

540539
# NaN where argw overflows. Switch to log space to evaluate
541540
u = np.isinf(argw)
@@ -561,7 +560,7 @@ def _calc_theta_phi_exact(vmp, imp, iph, io, rs, rsh, nnsvth):
561560
np.nan,
562561
rsh / (rsh + rs) * rs * io / nnsvth * np.exp(
563562
rsh / (rsh + rs) * (rs * (iph + io) + vmp) / nnsvth))
564-
theta = np.where(argw > 0, lambertw(argw).real, np.nan)
563+
theta = np.where(argw > 0, _lambertw_pvlib(argw), np.nan)
565564

566565
# NaN where argw overflows. Switch to log space to evaluate
567566
u = np.isinf(argw)

pvlib/ivtools/sdm/desoto.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
from scipy import constants
44
from scipy import optimize
5-
from scipy.special import lambertw
65

7-
from pvlib.ivtools.utils import rectify_iv_curve
6+
from pvlib.ivtools.utils import rectify_iv_curve, _lambertw_pvlib
87
from pvlib.ivtools.sde import _fit_sandia_cocontent
98

109
from pvlib.ivtools.sdm._fit_desoto_pvsyst_sandia import (
@@ -454,7 +453,7 @@ def fit_desoto_batzelis(v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc):
454453
# Equation numbers refer to [1]
455454
t0 = 298.15 # K
456455
del0 = (1 - beta_voc * t0) / (50.1 - alpha_sc * t0) # Eq 9
457-
w0 = np.real(lambertw(np.exp(1/del0 + 1)))
456+
w0 = _lambertw_pvlib(np.exp(1/del0 + 1))
458457

459458
# Eqs 11-15
460459
a0 = del0 * v_oc

pvlib/pvarray.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
import numpy as np
1212
import pandas as pd
1313
from scipy.optimize import curve_fit
14-
from scipy.special import exp10, lambertw
14+
from scipy.special import exp10
15+
from pvlib.ivtools.utils import _lambertw_pvlib
1516

1617

1718
def pvefficiency_adr(effective_irradiance, temp_cell,
@@ -483,7 +484,7 @@ def batzelis(effective_irradiance, temp_cell,
483484

484485
# Eq 9-10
485486
del0 = (1 - beta_voc * t0) / (50.1 - alpha_sc * t0)
486-
w0 = np.real(lambertw(np.exp(1/del0 + 1)))
487+
w0 = _lambertw_pvlib(np.exp(1/del0 + 1))
487488

488489
# Eqs 27-28
489490
alpha_imp = alpha_sc + (beta_voc - 1/t0) / (w0 - 1)

pvlib/singlediode.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import numpy as np
66
import pandas as pd
77
from pvlib.tools import _golden_sect_DataFrame
8+
from pvlib.ivtools.utils import _lambertw_pvlib, _log_lambertw
89

910
from scipy.optimize import brentq, newton
10-
from scipy.special import lambertw
11+
1112

1213
# newton method default parameters for this module
1314
NEWTON_DEFAULT_PARAMS = {
@@ -801,12 +802,12 @@ def _lambertw_v_from_i(current, photocurrent, saturation_current,
801802
with np.errstate(over='ignore'):
802803
argW = I0 / (Gsh * a) * np.exp((-I + IL + I0) / (Gsh * a))
803804

804-
# lambertw typically returns complex value with zero imaginary part
805-
# may overflow to np.inf
806-
lambertwterm = lambertw(argW).real
805+
lambertwterm = np.zeros_like(argW)
806+
807+
# Record indices where lambertw input overflowed
808+
idx_inf = np.isinf(argW)
807809

808-
# Record indices where lambertw input overflowed output
809-
idx_inf = np.isinf(lambertwterm)
810+
lambertwterm[~idx_inf] = _lambertw_pvlib(argW[~idx_inf])
810811

811812
# Only re-compute LambertW if it overflowed
812813
if np.any(idx_inf):
@@ -815,15 +816,7 @@ def _lambertw_v_from_i(current, photocurrent, saturation_current,
815816
np.log(a[idx_inf]) +
816817
(-I[idx_inf] + IL[idx_inf] + I0[idx_inf]) /
817818
(Gsh[idx_inf] * a[idx_inf]))
818-
819-
# Three iterations of Newton-Raphson method to solve
820-
# w+log(w)=logargW. The initial guess is w=logargW. Where direct
821-
# evaluation (above) results in NaN from overflow, 3 iterations
822-
# of Newton's method gives approximately 8 digits of precision.
823-
w = logargW
824-
for _ in range(0, 3):
825-
w = w * (1. - np.log(w) + logargW) / (1. + w)
826-
lambertwterm[idx_inf] = w
819+
lambertwterm[idx_inf] = _log_lambertw(logargW)
827820

828821
# Eqn. 3 in Jain and Kapoor, 2004
829822
# V = -I*(Rs + Rsh) + IL*Rsh - a*lambertwterm + I0*Rsh
@@ -866,27 +859,25 @@ def _lambertw_i_from_v(voltage, photocurrent, saturation_current,
866859
# Explicit solutions where Rs=0
867860
if np.any(idx_z):
868861
I[idx_z] = IL[idx_z] - I0[idx_z] * np.expm1(V[idx_z] / a[idx_z]) - \
869-
Gsh[idx_z] * V[idx_z]
862+
Gsh[idx_z] * V[idx_z]
870863

871864
# Only compute using LambertW if there are cases with Rs>0
872865
# Does NOT handle possibility of overflow, github issue 298
873866
if np.any(idx_p):
874867
# LambertW argument, cannot be float128, may overflow to np.inf
875868
argW = Rs[idx_p] * I0[idx_p] / (
876-
a[idx_p] * (Rs[idx_p] * Gsh[idx_p] + 1.)) * \
877-
np.exp((Rs[idx_p] * (IL[idx_p] + I0[idx_p]) + V[idx_p]) /
878-
(a[idx_p] * (Rs[idx_p] * Gsh[idx_p] + 1.)))
869+
a[idx_p] * (Rs[idx_p] * Gsh[idx_p] + 1.)) * (
870+
np.exp((Rs[idx_p] * (IL[idx_p] + I0[idx_p]) + V[idx_p])
871+
/ (a[idx_p] * (Rs[idx_p] * Gsh[idx_p] + 1.))))
879872

880-
# lambertw typically returns complex value with zero imaginary part
881-
# may overflow to np.inf
882-
lambertwterm = lambertw(argW).real
873+
lambertwterm = _lambertw_pvlib(argW)
883874

884875
# Eqn. 2 in Jain and Kapoor, 2004
885876
# I = -V/(Rs + Rsh) - (a/Rs)*lambertwterm + Rsh*(IL + I0)/(Rs + Rsh)
886877
# Recast in terms of Gsh=1/Rsh for better numerical stability.
887878
I[idx_p] = (IL[idx_p] + I0[idx_p] - V[idx_p] * Gsh[idx_p]) / \
888-
(Rs[idx_p] * Gsh[idx_p] + 1.) - (
889-
a[idx_p] / Rs[idx_p]) * lambertwterm
879+
(Rs[idx_p] * Gsh[idx_p] + 1.) \
880+
- (a[idx_p] / Rs[idx_p]) * lambertwterm
890881

891882
if output_is_scalar:
892883
return I.item()
@@ -1031,7 +1022,7 @@ def batzelis(photocurrent, saturation_current, resistance_series,
10311022
voc = a * np.log(Iph / Is)
10321023

10331024
# Eqs 5-8
1034-
w = np.real(lambertw(np.e * Iph / Is))
1025+
w = _lambertw_pvlib(np.e * Iph / Is)
10351026
# vmp = (1 + Rs/Rsh) * a * (w - 1) - Rs * Iph * (1 - 1/w) # not needed
10361027
with np.errstate(divide='ignore', invalid='ignore'): # zero Iph -> zero w
10371028
imp = Iph * (1 - 1/w) - a * (w - 1) / Rsh

0 commit comments

Comments
 (0)