pvlib 0.11.0a1__py3-none-any.whl → 0.11.2__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 (62) hide show
  1. pvlib/_deprecation.py +73 -0
  2. pvlib/atmosphere.py +236 -1
  3. pvlib/bifacial/__init__.py +4 -4
  4. pvlib/bifacial/loss_models.py +163 -0
  5. pvlib/clearsky.py +53 -51
  6. pvlib/data/pvgis_tmy_meta.json +32 -93
  7. pvlib/data/pvgis_tmy_test.csv +8761 -0
  8. pvlib/data/tmy_45.000_8.000_2005_2023.csv +8789 -0
  9. pvlib/data/tmy_45.000_8.000_2005_2023.epw +8768 -0
  10. pvlib/data/tmy_45.000_8.000_2005_2023.json +1 -0
  11. pvlib/data/tmy_45.000_8.000_2005_2023.txt +8761 -0
  12. pvlib/data/tmy_45.000_8.000_userhorizon.json +1 -1
  13. pvlib/iam.py +4 -4
  14. pvlib/iotools/midc.py +1 -1
  15. pvlib/iotools/pvgis.py +39 -13
  16. pvlib/irradiance.py +237 -173
  17. pvlib/ivtools/sdm.py +75 -52
  18. pvlib/location.py +5 -5
  19. pvlib/modelchain.py +1 -1
  20. pvlib/pvsystem.py +134 -86
  21. pvlib/shading.py +8 -8
  22. pvlib/singlediode.py +1 -1
  23. pvlib/solarposition.py +101 -80
  24. pvlib/spa.py +28 -24
  25. pvlib/spectrum/__init__.py +9 -4
  26. pvlib/spectrum/irradiance.py +273 -0
  27. pvlib/spectrum/mismatch.py +118 -508
  28. pvlib/spectrum/response.py +280 -0
  29. pvlib/spectrum/spectrl2.py +18 -17
  30. pvlib/temperature.py +49 -3
  31. pvlib/tests/bifacial/test_losses_models.py +54 -0
  32. pvlib/tests/iotools/test_pvgis.py +58 -12
  33. pvlib/tests/ivtools/test_sdm.py +23 -1
  34. pvlib/tests/spectrum/__init__.py +0 -0
  35. pvlib/tests/spectrum/conftest.py +40 -0
  36. pvlib/tests/spectrum/test_irradiance.py +138 -0
  37. pvlib/tests/{test_spectrum.py → spectrum/test_mismatch.py} +32 -306
  38. pvlib/tests/spectrum/test_response.py +124 -0
  39. pvlib/tests/spectrum/test_spectrl2.py +72 -0
  40. pvlib/tests/test__deprecation.py +97 -0
  41. pvlib/tests/test_atmosphere.py +218 -0
  42. pvlib/tests/test_clearsky.py +44 -26
  43. pvlib/tests/test_conftest.py +0 -44
  44. pvlib/tests/test_irradiance.py +62 -16
  45. pvlib/tests/test_pvsystem.py +17 -1
  46. pvlib/tests/test_solarposition.py +117 -36
  47. pvlib/tests/test_spa.py +30 -1
  48. pvlib/tools.py +26 -2
  49. pvlib/tracking.py +53 -47
  50. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/METADATA +34 -31
  51. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/RECORD +55 -47
  52. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/WHEEL +1 -1
  53. pvlib/data/aod550_tcwv_20121101_test.nc +0 -0
  54. pvlib/data/pvgis_tmy_test.dat +0 -8761
  55. pvlib/data/tmy_45.000_8.000_2005_2016.csv +0 -8789
  56. pvlib/data/tmy_45.000_8.000_2005_2016.epw +0 -8768
  57. pvlib/data/tmy_45.000_8.000_2005_2016.json +0 -1
  58. pvlib/data/tmy_45.000_8.000_2005_2016.txt +0 -8761
  59. pvlib/data/variables_style_rules.csv +0 -55
  60. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/AUTHORS.md +0 -0
  61. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/LICENSE +0 -0
  62. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/top_level.txt +0 -0
pvlib/solarposition.py CHANGED
@@ -22,16 +22,15 @@ import numpy as np
22
22
  import pandas as pd
23
23
  import scipy.optimize as so
24
24
  import warnings
25
- import datetime
26
25
 
27
- from pvlib import atmosphere
26
+ from pvlib import atmosphere, tools
28
27
  from pvlib.tools import datetime_to_djd, djd_to_datetime
29
28
 
30
29
 
31
30
  def get_solarposition(time, latitude, longitude,
32
31
  altitude=None, pressure=None,
33
32
  method='nrel_numpy',
34
- temperature=12, **kwargs):
33
+ temperature=12.0, **kwargs):
35
34
  """
36
35
  A convenience wrapper for the solar position calculators.
37
36
 
@@ -126,8 +125,8 @@ def get_solarposition(time, latitude, longitude,
126
125
  return ephem_df
127
126
 
128
127
 
129
- def spa_c(time, latitude, longitude, pressure=101325, altitude=0,
130
- temperature=12, delta_t=67.0,
128
+ def spa_c(time, latitude, longitude, pressure=101325., altitude=0.,
129
+ temperature=12., delta_t=67.0,
131
130
  raw_spa_output=False):
132
131
  r"""
133
132
  Calculate the solar position using the C implementation of the NREL
@@ -150,11 +149,11 @@ def spa_c(time, latitude, longitude, pressure=101325, altitude=0,
150
149
  longitude : float
151
150
  Longitude in decimal degrees. Positive east of prime meridian,
152
151
  negative to west.
153
- pressure : float, default 101325
152
+ pressure : float, default 101325.0
154
153
  Pressure in Pascals
155
- altitude : float, default 0
154
+ altitude : float, default 0.0
156
155
  Height above sea level. [m]
157
- temperature : float, default 12
156
+ temperature : float, default 12.0
158
157
  Temperature in C
159
158
  delta_t : float, default 67.0
160
159
  Difference between terrestrial time and UT1.
@@ -200,11 +199,7 @@ def spa_c(time, latitude, longitude, pressure=101325, altitude=0,
200
199
  raise ImportError('Could not import built-in SPA calculator. ' +
201
200
  'You may need to recompile the SPA code.')
202
201
 
203
- # if localized, convert to UTC. otherwise, assume UTC.
204
- try:
205
- time_utc = time.tz_convert('UTC')
206
- except TypeError:
207
- time_utc = time
202
+ time_utc = tools._pandas_to_utc(time)
208
203
 
209
204
  spa_out = []
210
205
 
@@ -284,7 +279,7 @@ def _datetime_to_unixtime(dtindex):
284
279
 
285
280
 
286
281
  def spa_python(time, latitude, longitude,
287
- altitude=0, pressure=101325, temperature=12, delta_t=67.0,
282
+ altitude=0., pressure=101325., temperature=12., delta_t=67.0,
288
283
  atmos_refract=None, how='numpy', numthreads=4):
289
284
  """
290
285
  Calculate the solar position using a python implementation of the
@@ -307,19 +302,17 @@ def spa_python(time, latitude, longitude,
307
302
  longitude : float
308
303
  Longitude in decimal degrees. Positive east of prime meridian,
309
304
  negative to west.
310
- altitude : float, default 0
305
+ altitude : float, default 0.0
311
306
  Distance above sea level.
312
- pressure : int or float, optional, default 101325
307
+ pressure : int or float, optional, default 101325.0
313
308
  avg. yearly air pressure in Pascals.
314
- temperature : int or float, optional, default 12
309
+ temperature : int or float, optional, default 12.0
315
310
  avg. yearly air temperature in degrees C.
316
- delta_t : float, optional, default 67.0
311
+ delta_t : float or array, optional, default 67.0
317
312
  Difference between terrestrial time and UT1.
318
313
  If delta_t is None, uses spa.calculate_deltat
319
314
  using time.year and time.month from pandas.DatetimeIndex.
320
315
  For most simulations the default delta_t is sufficient.
321
- *Note: delta_t = None will break code using nrel_numba,
322
- this will be fixed in a future version.*
323
316
  The USNO has historical and forecasted delta_t [3]_.
324
317
  atmos_refrac : float, optional
325
318
  The approximate atmospheric refraction (in degrees)
@@ -379,7 +372,9 @@ def spa_python(time, latitude, longitude,
379
372
 
380
373
  spa = _spa_python_import(how)
381
374
 
382
- delta_t = delta_t or spa.calculate_deltat(time.year, time.month)
375
+ if delta_t is None:
376
+ time_utc = tools._pandas_to_utc(time)
377
+ delta_t = spa.calculate_deltat(time_utc.year, time_utc.month)
383
378
 
384
379
  app_zenith, zenith, app_elevation, elevation, azimuth, eot = \
385
380
  spa.solar_position(unixtime, lat, lon, elev, pressure, temperature,
@@ -419,13 +414,11 @@ def sun_rise_set_transit_spa(times, latitude, longitude, how='numpy',
419
414
  Options are 'numpy' or 'numba'. If numba >= 0.17.0
420
415
  is installed, how='numba' will compile the spa functions
421
416
  to machine code and run them multithreaded.
422
- delta_t : float, optional, default 67.0
417
+ delta_t : float or array, optional, default 67.0
423
418
  Difference between terrestrial time and UT1.
424
419
  If delta_t is None, uses spa.calculate_deltat
425
420
  using times.year and times.month from pandas.DatetimeIndex.
426
421
  For most simulations the default delta_t is sufficient.
427
- *Note: delta_t = None will break code using nrel_numba,
428
- this will be fixed in a future version.*
429
422
  numthreads : int, optional, default 4
430
423
  Number of threads to use if how == 'numba'.
431
424
 
@@ -453,12 +446,13 @@ def sun_rise_set_transit_spa(times, latitude, longitude, how='numpy',
453
446
  raise ValueError('times must be localized')
454
447
 
455
448
  # must convert to midnight UTC on day of interest
456
- utcday = pd.DatetimeIndex(times.date).tz_localize('UTC')
457
- unixtime = _datetime_to_unixtime(utcday)
449
+ times_utc = times.tz_convert('UTC')
450
+ unixtime = _datetime_to_unixtime(times_utc.normalize())
458
451
 
459
452
  spa = _spa_python_import(how)
460
453
 
461
- delta_t = delta_t or spa.calculate_deltat(times.year, times.month)
454
+ if delta_t is None:
455
+ delta_t = spa.calculate_deltat(times_utc.year, times_utc.month)
462
456
 
463
457
  transit, sunrise, sunset = spa.transit_sunrise_sunset(
464
458
  unixtime, lat, lon, delta_t, numthreads)
@@ -513,9 +507,9 @@ def _ephem_setup(latitude, longitude, altitude, pressure, temperature,
513
507
 
514
508
  def sun_rise_set_transit_ephem(times, latitude, longitude,
515
509
  next_or_previous='next',
516
- altitude=0,
517
- pressure=101325,
518
- temperature=12, horizon='0:00'):
510
+ altitude=0.,
511
+ pressure=101325.,
512
+ temperature=12., horizon='0:00'):
519
513
  """
520
514
  Calculate the next sunrise and sunset times using the PyEphem package.
521
515
 
@@ -529,11 +523,11 @@ def sun_rise_set_transit_ephem(times, latitude, longitude,
529
523
  Longitude in degrees, positive east of prime meridian, negative to west
530
524
  next_or_previous : str
531
525
  'next' or 'previous' sunrise and sunset relative to time
532
- altitude : float, default 0
526
+ altitude : float, default 0.0
533
527
  distance above sea level in meters.
534
- pressure : int or float, optional, default 101325
528
+ pressure : int or float, optional, default 101325.0
535
529
  air pressure in Pascals.
536
- temperature : int or float, optional, default 12
530
+ temperature : int or float, optional, default 12.0
537
531
  air temperature in degrees C.
538
532
  horizon : string, format +/-X:YY
539
533
  arc degrees:arc minutes from geometrical horizon for sunrise and
@@ -582,12 +576,11 @@ def sun_rise_set_transit_ephem(times, latitude, longitude,
582
576
  sunrise = []
583
577
  sunset = []
584
578
  trans = []
585
- for thetime in times:
586
- thetime = thetime.to_pydatetime()
579
+ for thetime in tools._pandas_to_utc(times):
587
580
  # older versions of pyephem ignore timezone when converting to its
588
581
  # internal datetime format, so convert to UTC here to support
589
582
  # all versions. GH #1449
590
- obs.date = ephem.Date(thetime.astimezone(datetime.timezone.utc))
583
+ obs.date = ephem.Date(thetime)
591
584
  sunrise.append(_ephem_to_timezone(rising(sun), tzinfo))
592
585
  sunset.append(_ephem_to_timezone(setting(sun), tzinfo))
593
586
  trans.append(_ephem_to_timezone(transit(sun), tzinfo))
@@ -597,8 +590,8 @@ def sun_rise_set_transit_ephem(times, latitude, longitude,
597
590
  'transit': trans})
598
591
 
599
592
 
600
- def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
601
- temperature=12, horizon='+0:00'):
593
+ def pyephem(time, latitude, longitude, altitude=0., pressure=101325.,
594
+ temperature=12., horizon='+0:00'):
602
595
  """
603
596
  Calculate the solar position using the PyEphem package.
604
597
 
@@ -612,11 +605,11 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
612
605
  longitude : float
613
606
  Longitude in decimal degrees. Positive east of prime meridian,
614
607
  negative to west.
615
- altitude : float, default 0
608
+ altitude : float, default 0.0
616
609
  Height above sea level in meters. [m]
617
- pressure : int or float, optional, default 101325
610
+ pressure : int or float, optional, default 101325.0
618
611
  air pressure in Pascals.
619
- temperature : int or float, optional, default 12
612
+ temperature : int or float, optional, default 12.0
620
613
  air temperature in degrees C.
621
614
  horizon : string, optional, default '+0:00'
622
615
  arc degrees:arc minutes from geometrical horizon for sunrise and
@@ -645,11 +638,7 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
645
638
  except ImportError:
646
639
  raise ImportError('PyEphem must be installed')
647
640
 
648
- # if localized, convert to UTC. otherwise, assume UTC.
649
- try:
650
- time_utc = time.tz_convert('UTC')
651
- except TypeError:
652
- time_utc = time
641
+ time_utc = tools._pandas_to_utc(time)
653
642
 
654
643
  sun_coords = pd.DataFrame(index=time)
655
644
 
@@ -690,7 +679,7 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
690
679
  return sun_coords
691
680
 
692
681
 
693
- def ephemeris(time, latitude, longitude, pressure=101325, temperature=12):
682
+ def ephemeris(time, latitude, longitude, pressure=101325.0, temperature=12.0):
694
683
  """
695
684
  Python-native solar position calculator.
696
685
  The accuracy of this code is not guaranteed.
@@ -706,9 +695,9 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12):
706
695
  longitude : float
707
696
  Longitude in decimal degrees. Positive east of prime meridian,
708
697
  negative to west.
709
- pressure : float or Series, default 101325
698
+ pressure : float or Series, default 101325.0
710
699
  Ambient pressure (Pascals)
711
- temperature : float or Series, default 12
700
+ temperature : float or Series, default 12.0
712
701
  Ambient temperature (C)
713
702
 
714
703
  Returns
@@ -718,11 +707,11 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12):
718
707
 
719
708
  * apparent_elevation : apparent sun elevation accounting for
720
709
  atmospheric refraction.
710
+ This is the complement of the apparent zenith angle.
721
711
  * elevation : actual elevation (not accounting for refraction)
722
712
  of the sun in decimal degrees, 0 = on horizon.
723
713
  The complement of the zenith angle.
724
714
  * azimuth : Azimuth of the sun in decimal degrees East of North.
725
- This is the complement of the apparent zenith angle.
726
715
  * apparent_zenith : apparent sun zenith accounting for atmospheric
727
716
  refraction.
728
717
  * zenith : Solar zenith angle
@@ -766,11 +755,7 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12):
766
755
  # the SPA algorithm needs time to be expressed in terms of
767
756
  # decimal UTC hours of the day of the year.
768
757
 
769
- # if localized, convert to UTC. otherwise, assume UTC.
770
- try:
771
- time_utc = time.tz_convert('UTC')
772
- except TypeError:
773
- time_utc = time
758
+ time_utc = tools._pandas_to_utc(time)
774
759
 
775
760
  # strip out the day of the year and calculate the decimal hour
776
761
  DayOfYear = time_utc.dayofyear
@@ -871,8 +856,8 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12):
871
856
 
872
857
 
873
858
  def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value,
874
- altitude=0, pressure=101325, temperature=12, horizon='+0:00',
875
- xtol=1.0e-12):
859
+ altitude=0.0, pressure=101325.0, temperature=12.0,
860
+ horizon='+0:00', xtol=1.0e-12):
876
861
  """
877
862
  Calculate the time between lower_bound and upper_bound
878
863
  where the attribute is equal to value. Uses PyEphem for
@@ -894,12 +879,12 @@ def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value,
894
879
  and 'az' (which must be given in radians).
895
880
  value : int or float
896
881
  The value of the attribute to solve for
897
- altitude : float, default 0
882
+ altitude : float, default 0.0
898
883
  Distance above sea level.
899
- pressure : int or float, optional, default 101325
884
+ pressure : int or float, optional, default 101325.0
900
885
  Air pressure in Pascals. Set to 0 for no
901
886
  atmospheric correction.
902
- temperature : int or float, optional, default 12
887
+ temperature : int or float, optional, default 12.0
903
888
  Air temperature in degrees C.
904
889
  horizon : string, optional, default '+0:00'
905
890
  arc degrees:arc minutes from geometrical horizon for sunrise and
@@ -957,7 +942,10 @@ def pyephem_earthsun_distance(time):
957
942
 
958
943
  sun = ephem.Sun()
959
944
  earthsun = []
960
- for thetime in time:
945
+ for thetime in tools._pandas_to_utc(time):
946
+ # older versions of pyephem ignore timezone when converting to its
947
+ # internal datetime format, so convert to UTC here to support
948
+ # all versions. GH #1449
961
949
  sun.compute(ephem.Date(thetime))
962
950
  earthsun.append(sun.earth_distance)
963
951
 
@@ -981,13 +969,11 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=67.0, numthreads=4):
981
969
  is installed, how='numba' will compile the spa functions
982
970
  to machine code and run them multithreaded.
983
971
 
984
- delta_t : float, optional, default 67.0
972
+ delta_t : float or array, optional, default 67.0
985
973
  Difference between terrestrial time and UT1.
986
974
  If delta_t is None, uses spa.calculate_deltat
987
975
  using time.year and time.month from pandas.DatetimeIndex.
988
976
  For most simulations the default delta_t is sufficient.
989
- *Note: delta_t = None will break code using nrel_numba,
990
- this will be fixed in a future version.*
991
977
 
992
978
  numthreads : int, optional, default 4
993
979
  Number of threads to use if how == 'numba'.
@@ -1014,7 +1000,9 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=67.0, numthreads=4):
1014
1000
 
1015
1001
  spa = _spa_python_import(how)
1016
1002
 
1017
- delta_t = delta_t or spa.calculate_deltat(time.year, time.month)
1003
+ if delta_t is None:
1004
+ time_utc = tools._pandas_to_utc(time)
1005
+ delta_t = spa.calculate_deltat(time_utc.year, time_utc.month)
1018
1006
 
1019
1007
  dist = spa.earthsun_distance(unixtime, delta_t, numthreads)
1020
1008
 
@@ -1361,6 +1349,12 @@ def hour_angle(times, longitude, equation_of_time):
1361
1349
  times : :class:`pandas.DatetimeIndex`
1362
1350
  Corresponding timestamps, must be localized to the timezone for the
1363
1351
  ``longitude``.
1352
+
1353
+ A `pytz.exceptions.AmbiguousTimeError` will be raised if any of the
1354
+ given times are on a day when the local daylight savings transition
1355
+ happens at midnight. If you're working with such a timezone,
1356
+ consider converting to a non-DST timezone (e.g. GMT-4) before
1357
+ calling this function.
1364
1358
  longitude : numeric
1365
1359
  Longitude in degrees
1366
1360
  equation_of_time : numeric
@@ -1387,22 +1381,26 @@ def hour_angle(times, longitude, equation_of_time):
1387
1381
  equation_of_time_spencer71
1388
1382
  equation_of_time_pvcdrom
1389
1383
  """
1384
+
1385
+ # times must be localized
1386
+ if not times.tz:
1387
+ raise ValueError('times must be localized')
1388
+
1390
1389
  # hours - timezone = (times - normalized_times) - (naive_times - times)
1391
- if times.tz is None:
1392
- times = times.tz_localize('utc')
1393
1390
  tzs = np.array([ts.utcoffset().total_seconds() for ts in times]) / 3600
1394
1391
 
1395
- hrs_minus_tzs = (times - times.normalize()) / pd.Timedelta('1h') - tzs
1392
+ hrs_minus_tzs = _times_to_hours_after_local_midnight(times) - tzs
1396
1393
 
1397
- # ensure array return instead of a version-dependent pandas <T>Index
1398
- return np.asarray(
1399
- 15. * (hrs_minus_tzs - 12.) + longitude + equation_of_time / 4.)
1394
+ return 15. * (hrs_minus_tzs - 12.) + longitude + equation_of_time / 4.
1400
1395
 
1401
1396
 
1402
1397
  def _hour_angle_to_hours(times, hourangle, longitude, equation_of_time):
1403
1398
  """converts hour angles in degrees to hours as a numpy array"""
1404
- if times.tz is None:
1405
- times = times.tz_localize('utc')
1399
+
1400
+ # times must be localized
1401
+ if not times.tz:
1402
+ raise ValueError('times must be localized')
1403
+
1406
1404
  tzs = np.array([ts.utcoffset().total_seconds() for ts in times]) / 3600
1407
1405
  hours = (hourangle - longitude - equation_of_time / 4.) / 15. + 12. + tzs
1408
1406
  return np.asarray(hours)
@@ -1412,18 +1410,36 @@ def _local_times_from_hours_since_midnight(times, hours):
1412
1410
  """
1413
1411
  converts hours since midnight from an array of floats to localized times
1414
1412
  """
1415
- tz_info = times.tz # pytz timezone info
1416
- naive_times = times.tz_localize(None) # naive but still localized
1417
- # normalize local, naive times to previous midnight and add the hours until
1413
+
1414
+ # times must be localized
1415
+ if not times.tz:
1416
+ raise ValueError('times must be localized')
1417
+
1418
+ # normalize local times to previous local midnight and add the hours until
1418
1419
  # sunrise, sunset, and transit
1419
- return pd.DatetimeIndex(
1420
- naive_times.normalize() + pd.to_timedelta(hours, unit='h'), tz=tz_info)
1420
+ return times.normalize() + pd.to_timedelta(hours, unit='h')
1421
1421
 
1422
1422
 
1423
1423
  def _times_to_hours_after_local_midnight(times):
1424
1424
  """convert local pandas datetime indices to array of hours as floats"""
1425
- times = times.tz_localize(None)
1426
- hrs = (times - times.normalize()) / pd.Timedelta('1h')
1425
+
1426
+ # times must be localized
1427
+ if not times.tz:
1428
+ raise ValueError('times must be localized')
1429
+
1430
+ # Some timezones have a DST shift at midnight:
1431
+ # 11:59pm -> 1:00am - results in a nonexistent midnight
1432
+ # 12:59am -> 12:00am - results in an ambiguous midnight
1433
+ # We remove the timezone before normalizing for this reason.
1434
+ naive_normalized_times = times.tz_localize(None).normalize()
1435
+
1436
+ # Use Pandas functionality for shifting nonexistent times forward
1437
+ normalized_times = naive_normalized_times.tz_localize(
1438
+ times.tz, nonexistent='shift_forward', ambiguous='raise')
1439
+
1440
+ hrs = (times - normalized_times) / pd.Timedelta('1h')
1441
+
1442
+ # ensure array return instead of a version-dependent pandas <T>Index
1427
1443
  return np.array(hrs)
1428
1444
 
1429
1445
 
@@ -1469,6 +1485,11 @@ def sun_rise_set_transit_geometric(times, latitude, longitude, declination,
1469
1485
  CRC Press (2012)
1470
1486
 
1471
1487
  """
1488
+
1489
+ # times must be localized
1490
+ if not times.tz:
1491
+ raise ValueError('times must be localized')
1492
+
1472
1493
  latitude_rad = np.radians(latitude) # radians
1473
1494
  sunset_angle_rad = np.arccos(-np.tan(declination) * np.tan(latitude_rad))
1474
1495
  sunset_angle = np.degrees(sunset_angle_rad) # degrees
pvlib/spa.py CHANGED
@@ -413,8 +413,10 @@ def julian_day_dt(year, month, day, hour, minute, second, microsecond):
413
413
  frac_of_day = (microsecond / 1e6 + (second + minute * 60 + hour * 3600)
414
414
  ) * 1.0 / (3600*24)
415
415
  d = day + frac_of_day
416
- jd = (int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + d +
417
- b - 1524.5)
416
+ jd = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + d - 1524.5
417
+ if jd > 2299160.0:
418
+ jd += b
419
+
418
420
  return jd
419
421
 
420
422
 
@@ -835,24 +837,24 @@ def equation_of_time(sun_mean_longitude, geocentric_sun_right_ascension,
835
837
  return E
836
838
 
837
839
 
838
- @jcompile('void(float64[:], float64[:], float64[:,:])', nopython=True,
839
- nogil=True)
840
- def solar_position_loop(unixtime, loc_args, out):
840
+ @jcompile('void(float64[:], float64[:], float64[:], float64[:,:])',
841
+ nopython=True, nogil=True)
842
+ def solar_position_loop(unixtime, delta_t, loc_args, out):
841
843
  """Loop through the time array and calculate the solar position"""
842
844
  lat = loc_args[0]
843
845
  lon = loc_args[1]
844
846
  elev = loc_args[2]
845
847
  pressure = loc_args[3]
846
848
  temp = loc_args[4]
847
- delta_t = loc_args[5]
848
- atmos_refract = loc_args[6]
849
- sst = loc_args[7]
850
- esd = loc_args[8]
849
+ atmos_refract = loc_args[5]
850
+ sst = loc_args[6]
851
+ esd = loc_args[7]
851
852
 
852
853
  for i in range(unixtime.shape[0]):
853
854
  utime = unixtime[i]
855
+ dT = delta_t[i]
854
856
  jd = julian_day(utime)
855
- jde = julian_ephemeris_day(jd, delta_t)
857
+ jde = julian_ephemeris_day(jd, dT)
856
858
  jc = julian_century(jd)
857
859
  jce = julian_ephemeris_century(jde)
858
860
  jme = julian_ephemeris_millennium(jce)
@@ -920,8 +922,11 @@ def solar_position_numba(unixtime, lat, lon, elev, pressure, temp, delta_t,
920
922
  and multiple threads. Very slow if functions are not numba compiled.
921
923
  """
922
924
  # these args are the same for each thread
923
- loc_args = np.array([lat, lon, elev, pressure, temp, delta_t,
924
- atmos_refract, sst, esd])
925
+ loc_args = np.array([lat, lon, elev, pressure, temp,
926
+ atmos_refract, sst, esd], dtype=np.float64)
927
+
928
+ # turn delta_t into an array if it isn't already
929
+ delta_t = np.full_like(unixtime, delta_t, dtype=np.float64)
925
930
 
926
931
  # construct dims x ulength array to put the results in
927
932
  ulength = unixtime.shape[0]
@@ -937,18 +942,20 @@ def solar_position_numba(unixtime, lat, lon, elev, pressure, temp, delta_t,
937
942
  unixtime = unixtime.astype(np.float64)
938
943
 
939
944
  if ulength < numthreads:
940
- warnings.warn('The number of threads is more than the length of '
941
- 'the time array. Only using %s threads.'.format(ulength))
942
945
  numthreads = ulength
943
946
 
944
947
  if numthreads <= 1:
945
- solar_position_loop(unixtime, loc_args, result)
948
+ solar_position_loop(unixtime, delta_t, loc_args, result)
946
949
  return result
947
950
 
948
951
  # split the input and output arrays into numthreads chunks
949
952
  split0 = np.array_split(unixtime, numthreads)
953
+ split1 = np.array_split(delta_t, numthreads)
950
954
  split2 = np.array_split(result, numthreads, axis=1)
951
- chunks = [[a0, loc_args, split2[i]] for i, a0 in enumerate(split0)]
955
+ chunks = [
956
+ [a0, a1, loc_args, a2]
957
+ for a0, a1, a2 in zip(split0, split1, split2)
958
+ ]
952
959
  # Spawn one thread per chunk
953
960
  threads = [threading.Thread(target=solar_position_loop, args=chunk)
954
961
  for chunk in chunks]
@@ -1035,7 +1042,7 @@ def solar_position(unixtime, lat, lon, elev, pressure, temp, delta_t,
1035
1042
  unixtime : numpy array
1036
1043
  Array of unix/epoch timestamps to calculate solar position for.
1037
1044
  Unixtime is the number of seconds since Jan. 1, 1970 00:00:00 UTC.
1038
- A pandas.DatetimeIndex is easily converted using .astype(np.int64)/10**9
1045
+ A pandas.DatetimeIndex is easily converted using .view(np.int64)/10**9
1039
1046
  lat : float
1040
1047
  Latitude to calculate solar position for
1041
1048
  lon : float
@@ -1048,7 +1055,7 @@ def solar_position(unixtime, lat, lon, elev, pressure, temp, delta_t,
1048
1055
  temp : int or float
1049
1056
  avg. yearly temperature at location in
1050
1057
  degrees C; used for atmospheric correction
1051
- delta_t : float
1058
+ delta_t : float or array
1052
1059
  Difference between terrestrial time and UT1.
1053
1060
  atmos_refrac : float
1054
1061
  The approximate atmospheric refraction (in degrees)
@@ -1113,7 +1120,7 @@ def transit_sunrise_sunset(dates, lat, lon, delta_t, numthreads):
1113
1120
  Latitude of location to perform calculation for
1114
1121
  lon : float
1115
1122
  Longitude of location
1116
- delta_t : float
1123
+ delta_t : float or array
1117
1124
  Difference between terrestrial time and UT. USNO has tables.
1118
1125
  numthreads : int
1119
1126
  Number to threads to use for calculation (if using numba)
@@ -1214,8 +1221,8 @@ def earthsun_distance(unixtime, delta_t, numthreads):
1214
1221
  unixtime : numpy array
1215
1222
  Array of unix/epoch timestamps to calculate solar position for.
1216
1223
  Unixtime is the number of seconds since Jan. 1, 1970 00:00:00 UTC.
1217
- A pandas.DatetimeIndex is easily converted using .astype(np.int64)/10**9
1218
- delta_t : float
1224
+ A pandas.DatetimeIndex is easily converted using .view(np.int64)/10**9
1225
+ delta_t : float or array
1219
1226
  Difference between terrestrial time and UT. USNO has tables.
1220
1227
  numthreads : int
1221
1228
  Number to threads to use for calculation (if using numba)
@@ -1242,9 +1249,6 @@ def calculate_deltat(year, month):
1242
1249
  """Calculate the difference between Terrestrial Dynamical Time (TD)
1243
1250
  and Universal Time (UT).
1244
1251
 
1245
- Note: This function is not yet compatible for calculations using
1246
- Numba.
1247
-
1248
1252
  Equations taken from http://eclipse.gsfc.nasa.gov/SEcat5/deltatpoly.html
1249
1253
  """
1250
1254
 
@@ -1,14 +1,19 @@
1
1
  from pvlib.spectrum.spectrl2 import spectrl2 # noqa: F401
2
2
  from pvlib.spectrum.mismatch import ( # noqa: F401
3
3
  calc_spectral_mismatch_field,
4
- get_am15g,
5
- get_reference_spectra,
6
- get_example_spectral_response,
7
4
  spectral_factor_caballero,
8
5
  spectral_factor_firstsolar,
9
6
  spectral_factor_sapm,
10
7
  spectral_factor_pvspec,
11
8
  spectral_factor_jrc,
9
+ )
10
+ from pvlib.spectrum.irradiance import ( # noqa: F401
11
+ get_am15g,
12
+ get_reference_spectra,
13
+ average_photon_energy,
14
+ )
15
+ from pvlib.spectrum.response import ( # noqa: F401
16
+ get_example_spectral_response,
12
17
  sr_to_qe,
13
- qe_to_sr
18
+ qe_to_sr,
14
19
  )