@@ -1853,7 +1853,8 @@ def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
1853
1853
1854
1854
1855
1855
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' ):
1857
1858
"""
1858
1859
Solve the single-diode model to obtain a photovoltaic IV curve.
1859
1860
@@ -2022,53 +2023,16 @@ def singlediode(photocurrent, saturation_current, resistance_series,
2022
2023
calcparams_desoto
2023
2024
pvlib.singlediode_methods.bishop88
2024
2025
"""
2025
- # make IDE happy
2026
- ivcurve_v = NotImplemented
2027
- ivcurve_i = NotImplemented
2028
-
2029
2026
# Calculate points on the IV curve using the LambertW solution to the
2030
2027
# single diode equation
2031
2028
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 ]
2064
2034
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 :]
2072
2036
else :
2073
2037
# Calculate points on the IV curve using either 'newton' or 'brentq'
2074
2038
# methods. Voltages are determined by first solving the single diode
@@ -2165,89 +2129,6 @@ def max_power_point(photocurrent, saturation_current, resistance_series,
2165
2129
return out
2166
2130
2167
2131
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
-
2251
2132
def v_from_i (resistance_shunt , resistance_series , nNsVth , current ,
2252
2133
saturation_current , photocurrent , method = 'lambertw' ):
2253
2134
'''
@@ -2313,84 +2194,10 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
2313
2194
Energy Materials and Solar Cells, 81 (2004) 269-277.
2314
2195
'''
2315
2196
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
+ )
2394
2201
else :
2395
2202
# Calculate points on the IV curve using either 'newton' or 'brentq'
2396
2203
# methods. Voltages are determined by first solving the single diode
@@ -2475,63 +2282,10 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
2475
2282
Energy Materials and Solar Cells, 81 (2004) 269-277.
2476
2283
'''
2477
2284
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
+ )
2535
2289
else :
2536
2290
# Calculate points on the IV curve using either 'newton' or 'brentq'
2537
2291
# methods. Voltages are determined by first solving the single diode
0 commit comments