@@ -732,9 +732,10 @@ def ashraeiam(aoi, b=0.05):
732
732
physicaliam
733
733
'''
734
734
735
- iam = 1 - b * ((1 / np .cos (np .radians (aoi )) - 1 ))
736
-
737
- iam = np .where (np .abs (aoi ) >= 90 , 0 , iam )
735
+ iam = 1 - b * ((1 / np .cos (np .radians (aoi )) - 1 ))
736
+ aoi_gte_90 = np .full_like (aoi , False , dtype = 'bool' )
737
+ np .greater_equal (np .abs (aoi ), 90 , where = ~ np .isnan (aoi ), out = aoi_gte_90 )
738
+ iam = np .where (aoi_gte_90 , 0 , iam )
738
739
iam = np .maximum (0 , iam )
739
740
740
741
if isinstance (iam , pd .Series ):
@@ -836,16 +837,18 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002):
836
837
# after deducting the reflected portion of each
837
838
iam = ((1 - (rho_para + rho_perp ) / 2 ) / (1 - rho_zero ) * tau / tau_zero )
838
839
839
- # angles near zero produce nan, but iam is defined as one
840
- small_angle = 1e-06
841
- iam = np .where (np .abs (aoi ) < small_angle , 1.0 , iam )
840
+ with np .errstate (invalid = 'ignore' ):
841
+ # angles near zero produce nan, but iam is defined as one
842
+ small_angle = 1e-06
843
+ iam = np .where (np .abs (aoi ) < small_angle , 1.0 , iam )
842
844
843
- # angles at 90 degrees can produce tiny negative values, which should be zero
844
- # this is a result of calculation precision rather than the physical model
845
- iam = np .where (iam < 0 , 0 , iam )
845
+ # angles at 90 degrees can produce tiny negative values,
846
+ # which should be zero. this is a result of calculation precision
847
+ # rather than the physical model
848
+ iam = np .where (iam < 0 , 0 , iam )
846
849
847
- # for light coming from behind the plane, none can enter the module
848
- iam = np .where (aoi > 90 , 0 , iam )
850
+ # for light coming from behind the plane, none can enter the module
851
+ iam = np .where (aoi > 90 , 0 , iam )
849
852
850
853
if isinstance (aoi_input , pd .Series ):
851
854
iam = pd .Series (iam , index = aoi_input .index )
@@ -1296,12 +1299,27 @@ def sapm(effective_irradiance, temp_cell, module):
1296
1299
q = 1.60218e-19 # Elementary charge in units of coulombs
1297
1300
kb = 1.38066e-23 # Boltzmann's constant in units of J/K
1298
1301
1299
- Ee = effective_irradiance
1302
+ # avoid problem with integer input
1303
+ Ee = np .array (effective_irradiance , dtype = 'float64' )
1304
+
1305
+ # set up masking for 0, positive, and nan inputs
1306
+ Ee_gt_0 = np .full_like (Ee , False , dtype = 'bool' )
1307
+ Ee_eq_0 = np .full_like (Ee , False , dtype = 'bool' )
1308
+ notnan = ~ np .isnan (Ee )
1309
+ np .greater (Ee , 0 , where = notnan , out = Ee_gt_0 )
1310
+ np .equal (Ee , 0 , where = notnan , out = Ee_eq_0 )
1300
1311
1301
1312
Bvmpo = module ['Bvmpo' ] + module ['Mbvmp' ]* (1 - Ee )
1302
1313
Bvoco = module ['Bvoco' ] + module ['Mbvoc' ]* (1 - Ee )
1303
1314
delta = module ['N' ] * kb * (temp_cell + 273.15 ) / q
1304
1315
1316
+ # avoid repeated computation
1317
+ logEe = np .full_like (Ee , np .nan )
1318
+ np .log (Ee , where = Ee_gt_0 , out = logEe )
1319
+ logEe = np .where (Ee_eq_0 , - np .inf , logEe )
1320
+ # avoid repeated __getitem__
1321
+ cells_in_series = module ['Cells_in_Series' ]
1322
+
1305
1323
out = OrderedDict ()
1306
1324
1307
1325
out ['i_sc' ] = (
@@ -1312,13 +1330,13 @@ def sapm(effective_irradiance, temp_cell, module):
1312
1330
(1 + module ['Aimp' ]* (temp_cell - T0 )))
1313
1331
1314
1332
out ['v_oc' ] = np .maximum (0 , (
1315
- module ['Voco' ] + module [ 'Cells_in_Series' ] * delta * np . log ( Ee ) +
1333
+ module ['Voco' ] + cells_in_series * delta * logEe +
1316
1334
Bvoco * (temp_cell - T0 )))
1317
1335
1318
1336
out ['v_mp' ] = np .maximum (0 , (
1319
1337
module ['Vmpo' ] +
1320
- module ['C2' ]* module [ 'Cells_in_Series' ] * delta * np . log ( Ee ) +
1321
- module ['C3' ]* module [ 'Cells_in_Series' ] * ((delta * np . log ( Ee ) ) ** 2 ) +
1338
+ module ['C2' ] * cells_in_series * delta * logEe +
1339
+ module ['C3' ] * cells_in_series * ((delta * logEe ) ** 2 ) +
1322
1340
Bvmpo * (temp_cell - T0 )))
1323
1341
1324
1342
out ['p_mp' ] = out ['i_mp' ] * out ['v_mp' ]
@@ -1518,7 +1536,10 @@ def sapm_aoi_loss(aoi, module, upper=None):
1518
1536
1519
1537
aoi_loss = np .polyval (aoi_coeff , aoi )
1520
1538
aoi_loss = np .clip (aoi_loss , 0 , upper )
1521
- aoi_loss = np .where (aoi < 0 , 0 , aoi_loss )
1539
+ # nan tolerant masking
1540
+ aoi_lt_0 = np .full_like (aoi , False , dtype = 'bool' )
1541
+ np .less (aoi , 0 , where = ~ np .isnan (aoi ), out = aoi_lt_0 )
1542
+ aoi_loss = np .where (aoi_lt_0 , 0 , aoi_loss )
1522
1543
1523
1544
if isinstance (aoi , pd .Series ):
1524
1545
aoi_loss = pd .Series (aoi_loss , aoi .index )
@@ -1912,9 +1933,11 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
1912
1933
# Only compute using LambertW if there are cases with Gsh>0
1913
1934
if np .any (idx_p ):
1914
1935
# LambertW argument, cannot be float128, may overflow to np.inf
1915
- argW = I0 [idx_p ] / (Gsh [idx_p ]* a [idx_p ]) * \
1916
- np .exp ((- I [idx_p ] + IL [idx_p ] + I0 [idx_p ]) /
1917
- (Gsh [idx_p ]* a [idx_p ]))
1936
+ # overflow is explicitly handled below, so ignore warnings here
1937
+ with np .errstate (over = 'ignore' ):
1938
+ argW = (I0 [idx_p ] / (Gsh [idx_p ]* a [idx_p ]) *
1939
+ np .exp ((- I [idx_p ] + IL [idx_p ] + I0 [idx_p ]) /
1940
+ (Gsh [idx_p ]* a [idx_p ])))
1918
1941
1919
1942
# lambertw typically returns complex value with zero imaginary part
1920
1943
# may overflow to np.inf
@@ -2274,22 +2297,39 @@ def adrinverter(v_dc, p_dc, inverter, vtol=0.10):
2274
2297
mppt_hi = inverter ['MPPTHi' ]
2275
2298
mppt_low = inverter ['MPPTLow' ]
2276
2299
2277
- v_lim_upper = np .nanmax ([v_max , vdc_max , mppt_hi ])* (1 + vtol )
2278
- v_lim_lower = np .nanmax ([v_min , mppt_low ])* (1 - vtol )
2279
-
2280
- pdc = p_dc / p_nom
2281
- vdc = v_dc / v_nom
2282
- poly = np .array ([pdc ** 0 , pdc , pdc ** 2 , vdc - 1 , pdc * (vdc - 1 ),
2283
- pdc ** 2 * (vdc - 1 ), 1 / vdc - 1 , pdc * (1. / vdc - 1 ),
2284
- pdc ** 2 * (1. / vdc - 1 )])
2300
+ v_lim_upper = np .nanmax ([v_max , vdc_max , mppt_hi ]) * (1 + vtol )
2301
+ v_lim_lower = np .nanmax ([v_min , mppt_low ]) * (1 - vtol )
2302
+
2303
+ pdc = p_dc / p_nom
2304
+ vdc = v_dc / v_nom
2305
+ # zero voltage will lead to division by zero, but since power is
2306
+ # set to night time value later, these errors can be safely ignored
2307
+ with np .errstate (invalid = 'ignore' , divide = 'ignore' ):
2308
+ poly = np .array ([pdc ** 0 , # replace with np.ones_like?
2309
+ pdc ,
2310
+ pdc ** 2 ,
2311
+ vdc - 1 ,
2312
+ pdc * (vdc - 1 ),
2313
+ pdc ** 2 * (vdc - 1 ),
2314
+ 1. / vdc - 1 , # divide by 0
2315
+ pdc * (1. / vdc - 1 ), # invalid 0./0. --> nan
2316
+ pdc ** 2 * (1. / vdc - 1 )]) # divide by 0
2285
2317
p_loss = np .dot (np .array (ce_list ), poly )
2286
2318
ac_power = p_nom * (pdc - p_loss )
2287
- p_nt = - 1 * np .absolute (p_nt )
2319
+ p_nt = - 1 * np .absolute (p_nt )
2320
+
2321
+ # set output to nan where input is outside of limits
2322
+ # errstate silences case where input is nan
2323
+ with np .errstate (invalid = 'ignore' ):
2324
+ invalid = (v_lim_upper < v_dc ) | (v_dc < v_lim_lower )
2325
+ ac_power = np .where (invalid , np .nan , ac_power )
2326
+
2327
+ # set night values
2328
+ ac_power = np .where (vdc == 0 , p_nt , ac_power )
2329
+ ac_power = np .maximum (ac_power , p_nt )
2288
2330
2289
- ac_power = np .where ((v_lim_upper < v_dc ) | (v_dc < v_lim_lower ),
2290
- np .nan , ac_power )
2291
- ac_power = np .where ((ac_power < p_nt ) | (vdc == 0 ), p_nt , ac_power )
2292
- ac_power = np .where (ac_power > pac_max , pac_max , ac_power )
2331
+ # set max ac output
2332
+ ac_power = np .minimum (ac_power , pac_max )
2293
2333
2294
2334
if isinstance (p_dc , pd .Series ):
2295
2335
ac_power = pd .Series (ac_power , index = pdc .index )
0 commit comments