55import numpy as np
66import pandas as pd
77from pvlib .tools import _golden_sect_DataFrame
8+ from pvlib .ivtools .utils import _lambertw_pvlib , _log_lambertw
89
910from scipy .optimize import brentq , newton
10- from scipy . special import lambertw
11+
1112
1213# newton method default parameters for this module
1314NEWTON_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