pvlib 0.9.5__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. pvlib/__init__.py +3 -2
  2. pvlib/atmosphere.py +6 -171
  3. pvlib/bifacial/infinite_sheds.py +30 -267
  4. pvlib/bifacial/utils.py +225 -5
  5. pvlib/data/test_psm3_2017.csv +17521 -17521
  6. pvlib/data/test_read_psm3.csv +17522 -17522
  7. pvlib/data/test_read_pvgis_horizon.csv +49 -0
  8. pvlib/data/variables_style_rules.csv +3 -0
  9. pvlib/iam.py +17 -4
  10. pvlib/inverter.py +6 -1
  11. pvlib/iotools/__init__.py +7 -2
  12. pvlib/iotools/acis.py +516 -0
  13. pvlib/iotools/midc.py +4 -4
  14. pvlib/iotools/psm3.py +32 -31
  15. pvlib/iotools/pvgis.py +84 -28
  16. pvlib/iotools/sodapro.py +8 -6
  17. pvlib/iotools/srml.py +121 -18
  18. pvlib/iotools/surfrad.py +2 -2
  19. pvlib/iotools/tmy.py +146 -102
  20. pvlib/irradiance.py +151 -0
  21. pvlib/ivtools/sde.py +11 -7
  22. pvlib/ivtools/sdm.py +16 -10
  23. pvlib/ivtools/utils.py +6 -6
  24. pvlib/location.py +3 -2
  25. pvlib/modelchain.py +67 -70
  26. pvlib/pvsystem.py +160 -532
  27. pvlib/shading.py +41 -0
  28. pvlib/singlediode.py +215 -65
  29. pvlib/soiling.py +3 -3
  30. pvlib/spa.py +327 -368
  31. pvlib/spectrum/__init__.py +8 -2
  32. pvlib/spectrum/mismatch.py +335 -0
  33. pvlib/temperature.py +1 -8
  34. pvlib/tests/bifacial/test_infinite_sheds.py +0 -111
  35. pvlib/tests/bifacial/test_utils.py +101 -4
  36. pvlib/tests/conftest.py +0 -31
  37. pvlib/tests/iotools/test_acis.py +213 -0
  38. pvlib/tests/iotools/test_midc.py +6 -6
  39. pvlib/tests/iotools/test_psm3.py +3 -3
  40. pvlib/tests/iotools/test_pvgis.py +21 -14
  41. pvlib/tests/iotools/test_sodapro.py +1 -1
  42. pvlib/tests/iotools/test_srml.py +71 -6
  43. pvlib/tests/iotools/test_tmy.py +43 -8
  44. pvlib/tests/ivtools/test_sde.py +19 -17
  45. pvlib/tests/ivtools/test_sdm.py +9 -4
  46. pvlib/tests/test_atmosphere.py +6 -62
  47. pvlib/tests/test_iam.py +12 -0
  48. pvlib/tests/test_irradiance.py +40 -2
  49. pvlib/tests/test_location.py +1 -1
  50. pvlib/tests/test_modelchain.py +33 -76
  51. pvlib/tests/test_pvsystem.py +366 -201
  52. pvlib/tests/test_shading.py +28 -0
  53. pvlib/tests/test_singlediode.py +166 -30
  54. pvlib/tests/test_soiling.py +8 -7
  55. pvlib/tests/test_spa.py +6 -7
  56. pvlib/tests/test_spectrum.py +145 -1
  57. pvlib/tests/test_temperature.py +0 -7
  58. pvlib/tests/test_tools.py +25 -0
  59. pvlib/tests/test_tracking.py +0 -149
  60. pvlib/tools.py +26 -1
  61. pvlib/tracking.py +1 -269
  62. {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/METADATA +1 -9
  63. {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/RECORD +67 -68
  64. pvlib/forecast.py +0 -1211
  65. pvlib/iotools/ecmwf_macc.py +0 -312
  66. pvlib/tests/iotools/test_ecmwf_macc.py +0 -162
  67. pvlib/tests/test_forecast.py +0 -228
  68. {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/AUTHORS.md +0 -0
  69. {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/LICENSE +0 -0
  70. {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/WHEEL +0 -0
  71. {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/top_level.txt +0 -0
pvlib/shading.py CHANGED
@@ -8,6 +8,47 @@ import pandas as pd
8
8
  from pvlib.tools import sind, cosd
9
9
 
10
10
 
11
+ def ground_angle(surface_tilt, gcr, slant_height):
12
+ """
13
+ Angle from horizontal of the line from a point on the row slant length
14
+ to the bottom of the facing row.
15
+
16
+ The angles are clockwise from horizontal, rather than the usual
17
+ counterclockwise direction.
18
+
19
+ Parameters
20
+ ----------
21
+ surface_tilt : numeric
22
+ Surface tilt angle in degrees from horizontal, e.g., surface facing up
23
+ = 0, surface facing horizon = 90. [degree]
24
+ gcr : float
25
+ ground coverage ratio, ratio of row slant length to row spacing.
26
+ [unitless]
27
+ slant_height : numeric
28
+ The distance up the module's slant height to evaluate the ground
29
+ angle, as a fraction [0-1] of the module slant height [unitless].
30
+
31
+ Returns
32
+ -------
33
+ psi : numeric
34
+ Angle [degree].
35
+ """
36
+ # : \\ \
37
+ # : \\ \
38
+ # : \\ \
39
+ # : \\ \ facing row
40
+ # : \\.___________\
41
+ # : \ ^*-. psi \
42
+ # : \ x *-. \
43
+ # : \ v *-.\
44
+ # : \<-----P---->\
45
+
46
+ x1 = gcr * slant_height * sind(surface_tilt)
47
+ x2 = gcr * slant_height * cosd(surface_tilt) + 1
48
+ psi = np.arctan2(x1, x2) # do this before rad2deg because it handles 0 / 0
49
+ return np.rad2deg(psi)
50
+
51
+
11
52
  def masking_angle(surface_tilt, gcr, slant_height):
12
53
  """
13
54
  The elevation angle below which diffuse irradiance is blocked.
pvlib/singlediode.py CHANGED
@@ -2,15 +2,17 @@
2
2
  Low-level functions for solving the single diode equation.
3
3
  """
4
4
 
5
- from functools import partial
6
5
  import numpy as np
7
6
  from pvlib.tools import _golden_sect_DataFrame
8
7
 
9
8
  from scipy.optimize import brentq, newton
10
9
  from scipy.special import lambertw
11
10
 
12
- # set keyword arguments for all uses of newton in this module
13
- newton = partial(newton, tol=1e-6, maxiter=100, fprime2=None)
11
+ # newton method default parameters for this module
12
+ NEWTON_DEFAULT_PARAMS = {
13
+ 'tol': 1e-6,
14
+ 'maxiter': 100
15
+ }
14
16
 
15
17
  # intrinsic voltage per cell junction for a:Si, CdTe, Mertens et al.
16
18
  VOLTAGE_BUILTIN = 0.9 # [V]
@@ -206,7 +208,7 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current,
206
208
  resistance_series, resistance_shunt, nNsVth,
207
209
  d2mutau=0, NsVbi=np.Inf, breakdown_factor=0.,
208
210
  breakdown_voltage=-5.5, breakdown_exp=3.28,
209
- method='newton'):
211
+ method='newton', method_kwargs=None):
210
212
  """
211
213
  Find current given any voltage.
212
214
 
@@ -247,22 +249,59 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current,
247
249
  method : str, default 'newton'
248
250
  Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
249
251
  if ``breakdown_factor`` is not 0.
252
+ method_kwargs : dict, optional
253
+ Keyword arguments passed to root finder method. See
254
+ :py:func:`scipy:scipy.optimize.brentq` and
255
+ :py:func:`scipy:scipy.optimize.newton` parameters.
256
+ ``'full_output': True`` is allowed, and ``optimizer_output`` would be
257
+ returned. See examples section.
250
258
 
251
259
  Returns
252
260
  -------
253
261
  current : numeric
254
262
  current (I) at the specified voltage (V). [A]
263
+ optimizer_output : tuple, optional, if specified in ``method_kwargs``
264
+ see root finder documentation for selected method.
265
+ Found root is diode voltage in [1]_.
266
+
267
+ Examples
268
+ --------
269
+ Using the following arguments that may come from any
270
+ `calcparams_.*` function in :py:mod:`pvlib.pvsystem`:
271
+
272
+ >>> args = {'photocurrent': 1., 'saturation_current': 9e-10, 'nNsVth': 4.,
273
+ ... 'resistance_series': 4., 'resistance_shunt': 5000.0}
274
+
275
+ Use default values:
276
+
277
+ >>> i = bishop88_i_from_v(0.0, **args)
278
+
279
+ Specify tolerances and maximum number of iterations:
280
+
281
+ >>> i = bishop88_i_from_v(0.0, **args, method='newton',
282
+ ... method_kwargs={'tol': 1e-3, 'rtol': 1e-3, 'maxiter': 20})
283
+
284
+ Retrieve full output from the root finder:
285
+
286
+ >>> i, method_output = bishop88_i_from_v(0.0, **args, method='newton',
287
+ ... method_kwargs={'full_output': True})
255
288
  """
256
289
  # collect args
257
290
  args = (photocurrent, saturation_current, resistance_series,
258
291
  resistance_shunt, nNsVth, d2mutau, NsVbi,
259
292
  breakdown_factor, breakdown_voltage, breakdown_exp)
293
+ method = method.lower()
294
+
295
+ # method_kwargs create dict if not provided
296
+ # this pattern avoids bugs with Mutable Default Parameters
297
+ if not method_kwargs:
298
+ method_kwargs = {}
260
299
 
261
300
  def fv(x, v, *a):
262
301
  # calculate voltage residual given diode voltage "x"
263
302
  return bishop88(x, *a)[1] - v
264
303
 
265
- if method.lower() == 'brentq':
304
+ if method == 'brentq':
266
305
  # first bound the search using voc
267
306
  voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)
268
307
 
@@ -274,27 +313,37 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current,
274
313
  return brentq(fv, 0.0, voc,
275
314
  args=(v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
276
315
  breakdown_factor, breakdown_voltage,
277
- breakdown_exp))
316
+ breakdown_exp),
317
+ **method_kwargs)
278
318
 
279
319
  vd_from_brent_vectorized = np.vectorize(vd_from_brent)
280
320
  vd = vd_from_brent_vectorized(voc_est, voltage, *args)
281
- elif method.lower() == 'newton':
321
+ elif method == 'newton':
282
322
  # make sure all args are numpy arrays if max size > 1
283
323
  # if voltage is an array, then make a copy to use for initial guess, v0
284
- args, v0 = _prepare_newton_inputs((voltage,), args, voltage)
324
+ args, v0, method_kwargs = \
325
+ _prepare_newton_inputs((voltage,), args, voltage, method_kwargs)
285
326
  vd = newton(func=lambda x, *a: fv(x, voltage, *a), x0=v0,
286
327
  fprime=lambda x, *a: bishop88(x, *a, gradients=True)[4],
287
- args=args)
328
+ args=args,
329
+ **method_kwargs)
288
330
  else:
289
331
  raise NotImplementedError("Method '%s' isn't implemented" % method)
290
- return bishop88(vd, *args)[0]
332
+
333
+ # When 'full_output' parameter is specified, returned 'vd' is a tuple with
334
+ # many elements, where the root is the first one. So we use it to output
335
+ # the bishop88 result and return tuple(scalar, tuple with method results)
336
+ if method_kwargs.get('full_output') is True:
337
+ return (bishop88(vd[0], *args)[0], vd)
338
+ else:
339
+ return bishop88(vd, *args)[0]
291
340
 
292
341
 
293
342
  def bishop88_v_from_i(current, photocurrent, saturation_current,
294
343
  resistance_series, resistance_shunt, nNsVth,
295
344
  d2mutau=0, NsVbi=np.Inf, breakdown_factor=0.,
296
345
  breakdown_voltage=-5.5, breakdown_exp=3.28,
297
- method='newton'):
346
+ method='newton', method_kwargs=None):
298
347
  """
299
348
  Find voltage given any current.
300
349
 
@@ -335,16 +384,54 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
335
384
  method : str, default 'newton'
336
385
  Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
337
386
  if ``breakdown_factor`` is not 0.
387
+ method_kwargs : dict, optional
388
+ Keyword arguments passed to root finder method. See
389
+ :py:func:`scipy:scipy.optimize.brentq` and
390
+ :py:func:`scipy:scipy.optimize.newton` parameters.
391
+ ``'full_output': True`` is allowed, and ``optimizer_output`` would be
392
+ returned. See examples section.
338
393
 
339
394
  Returns
340
395
  -------
341
396
  voltage : numeric
342
397
  voltage (V) at the specified current (I) in volts [V]
398
+ optimizer_output : tuple, optional, if specified in ``method_kwargs``
399
+ see root finder documentation for selected method.
400
+ Found root is diode voltage in [1]_.
401
+
402
+ Examples
403
+ --------
404
+ Using the following arguments that may come from any
405
+ `calcparams_.*` function in :py:mod:`pvlib.pvsystem`:
406
+
407
+ >>> args = {'photocurrent': 1., 'saturation_current': 9e-10, 'nNsVth': 4.,
408
+ ... 'resistance_series': 4., 'resistance_shunt': 5000.0}
409
+
410
+ Use default values:
411
+
412
+ >>> v = bishop88_v_from_i(0.0, **args)
413
+
414
+ Specify tolerances and maximum number of iterations:
415
+
416
+ >>> v = bishop88_v_from_i(0.0, **args, method='newton',
417
+ ... method_kwargs={'tol': 1e-3, 'rtol': 1e-3, 'maxiter': 20})
418
+
419
+ Retrieve full output from the root finder:
420
+
421
+ >>> v, method_output = bishop88_v_from_i(0.0, **args, method='newton',
422
+ ... method_kwargs={'full_output': True})
343
423
  """
344
424
  # collect args
345
425
  args = (photocurrent, saturation_current, resistance_series,
346
426
  resistance_shunt, nNsVth, d2mutau, NsVbi, breakdown_factor,
347
427
  breakdown_voltage, breakdown_exp)
428
+ method = method.lower()
429
+
430
+ # method_kwargs create dict if not provided
431
+ # this pattern avoids bugs with Mutable Default Parameters
432
+ if not method_kwargs:
433
+ method_kwargs = {}
434
+
348
435
  # first bound the search using voc
349
436
  voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)
350
437
 
@@ -352,7 +439,7 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
352
439
  # calculate current residual given diode voltage "x"
353
440
  return bishop88(x, *a)[0] - i
354
441
 
355
- if method.lower() == 'brentq':
442
+ if method == 'brentq':
356
443
  # brentq only works with scalar inputs, so we need a set up function
357
444
  # and np.vectorize to repeatedly call the optimizer with the right
358
445
  # arguments for possible array input
@@ -361,26 +448,36 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
361
448
  return brentq(fi, 0.0, voc,
362
449
  args=(i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
363
450
  breakdown_factor, breakdown_voltage,
364
- breakdown_exp))
451
+ breakdown_exp),
452
+ **method_kwargs)
365
453
 
366
454
  vd_from_brent_vectorized = np.vectorize(vd_from_brent)
367
455
  vd = vd_from_brent_vectorized(voc_est, current, *args)
368
- elif method.lower() == 'newton':
456
+ elif method == 'newton':
369
457
  # make sure all args are numpy arrays if max size > 1
370
458
  # if voc_est is an array, then make a copy to use for initial guess, v0
371
- args, v0 = _prepare_newton_inputs((current,), args, voc_est)
459
+ args, v0, method_kwargs = \
460
+ _prepare_newton_inputs((current,), args, voc_est, method_kwargs)
372
461
  vd = newton(func=lambda x, *a: fi(x, current, *a), x0=v0,
373
462
  fprime=lambda x, *a: bishop88(x, *a, gradients=True)[3],
374
- args=args)
463
+ args=args,
464
+ **method_kwargs)
375
465
  else:
376
466
  raise NotImplementedError("Method '%s' isn't implemented" % method)
377
- return bishop88(vd, *args)[1]
467
+
468
+ # When 'full_output' parameter is specified, returned 'vd' is a tuple with
469
+ # many elements, where the root is the first one. So we use it to output
470
+ # the bishop88 result and return tuple(scalar, tuple with method results)
471
+ if method_kwargs.get('full_output') is True:
472
+ return (bishop88(vd[0], *args)[1], vd)
473
+ else:
474
+ return bishop88(vd, *args)[1]
378
475
 
379
476
 
380
477
  def bishop88_mpp(photocurrent, saturation_current, resistance_series,
381
478
  resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,
382
479
  breakdown_factor=0., breakdown_voltage=-5.5,
383
- breakdown_exp=3.28, method='newton'):
480
+ breakdown_exp=3.28, method='newton', method_kwargs=None):
384
481
  """
385
482
  Find max power point.
386
483
 
@@ -419,43 +516,91 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series,
419
516
  method : str, default 'newton'
420
517
  Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
421
518
  if ``breakdown_factor`` is not 0.
519
+ method_kwargs : dict, optional
520
+ Keyword arguments passed to root finder method. See
521
+ :py:func:`scipy:scipy.optimize.brentq` and
522
+ :py:func:`scipy:scipy.optimize.newton` parameters.
523
+ ``'full_output': True`` is allowed, and ``optimizer_output`` would be
524
+ returned. See examples section.
422
525
 
423
526
  Returns
424
527
  -------
425
528
  tuple
426
529
  max power current ``i_mp`` [A], max power voltage ``v_mp`` [V], and
427
530
  max power ``p_mp`` [W]
531
+ optimizer_output : tuple, optional, if specified in ``method_kwargs``
532
+ see root finder documentation for selected method.
533
+ Found root is diode voltage in [1]_.
534
+
535
+ Examples
536
+ --------
537
+ Using the following arguments that may come from any
538
+ `calcparams_.*` function in :py:mod:`pvlib.pvsystem`:
539
+
540
+ >>> args = {'photocurrent': 1., 'saturation_current': 9e-10, 'nNsVth': 4.,
541
+ ... 'resistance_series': 4., 'resistance_shunt': 5000.0}
542
+
543
+ Use default values:
544
+
545
+ >>> i_mp, v_mp, p_mp = bishop88_mpp(**args)
546
+
547
+ Specify tolerances and maximum number of iterations:
548
+
549
+ >>> i_mp, v_mp, p_mp = bishop88_mpp(**args, method='newton',
550
+ ... method_kwargs={'tol': 1e-3, 'rtol': 1e-3, 'maxiter': 20})
551
+
552
+ Retrieve full output from the root finder:
553
+
554
+ >>> (i_mp, v_mp, p_mp), method_output = bishop88_mpp(**args,
555
+ ... method='newton', method_kwargs={'full_output': True})
428
556
  """
429
557
  # collect args
430
558
  args = (photocurrent, saturation_current, resistance_series,
431
559
  resistance_shunt, nNsVth, d2mutau, NsVbi, breakdown_factor,
432
560
  breakdown_voltage, breakdown_exp)
561
+ method = method.lower()
562
+
563
+ # method_kwargs create dict if not provided
564
+ # this pattern avoids bugs with Mutable Default Parameters
565
+ if not method_kwargs:
566
+ method_kwargs = {}
567
+
433
568
  # first bound the search using voc
434
569
  voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)
435
570
 
436
571
  def fmpp(x, *a):
437
572
  return bishop88(x, *a, gradients=True)[6]
438
573
 
439
- if method.lower() == 'brentq':
574
+ if method == 'brentq':
440
575
  # break out arguments for numpy.vectorize to handle broadcasting
441
576
  vec_fun = np.vectorize(
442
577
  lambda voc, iph, isat, rs, rsh, gamma, d2mutau, NsVbi, vbr_a, vbr,
443
578
  vbr_exp: brentq(fmpp, 0.0, voc,
444
579
  args=(iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
445
- vbr_a, vbr, vbr_exp))
580
+ vbr_a, vbr, vbr_exp),
581
+ **method_kwargs)
446
582
  )
447
583
  vd = vec_fun(voc_est, *args)
448
- elif method.lower() == 'newton':
584
+ elif method == 'newton':
449
585
  # make sure all args are numpy arrays if max size > 1
450
586
  # if voc_est is an array, then make a copy to use for initial guess, v0
451
- args, v0 = _prepare_newton_inputs((), args, voc_est)
587
+ args, v0, method_kwargs = \
588
+ _prepare_newton_inputs((), args, voc_est, method_kwargs)
452
589
  vd = newton(
453
590
  func=fmpp, x0=v0,
454
- fprime=lambda x, *a: bishop88(x, *a, gradients=True)[7], args=args
455
- )
591
+ fprime=lambda x, *a: bishop88(x, *a, gradients=True)[7], args=args,
592
+ **method_kwargs)
456
593
  else:
457
594
  raise NotImplementedError("Method '%s' isn't implemented" % method)
458
- return bishop88(vd, *args)
595
+
596
+ # When 'full_output' parameter is specified, returned 'vd' is a tuple with
597
+ # many elements, where the root is the first one. So we use it to output
598
+ # the bishop88 result and return
599
+ # tuple(tuple with bishop88 solution, tuple with method results)
600
+ if method_kwargs.get('full_output') is True:
601
+ return (bishop88(vd[0], *args), vd)
602
+ else:
603
+ return bishop88(vd, *args)
459
604
 
460
605
 
461
606
  def _get_size_and_shape(args):
@@ -482,7 +627,7 @@ def _get_size_and_shape(args):
482
627
  return size, shape
483
628
 
484
629
 
485
- def _prepare_newton_inputs(i_or_v_tup, args, v0):
630
+ def _prepare_newton_inputs(i_or_v_tup, args, v0, method_kwargs):
486
631
  # broadcast arguments for newton method
487
632
  # the first argument should be a tuple, eg: (i,), (v,) or ()
488
633
  size, shape = _get_size_and_shape(i_or_v_tup + args)
@@ -492,15 +637,20 @@ def _prepare_newton_inputs(i_or_v_tup, args, v0):
492
637
  # copy v0 to a new array and broadcast it to the shape of max size
493
638
  if shape is not None:
494
639
  v0 = np.broadcast_to(v0, shape).copy()
495
- return args, v0
640
+
641
+ # set abs tolerance and maxiter from method_kwargs if not provided
642
+ # apply defaults, but giving priority to user-specified values
643
+ method_kwargs = {**NEWTON_DEFAULT_PARAMS, **method_kwargs}
644
+
645
+ return args, v0, method_kwargs
496
646
 
497
647
 
498
- def _lambertw_v_from_i(resistance_shunt, resistance_series, nNsVth, current,
499
- saturation_current, photocurrent):
648
+ def _lambertw_v_from_i(current, photocurrent, saturation_current,
649
+ resistance_series, resistance_shunt, nNsVth):
500
650
  # Record if inputs were all scalar
501
651
  output_is_scalar = all(map(np.isscalar,
502
- [resistance_shunt, resistance_series, nNsVth,
503
- current, saturation_current, photocurrent]))
652
+ (current, photocurrent, saturation_current,
653
+ resistance_series, resistance_shunt, nNsVth)))
504
654
 
505
655
  # This transforms Gsh=1/Rsh, including ideal Rsh=np.inf into Gsh=0., which
506
656
  # is generally more numerically stable
@@ -509,9 +659,9 @@ def _lambertw_v_from_i(resistance_shunt, resistance_series, nNsVth, current,
509
659
  # Ensure that we are working with read-only views of numpy arrays
510
660
  # Turns Series into arrays so that we don't have to worry about
511
661
  # multidimensional broadcasting failing
512
- Gsh, Rs, a, I, I0, IL = \
513
- np.broadcast_arrays(conductance_shunt, resistance_series, nNsVth,
514
- current, saturation_current, photocurrent)
662
+ I, IL, I0, Rs, Gsh, a = \
663
+ np.broadcast_arrays(current, photocurrent, saturation_current,
664
+ resistance_series, conductance_shunt, nNsVth)
515
665
 
516
666
  # Intitalize output V (I might not be float64)
517
667
  V = np.full_like(I, np.nan, dtype=np.float64)
@@ -572,12 +722,12 @@ def _lambertw_v_from_i(resistance_shunt, resistance_series, nNsVth, current,
572
722
  return V
573
723
 
574
724
 
575
- def _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
576
- saturation_current, photocurrent):
725
+ def _lambertw_i_from_v(voltage, photocurrent, saturation_current,
726
+ resistance_series, resistance_shunt, nNsVth):
577
727
  # Record if inputs were all scalar
578
728
  output_is_scalar = all(map(np.isscalar,
579
- [resistance_shunt, resistance_series, nNsVth,
580
- voltage, saturation_current, photocurrent]))
729
+ (voltage, photocurrent, saturation_current,
730
+ resistance_series, resistance_shunt, nNsVth)))
581
731
 
582
732
  # This transforms Gsh=1/Rsh, including ideal Rsh=np.inf into Gsh=0., which
583
733
  # is generally more numerically stable
@@ -586,9 +736,9 @@ def _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
586
736
  # Ensure that we are working with read-only views of numpy arrays
587
737
  # Turns Series into arrays so that we don't have to worry about
588
738
  # multidimensional broadcasting failing
589
- Gsh, Rs, a, V, I0, IL = \
590
- np.broadcast_arrays(conductance_shunt, resistance_series, nNsVth,
591
- voltage, saturation_current, photocurrent)
739
+ V, IL, I0, Rs, Gsh, a = \
740
+ np.broadcast_arrays(voltage, photocurrent, saturation_current,
741
+ resistance_series, conductance_shunt, nNsVth)
592
742
 
593
743
  # Intitalize output I (V might not be float64)
594
744
  I = np.full_like(V, np.nan, dtype=np.float64) # noqa: E741, N806
@@ -632,36 +782,36 @@ def _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
632
782
 
633
783
  def _lambertw(photocurrent, saturation_current, resistance_series,
634
784
  resistance_shunt, nNsVth, ivcurve_pnts=None):
785
+ # collect args
786
+ params = {'photocurrent': photocurrent,
787
+ 'saturation_current': saturation_current,
788
+ 'resistance_series': resistance_series,
789
+ 'resistance_shunt': resistance_shunt, 'nNsVth': nNsVth}
790
+
635
791
  # Compute short circuit current
636
- i_sc = _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, 0.,
637
- saturation_current, photocurrent)
792
+ i_sc = _lambertw_i_from_v(0., **params)
638
793
 
639
794
  # Compute open circuit voltage
640
- v_oc = _lambertw_v_from_i(resistance_shunt, resistance_series, nNsVth, 0.,
641
- saturation_current, photocurrent)
795
+ v_oc = _lambertw_v_from_i(0., **params)
642
796
 
643
- params = {'r_sh': resistance_shunt,
644
- 'r_s': resistance_series,
645
- 'nNsVth': nNsVth,
646
- 'i_0': saturation_current,
647
- 'i_l': photocurrent}
797
+ # Set small elements <0 in v_oc to 0
798
+ if isinstance(v_oc, np.ndarray):
799
+ v_oc[(v_oc < 0) & (v_oc > -1e-12)] = 0.
800
+ elif isinstance(v_oc, (float, int)):
801
+ if v_oc < 0 and v_oc > -1e-12:
802
+ v_oc = 0.
648
803
 
649
804
  # Find the voltage, v_mp, where the power is maximized.
650
805
  # Start the golden section search at v_oc * 1.14
651
- p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14,
652
- _pwr_optfcn)
806
+ p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14, _pwr_optfcn)
653
807
 
654
808
  # Find Imp using Lambert W
655
- i_mp = _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth,
656
- v_mp, saturation_current, photocurrent)
809
+ i_mp = _lambertw_i_from_v(v_mp, **params)
657
810
 
658
811
  # Find Ix and Ixx using Lambert W
659
- i_x = _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth,
660
- 0.5 * v_oc, saturation_current, photocurrent)
812
+ i_x = _lambertw_i_from_v(0.5 * v_oc, **params)
661
813
 
662
- i_xx = _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth,
663
- 0.5 * (v_oc + v_mp), saturation_current,
664
- photocurrent)
814
+ i_xx = _lambertw_i_from_v(0.5 * (v_oc + v_mp), **params)
665
815
 
666
816
  out = (i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx)
667
817
 
@@ -670,9 +820,7 @@ def _lambertw(photocurrent, saturation_current, resistance_series,
670
820
  ivcurve_v = (np.asarray(v_oc)[..., np.newaxis] *
671
821
  np.linspace(0, 1, ivcurve_pnts))
672
822
 
673
- ivcurve_i = _lambertw_i_from_v(resistance_shunt, resistance_series,
674
- nNsVth, ivcurve_v.T, saturation_current,
675
- photocurrent).T
823
+ ivcurve_i = _lambertw_i_from_v(ivcurve_v.T, **params).T
676
824
 
677
825
  out += (ivcurve_i, ivcurve_v)
678
826
 
@@ -684,7 +832,9 @@ def _pwr_optfcn(df, loc):
684
832
  Function to find power from ``i_from_v``.
685
833
  '''
686
834
 
687
- I = _lambertw_i_from_v(df['r_sh'], df['r_s'], # noqa: E741, N806
688
- df['nNsVth'], df[loc], df['i_0'], df['i_l'])
835
+ current = _lambertw_i_from_v(df[loc], df['photocurrent'],
836
+ df['saturation_current'],
837
+ df['resistance_series'],
838
+ df['resistance_shunt'], df['nNsVth'])
689
839
 
690
- return I * df[loc]
840
+ return current * df[loc]
pvlib/soiling.py CHANGED
@@ -10,7 +10,7 @@ from scipy.special import erf
10
10
  from pvlib.tools import cosd
11
11
 
12
12
 
13
- def hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10,
13
+ def hsu(rainfall, cleaning_threshold, surface_tilt, pm2_5, pm10,
14
14
  depo_veloc=None, rain_accum_period=pd.Timedelta('1h')):
15
15
  """
16
16
  Calculates soiling ratio given particulate and rain data using the
@@ -30,7 +30,7 @@ def hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10,
30
30
  Amount of rain in an accumulation period needed to clean the PV
31
31
  modules. [mm]
32
32
 
33
- tilt : float
33
+ surface_tilt : numeric
34
34
  Tilt of the PV panels from horizontal. [degree]
35
35
 
36
36
  pm2_5 : numeric
@@ -83,7 +83,7 @@ def hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10,
83
83
  horiz_mass_rate = (
84
84
  pm2_5 * depo_veloc['2_5'] + np.maximum(pm10 - pm2_5, 0.)
85
85
  * depo_veloc['10']) * dt_sec
86
- tilted_mass_rate = horiz_mass_rate * cosd(tilt) # assuming no rain
86
+ tilted_mass_rate = horiz_mass_rate * cosd(surface_tilt) # assuming no rain
87
87
 
88
88
  # tms -> tilt_mass_rate
89
89
  tms_cumsum = np.cumsum(tilted_mass_rate * np.ones(rainfall.shape))