Skip to content

Commit f14ba04

Browse files
committed
MAINT: move lambertw methods to singlediode_methods.py
* closes pvlib#497 * remove lines setting ivcurve points to NotImplemented, so that they raise an error if they are not set * replace nested code in if: else condition for lambertw with new private method "_lambertw" in singlediode_methods.py * need to break out returns from private methods, there are so many * move _golden_sect_DataFrame to pvlib.tools * replace nested code in if: else condition for lambertw with new private methods _lambertw_v_from_i and _lambertw_i_from_v in singlediode_methods.py * move _pwr_optfcn to singlediode_methods.py
1 parent 1ad6031 commit f14ba04

File tree

3 files changed

+294
-262
lines changed

3 files changed

+294
-262
lines changed

pvlib/pvsystem.py

+16-262
Original file line numberDiff line numberDiff line change
@@ -1853,7 +1853,8 @@ def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
18531853

18541854

18551855
def singlediode(photocurrent, saturation_current, resistance_series,
1856-
resistance_shunt, nNsVth, ivcurve_pnts=None, method='lambertw'):
1856+
resistance_shunt, nNsVth, ivcurve_pnts=None,
1857+
method='lambertw'):
18571858
"""
18581859
Solve the single-diode model to obtain a photovoltaic IV curve.
18591860
@@ -2022,53 +2023,16 @@ def singlediode(photocurrent, saturation_current, resistance_series,
20222023
calcparams_desoto
20232024
pvlib.singlediode_methods.bishop88
20242025
"""
2025-
# make IDE happy
2026-
ivcurve_v = NotImplemented
2027-
ivcurve_i = NotImplemented
2028-
20292026
# Calculate points on the IV curve using the LambertW solution to the
20302027
# single diode equation
20312028
if method.lower() == 'lambertw':
2032-
# Compute short circuit current
2033-
i_sc = i_from_v(resistance_shunt, resistance_series, nNsVth, 0.,
2034-
saturation_current, photocurrent, method)
2035-
2036-
# Compute open circuit voltage
2037-
v_oc = v_from_i(resistance_shunt, resistance_series, nNsVth, 0.,
2038-
saturation_current, photocurrent, method)
2039-
2040-
params = {'r_sh': resistance_shunt,
2041-
'r_s': resistance_series,
2042-
'nNsVth': nNsVth,
2043-
'i_0': saturation_current,
2044-
'i_l': photocurrent}
2045-
2046-
# Find the voltage, v_mp, where the power is maximized.
2047-
# Start the golden section search at v_oc * 1.14
2048-
p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14,
2049-
_pwr_optfcn)
2050-
2051-
# Find Imp using Lambert W
2052-
i_mp = i_from_v(resistance_shunt, resistance_series, nNsVth, v_mp,
2053-
saturation_current, photocurrent, method)
2054-
2055-
# Find Ix and Ixx using Lambert W
2056-
i_x = i_from_v(resistance_shunt, resistance_series, nNsVth, 0.5 * v_oc,
2057-
saturation_current, photocurrent, method)
2058-
2059-
i_xx = i_from_v(resistance_shunt, resistance_series, nNsVth,
2060-
0.5 * (v_oc + v_mp), saturation_current, photocurrent,
2061-
method)
2062-
2063-
# create ivcurve
2029+
out = singlediode_methods._lambertw(
2030+
photocurrent, saturation_current, resistance_series,
2031+
resistance_shunt, nNsVth, ivcurve_pnts
2032+
)
2033+
i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx = out[:7]
20642034
if ivcurve_pnts:
2065-
ivcurve_v = (np.asarray(v_oc)[..., np.newaxis] *
2066-
np.linspace(0, 1, ivcurve_pnts))
2067-
2068-
ivcurve_i = i_from_v(resistance_shunt, resistance_series, nNsVth,
2069-
ivcurve_v.T, saturation_current, photocurrent,
2070-
method).T
2071-
2035+
ivcurve_i, ivcurve_v = out[7:]
20722036
else:
20732037
# Calculate points on the IV curve using either 'newton' or 'brentq'
20742038
# methods. Voltages are determined by first solving the single diode
@@ -2165,89 +2129,6 @@ def max_power_point(photocurrent, saturation_current, resistance_series,
21652129
return out
21662130

21672131

2168-
# Created April,2014
2169-
# Author: Rob Andrews, Calama Consulting
2170-
2171-
def _golden_sect_DataFrame(params, VL, VH, func):
2172-
"""
2173-
Vectorized golden section search for finding MPP from a dataframe
2174-
timeseries.
2175-
2176-
Parameters
2177-
----------
2178-
params : dict
2179-
Dictionary containing scalars or arrays
2180-
of inputs to the function to be optimized.
2181-
Each row should represent an independent optimization.
2182-
2183-
VL: float
2184-
Lower bound of the optimization
2185-
2186-
VH: float
2187-
Upper bound of the optimization
2188-
2189-
func: function
2190-
Function to be optimized must be in the form f(array-like, x)
2191-
2192-
Returns
2193-
-------
2194-
func(df,'V1') : DataFrame
2195-
function evaluated at the optimal point
2196-
2197-
df['V1']: Dataframe
2198-
Dataframe of optimal points
2199-
2200-
Notes
2201-
-----
2202-
This function will find the MAXIMUM of a function
2203-
"""
2204-
2205-
df = params
2206-
df['VH'] = VH
2207-
df['VL'] = VL
2208-
2209-
err = df['VH'] - df['VL']
2210-
errflag = True
2211-
iterations = 0
2212-
2213-
while errflag:
2214-
2215-
phi = (np.sqrt(5)-1)/2*(df['VH']-df['VL'])
2216-
df['V1'] = df['VL'] + phi
2217-
df['V2'] = df['VH'] - phi
2218-
2219-
df['f1'] = func(df, 'V1')
2220-
df['f2'] = func(df, 'V2')
2221-
df['SW_Flag'] = df['f1'] > df['f2']
2222-
2223-
df['VL'] = df['V2']*df['SW_Flag'] + df['VL']*(~df['SW_Flag'])
2224-
df['VH'] = df['V1']*~df['SW_Flag'] + df['VH']*(df['SW_Flag'])
2225-
2226-
err = df['V1'] - df['V2']
2227-
try:
2228-
errflag = (abs(err) > .01).any()
2229-
except ValueError:
2230-
errflag = (abs(err) > .01)
2231-
2232-
iterations += 1
2233-
2234-
if iterations > 50:
2235-
raise Exception("EXCEPTION:iterations exceeded maximum (50)")
2236-
2237-
return func(df, 'V1'), df['V1']
2238-
2239-
2240-
def _pwr_optfcn(df, loc):
2241-
'''
2242-
Function to find power from ``i_from_v``.
2243-
'''
2244-
2245-
I = i_from_v(df['r_sh'], df['r_s'], df['nNsVth'], df[loc], df['i_0'],
2246-
df['i_l'], method='lambertw')
2247-
2248-
return I * df[loc]
2249-
2250-
22512132
def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
22522133
saturation_current, photocurrent, method='lambertw'):
22532134
'''
@@ -2313,84 +2194,10 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
23132194
Energy Materials and Solar Cells, 81 (2004) 269-277.
23142195
'''
23152196
if method.lower() == 'lambertw':
2316-
try:
2317-
from scipy.special import lambertw
2318-
except ImportError:
2319-
raise ImportError('This function requires scipy')
2320-
2321-
# Record if inputs were all scalar
2322-
output_is_scalar = all(map(np.isscalar,
2323-
[resistance_shunt, resistance_series, nNsVth,
2324-
current, saturation_current, photocurrent]))
2325-
2326-
# This transforms Gsh=1/Rsh, including ideal Rsh=np.inf into Gsh=0., which
2327-
# is generally more numerically stable
2328-
conductance_shunt = 1./resistance_shunt
2329-
2330-
# Ensure that we are working with read-only views of numpy arrays
2331-
# Turns Series into arrays so that we don't have to worry about
2332-
# multidimensional broadcasting failing
2333-
Gsh, Rs, a, I, I0, IL = \
2334-
np.broadcast_arrays(conductance_shunt, resistance_series, nNsVth,
2335-
current, saturation_current, photocurrent)
2336-
2337-
# Intitalize output V (I might not be float64)
2338-
V = np.full_like(I, np.nan, dtype=np.float64)
2339-
2340-
# Determine indices where 0 < Gsh requires implicit model solution
2341-
idx_p = 0. < Gsh
2342-
2343-
# Determine indices where 0 = Gsh allows explicit model solution
2344-
idx_z = 0. == Gsh
2345-
2346-
# Explicit solutions where Gsh=0
2347-
if np.any(idx_z):
2348-
V[idx_z] = a[idx_z]*np.log1p((IL[idx_z] - I[idx_z])/I0[idx_z]) - \
2349-
I[idx_z]*Rs[idx_z]
2350-
2351-
# Only compute using LambertW if there are cases with Gsh>0
2352-
if np.any(idx_p):
2353-
# LambertW argument, cannot be float128, may overflow to np.inf
2354-
# overflow is explicitly handled below, so ignore warnings here
2355-
with np.errstate(over='ignore'):
2356-
argW = (I0[idx_p] / (Gsh[idx_p]*a[idx_p]) *
2357-
np.exp((-I[idx_p] + IL[idx_p] + I0[idx_p]) /
2358-
(Gsh[idx_p]*a[idx_p])))
2359-
2360-
# lambertw typically returns complex value with zero imaginary part
2361-
# may overflow to np.inf
2362-
lambertwterm = lambertw(argW).real
2363-
2364-
# Record indices where lambertw input overflowed output
2365-
idx_inf = np.logical_not(np.isfinite(lambertwterm))
2366-
2367-
# Only re-compute LambertW if it overflowed
2368-
if np.any(idx_inf):
2369-
# Calculate using log(argW) in case argW is really big
2370-
logargW = (np.log(I0[idx_p]) - np.log(Gsh[idx_p]) -
2371-
np.log(a[idx_p]) +
2372-
(-I[idx_p] + IL[idx_p] + I0[idx_p]) /
2373-
(Gsh[idx_p] * a[idx_p]))[idx_inf]
2374-
2375-
# Three iterations of Newton-Raphson method to solve
2376-
# w+log(w)=logargW. The initial guess is w=logargW. Where direct
2377-
# evaluation (above) results in NaN from overflow, 3 iterations
2378-
# of Newton's method gives approximately 8 digits of precision.
2379-
w = logargW
2380-
for _ in range(0, 3):
2381-
w = w * (1. - np.log(w) + logargW) / (1. + w)
2382-
lambertwterm[idx_inf] = w
2383-
2384-
# Eqn. 3 in Jain and Kapoor, 2004
2385-
# V = -I*(Rs + Rsh) + IL*Rsh - a*lambertwterm + I0*Rsh
2386-
# Recast in terms of Gsh=1/Rsh for better numerical stability.
2387-
V[idx_p] = (IL[idx_p] + I0[idx_p] - I[idx_p])/Gsh[idx_p] - \
2388-
I[idx_p]*Rs[idx_p] - a[idx_p]*lambertwterm
2389-
2390-
if output_is_scalar:
2391-
return np.asscalar(V)
2392-
else:
2393-
return V
2197+
return singlediode_methods._lambertw_v_from_i(
2198+
resistance_shunt, resistance_series, nNsVth, current,
2199+
saturation_current, photocurrent
2200+
)
23942201
else:
23952202
# Calculate points on the IV curve using either 'newton' or 'brentq'
23962203
# methods. Voltages are determined by first solving the single diode
@@ -2475,63 +2282,10 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
24752282
Energy Materials and Solar Cells, 81 (2004) 269-277.
24762283
'''
24772284
if method.lower() == 'lambertw':
2478-
try:
2479-
from scipy.special import lambertw
2480-
except ImportError:
2481-
raise ImportError('This function requires scipy')
2482-
2483-
# Record if inputs were all scalar
2484-
output_is_scalar = all(map(np.isscalar,
2485-
[resistance_shunt, resistance_series, nNsVth,
2486-
voltage, saturation_current, photocurrent]))
2487-
2488-
# This transforms Gsh=1/Rsh, including ideal Rsh=np.inf into Gsh=0., which
2489-
# is generally more numerically stable
2490-
conductance_shunt = 1./resistance_shunt
2491-
2492-
# Ensure that we are working with read-only views of numpy arrays
2493-
# Turns Series into arrays so that we don't have to worry about
2494-
# multidimensional broadcasting failing
2495-
Gsh, Rs, a, V, I0, IL = \
2496-
np.broadcast_arrays(conductance_shunt, resistance_series, nNsVth,
2497-
voltage, saturation_current, photocurrent)
2498-
2499-
# Intitalize output I (V might not be float64)
2500-
I = np.full_like(V, np.nan, dtype=np.float64)
2501-
2502-
# Determine indices where 0 < Rs requires implicit model solution
2503-
idx_p = 0. < Rs
2504-
2505-
# Determine indices where 0 = Rs allows explicit model solution
2506-
idx_z = 0. == Rs
2507-
2508-
# Explicit solutions where Rs=0
2509-
if np.any(idx_z):
2510-
I[idx_z] = IL[idx_z] - I0[idx_z]*np.expm1(V[idx_z]/a[idx_z]) - \
2511-
Gsh[idx_z]*V[idx_z]
2512-
2513-
# Only compute using LambertW if there are cases with Rs>0
2514-
# Does NOT handle possibility of overflow, github issue 298
2515-
if np.any(idx_p):
2516-
# LambertW argument, cannot be float128, may overflow to np.inf
2517-
argW = Rs[idx_p]*I0[idx_p]/(a[idx_p]*(Rs[idx_p]*Gsh[idx_p] + 1.)) * \
2518-
np.exp((Rs[idx_p]*(IL[idx_p] + I0[idx_p]) + V[idx_p]) /
2519-
(a[idx_p]*(Rs[idx_p]*Gsh[idx_p] + 1.)))
2520-
2521-
# lambertw typically returns complex value with zero imaginary part
2522-
# may overflow to np.inf
2523-
lambertwterm = lambertw(argW).real
2524-
2525-
# Eqn. 2 in Jain and Kapoor, 2004
2526-
# I = -V/(Rs + Rsh) - (a/Rs)*lambertwterm + Rsh*(IL + I0)/(Rs + Rsh)
2527-
# Recast in terms of Gsh=1/Rsh for better numerical stability.
2528-
I[idx_p] = (IL[idx_p] + I0[idx_p] - V[idx_p]*Gsh[idx_p]) / \
2529-
(Rs[idx_p]*Gsh[idx_p] + 1.) - (a[idx_p]/Rs[idx_p])*lambertwterm
2530-
2531-
if output_is_scalar:
2532-
return np.asscalar(I)
2533-
else:
2534-
return I
2285+
return singlediode_methods._lambertw_i_from_v(
2286+
resistance_shunt, resistance_series, nNsVth, voltage,
2287+
saturation_current, photocurrent
2288+
)
25352289
else:
25362290
# Calculate points on the IV curve using either 'newton' or 'brentq'
25372291
# methods. Voltages are determined by first solving the single diode

0 commit comments

Comments
 (0)