pvlib 0.9.4a1__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 (86) hide show
  1. pvlib/__init__.py +3 -2
  2. pvlib/atmosphere.py +23 -173
  3. pvlib/bifacial/infinite_sheds.py +88 -277
  4. pvlib/bifacial/utils.py +270 -28
  5. pvlib/data/adr-library-cec-inverters-2019-03-05.csv +5009 -0
  6. pvlib/data/precise_iv_curves1.json +10251 -0
  7. pvlib/data/precise_iv_curves2.json +10251 -0
  8. pvlib/data/precise_iv_curves_parameter_sets1.csv +33 -0
  9. pvlib/data/precise_iv_curves_parameter_sets2.csv +33 -0
  10. pvlib/data/test_psm3_2017.csv +17521 -17521
  11. pvlib/data/test_psm3_2019_5min.csv +288 -288
  12. pvlib/data/test_read_psm3.csv +17522 -17522
  13. pvlib/data/test_read_pvgis_horizon.csv +49 -0
  14. pvlib/data/variables_style_rules.csv +3 -0
  15. pvlib/iam.py +207 -51
  16. pvlib/inverter.py +6 -1
  17. pvlib/iotools/__init__.py +7 -2
  18. pvlib/iotools/acis.py +516 -0
  19. pvlib/iotools/midc.py +4 -4
  20. pvlib/iotools/psm3.py +59 -42
  21. pvlib/iotools/pvgis.py +84 -28
  22. pvlib/iotools/sodapro.py +8 -6
  23. pvlib/iotools/srml.py +121 -18
  24. pvlib/iotools/surfrad.py +2 -2
  25. pvlib/iotools/tmy.py +146 -102
  26. pvlib/irradiance.py +270 -15
  27. pvlib/ivtools/sde.py +14 -20
  28. pvlib/ivtools/sdm.py +31 -20
  29. pvlib/ivtools/utils.py +127 -6
  30. pvlib/location.py +3 -2
  31. pvlib/modelchain.py +67 -70
  32. pvlib/pvarray.py +225 -0
  33. pvlib/pvsystem.py +169 -539
  34. pvlib/shading.py +43 -2
  35. pvlib/singlediode.py +216 -66
  36. pvlib/snow.py +36 -15
  37. pvlib/soiling.py +3 -3
  38. pvlib/spa.py +327 -368
  39. pvlib/spectrum/__init__.py +8 -2
  40. pvlib/spectrum/mismatch.py +335 -0
  41. pvlib/temperature.py +124 -13
  42. pvlib/tests/bifacial/test_infinite_sheds.py +44 -106
  43. pvlib/tests/bifacial/test_utils.py +102 -5
  44. pvlib/tests/conftest.py +0 -31
  45. pvlib/tests/iotools/test_acis.py +213 -0
  46. pvlib/tests/iotools/test_midc.py +6 -6
  47. pvlib/tests/iotools/test_psm3.py +7 -5
  48. pvlib/tests/iotools/test_pvgis.py +21 -14
  49. pvlib/tests/iotools/test_sodapro.py +1 -1
  50. pvlib/tests/iotools/test_srml.py +71 -6
  51. pvlib/tests/iotools/test_tmy.py +43 -8
  52. pvlib/tests/ivtools/test_sde.py +19 -17
  53. pvlib/tests/ivtools/test_sdm.py +9 -4
  54. pvlib/tests/ivtools/test_utils.py +96 -1
  55. pvlib/tests/test_atmosphere.py +8 -64
  56. pvlib/tests/test_clearsky.py +0 -1
  57. pvlib/tests/test_iam.py +74 -1
  58. pvlib/tests/test_irradiance.py +56 -2
  59. pvlib/tests/test_location.py +1 -1
  60. pvlib/tests/test_modelchain.py +33 -76
  61. pvlib/tests/test_pvarray.py +46 -0
  62. pvlib/tests/test_pvsystem.py +366 -201
  63. pvlib/tests/test_shading.py +35 -0
  64. pvlib/tests/test_singlediode.py +306 -29
  65. pvlib/tests/test_snow.py +84 -1
  66. pvlib/tests/test_soiling.py +8 -7
  67. pvlib/tests/test_solarposition.py +7 -7
  68. pvlib/tests/test_spa.py +6 -7
  69. pvlib/tests/test_spectrum.py +145 -1
  70. pvlib/tests/test_temperature.py +29 -11
  71. pvlib/tests/test_tools.py +41 -0
  72. pvlib/tests/test_tracking.py +0 -149
  73. pvlib/tools.py +49 -25
  74. pvlib/tracking.py +1 -269
  75. pvlib-0.10.0.dist-info/AUTHORS.md +35 -0
  76. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/LICENSE +5 -2
  77. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/METADATA +3 -13
  78. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/RECORD +80 -75
  79. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/WHEEL +1 -1
  80. pvlib/data/adr-library-2013-10-01.csv +0 -1762
  81. pvlib/forecast.py +0 -1211
  82. pvlib/iotools/ecmwf_macc.py +0 -312
  83. pvlib/tests/iotools/test_ecmwf_macc.py +0 -162
  84. pvlib/tests/test_forecast.py +0 -228
  85. pvlib-0.9.4a1.dist-info/AUTHORS.md +0 -32
  86. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ import pandas as pd
4
4
  import numpy as np
5
5
  from pvlib import spectrum
6
6
 
7
- from .conftest import DATA_DIR
7
+ from .conftest import DATA_DIR, assert_series_equal
8
8
 
9
9
  SPECTRL2_TEST_DATA = DATA_DIR / 'spectrl2_example_spectra.csv'
10
10
 
@@ -171,3 +171,147 @@ def test_calc_spectral_mismatch_field(spectrl2_data):
171
171
  mm = spectrum.calc_spectral_mismatch_field(sr, e_sun=e_sun)
172
172
  assert mm.index is e_sun.index
173
173
  assert_allclose(mm, expected, rtol=1e-6)
174
+
175
+
176
+ @pytest.mark.parametrize("module_type,expect", [
177
+ ('cdte', np.array(
178
+ [[ 0.99051020, 0.97640320, 0.93975028],
179
+ [ 1.02928735, 1.01881074, 0.98578821],
180
+ [ 1.04750335, 1.03814456, 1.00623986]])),
181
+ ('monosi', np.array(
182
+ [[ 0.97769770, 1.02043409, 1.03574032],
183
+ [ 0.98630905, 1.03055092, 1.04736262],
184
+ [ 0.98828494, 1.03299036, 1.05026561]])),
185
+ ('polysi', np.array(
186
+ [[ 0.97704080, 1.01705849, 1.02613202],
187
+ [ 0.98992828, 1.03173953, 1.04260662],
188
+ [ 0.99352435, 1.03588785, 1.04730718]])),
189
+ ('cigs', np.array(
190
+ [[ 0.97459190, 1.02821696, 1.05067895],
191
+ [ 0.97529378, 1.02967497, 1.05289307],
192
+ [ 0.97269159, 1.02730558, 1.05075651]])),
193
+ ('asi', np.array(
194
+ [[ 1.05552750, 0.87707583, 0.72243772],
195
+ [ 1.11225204, 0.93665901, 0.78487953],
196
+ [ 1.14555295, 0.97084011, 0.81994083]]))
197
+ ])
198
+ def test_spectral_factor_firstsolar(module_type, expect):
199
+ ams = np.array([1, 3, 5])
200
+ pws = np.array([1, 3, 5])
201
+ ams, pws = np.meshgrid(ams, pws)
202
+ out = spectrum.spectral_factor_firstsolar(pws, ams, module_type)
203
+ assert_allclose(out, expect, atol=0.001)
204
+
205
+
206
+ def test_spectral_factor_firstsolar_supplied():
207
+ # use the cdte coeffs
208
+ coeffs = (0.87102, -0.040543, -0.00929202, 0.10052, 0.073062, -0.0034187)
209
+ out = spectrum.spectral_factor_firstsolar(1, 1, coefficients=coeffs)
210
+ expected = 0.99134828
211
+ assert_allclose(out, expected, atol=1e-3)
212
+
213
+
214
+ def test_spectral_factor_firstsolar_ambiguous():
215
+ with pytest.raises(TypeError):
216
+ spectrum.spectral_factor_firstsolar(1, 1)
217
+
218
+
219
+ def test_spectral_factor_firstsolar_ambiguous_both():
220
+ # use the cdte coeffs
221
+ coeffs = (0.87102, -0.040543, -0.00929202, 0.10052, 0.073062, -0.0034187)
222
+ with pytest.raises(TypeError):
223
+ spectrum.spectral_factor_firstsolar(1, 1, 'cdte', coefficients=coeffs)
224
+
225
+
226
+ def test_spectral_factor_firstsolar_large_airmass():
227
+ # test that airmass > 10 is treated same as airmass==10
228
+ m_eq10 = spectrum.spectral_factor_firstsolar(1, 10, 'monosi')
229
+ m_gt10 = spectrum.spectral_factor_firstsolar(1, 15, 'monosi')
230
+ assert_allclose(m_eq10, m_gt10)
231
+
232
+
233
+ def test_spectral_factor_firstsolar_low_airmass():
234
+ with pytest.warns(UserWarning, match='Exceptionally low air mass'):
235
+ _ = spectrum.spectral_factor_firstsolar(1, 0.1, 'monosi')
236
+
237
+
238
+ def test_spectral_factor_firstsolar_range():
239
+ with pytest.warns(UserWarning, match='Exceptionally high pw values'):
240
+ out = spectrum.spectral_factor_firstsolar(np.array([.1, 3, 10]),
241
+ np.array([1, 3, 5]),
242
+ module_type='monosi')
243
+ expected = np.array([0.96080878, 1.03055092, np.nan])
244
+ assert_allclose(out, expected, atol=1e-3)
245
+ with pytest.warns(UserWarning, match='Exceptionally high pw values'):
246
+ out = spectrum.spectral_factor_firstsolar(6, 1.5,
247
+ max_precipitable_water=5,
248
+ module_type='monosi')
249
+ with pytest.warns(UserWarning, match='Exceptionally low pw values'):
250
+ out = spectrum.spectral_factor_firstsolar(np.array([0, 3, 8]),
251
+ np.array([1, 3, 5]),
252
+ module_type='monosi')
253
+ expected = np.array([0.96080878, 1.03055092, 1.04932727])
254
+ assert_allclose(out, expected, atol=1e-3)
255
+ with pytest.warns(UserWarning, match='Exceptionally low pw values'):
256
+ out = spectrum.spectral_factor_firstsolar(0.2, 1.5,
257
+ min_precipitable_water=1,
258
+ module_type='monosi')
259
+
260
+
261
+ @pytest.mark.parametrize('airmass,expected', [
262
+ (1.5, 1.00028714375),
263
+ (np.array([[10, np.nan]]), np.array([[0.999535, 0]])),
264
+ (pd.Series([5]), pd.Series([1.0387675]))
265
+ ])
266
+ def test_spectral_factor_sapm(sapm_module_params, airmass, expected):
267
+
268
+ out = spectrum.spectral_factor_sapm(airmass, sapm_module_params)
269
+
270
+ if isinstance(airmass, pd.Series):
271
+ assert_series_equal(out, expected, check_less_precise=4)
272
+ else:
273
+ assert_allclose(out, expected, atol=1e-4)
274
+
275
+
276
+ @pytest.mark.parametrize("module_type,expected", [
277
+ ('asi', np.array([0.9108, 0.9897, 0.9707, 1.0265, 1.0798, 0.9537])),
278
+ ('perovskite', np.array([0.9422, 0.9932, 0.9868, 1.0183, 1.0604, 0.9737])),
279
+ ('cdte', np.array([0.9824, 1.0000, 1.0065, 1.0117, 1.042, 0.9979])),
280
+ ('multisi', np.array([0.9907, 0.9979, 1.0203, 1.0081, 1.0058, 1.019])),
281
+ ('monosi', np.array([0.9935, 0.9987, 1.0264, 1.0074, 0.9999, 1.0263])),
282
+ ('cigs', np.array([1.0014, 1.0011, 1.0270, 1.0082, 1.0029, 1.026])),
283
+ ])
284
+ def test_spectral_factor_caballero(module_type, expected):
285
+ ams = np.array([3.0, 1.5, 3.0, 1.5, 1.5, 3.0])
286
+ aods = np.array([1.0, 1.0, 0.02, 0.02, 0.08, 0.08])
287
+ pws = np.array([1.42, 1.42, 1.42, 1.42, 4.0, 1.0])
288
+ out = spectrum.spectral_factor_caballero(pws, ams, aods,
289
+ module_type=module_type)
290
+ assert np.allclose(expected, out, atol=1e-3)
291
+
292
+
293
+ def test_spectral_factor_caballero_supplied():
294
+ # use the cdte coeffs
295
+ coeffs = (
296
+ 1.0044, 0.0095, -0.0037, 0.0002, 0.0000, -0.0046,
297
+ -0.0182, 0, 0.0095, 0.0068, 0, 1)
298
+ out = spectrum.spectral_factor_caballero(1, 1, 1, coefficients=coeffs)
299
+ expected = 1.0021964
300
+ assert_allclose(out, expected, atol=1e-3)
301
+
302
+
303
+ def test_spectral_factor_caballero_supplied_redundant():
304
+ # Error when specifying both module_type and coefficients
305
+ coeffs = (
306
+ 1.0044, 0.0095, -0.0037, 0.0002, 0.0000, -0.0046,
307
+ -0.0182, 0, 0.0095, 0.0068, 0, 1)
308
+ with pytest.raises(ValueError):
309
+ spectrum.spectral_factor_caballero(1, 1, 1, module_type='cdte',
310
+ coefficients=coeffs)
311
+
312
+
313
+ def test_spectral_factor_caballero_supplied_ambiguous():
314
+ # Error when specifying neither module_type nor coefficients
315
+ with pytest.raises(ValueError):
316
+ spectrum.spectral_factor_caballero(1, 1, 1, module_type=None,
317
+ coefficients=None)
@@ -99,21 +99,14 @@ def test_pvsyst_cell_series():
99
99
  assert_series_equal(expected, result)
100
100
 
101
101
 
102
- def test_pvsyst_cell_eta_m_deprecated():
103
- with pytest.warns(pvlibDeprecationWarning):
104
- result = temperature.pvsyst_cell(900, 20, wind_speed=5.0, u_c=23.5,
105
- u_v=6.25, eta_m=0.1)
106
- assert_allclose(result, 33.315, 0.001)
107
-
108
-
109
102
  def test_faiman_default():
110
103
  result = temperature.faiman(900, 20, 5)
111
- assert_allclose(result, 35.203, 0.001)
104
+ assert_allclose(result, 35.203, atol=0.001)
112
105
 
113
106
 
114
107
  def test_faiman_kwargs():
115
108
  result = temperature.faiman(900, 20, wind_speed=5.0, u0=22.0, u1=6.)
116
- assert_allclose(result, 37.308, 0.001)
109
+ assert_allclose(result, 37.308, atol=0.001)
117
110
 
118
111
 
119
112
  def test_faiman_list():
@@ -122,7 +115,7 @@ def test_faiman_list():
122
115
  winds = [10, 5, 0]
123
116
  result = temperature.faiman(irrads, temps, wind_speed=winds)
124
117
  expected = np.array([0.0, 18.446, 5.0])
125
- assert_allclose(expected, result, 3)
118
+ assert_allclose(expected, result, atol=0.001)
126
119
 
127
120
 
128
121
  def test_faiman_ndarray():
@@ -131,7 +124,32 @@ def test_faiman_ndarray():
131
124
  winds = np.array([10, 5, 0])
132
125
  result = temperature.faiman(irrads, temps, wind_speed=winds)
133
126
  expected = np.array([0.0, 18.446, 5.0])
134
- assert_allclose(expected, result, 3)
127
+ assert_allclose(expected, result, atol=0.001)
128
+
129
+
130
+ def test_faiman_rad_no_ir():
131
+ expected = temperature.faiman(900, 20, 5)
132
+ result = temperature.faiman_rad(900, 20, 5)
133
+ assert_allclose(result, expected)
134
+
135
+
136
+ def test_faiman_rad_ir():
137
+ ir_down = np.array([0, 100, 200, 315.6574, 400])
138
+ expected = [-11.111, -7.591, -4.071, -0.000, 2.969]
139
+ result = temperature.faiman_rad(0, 0, 0, ir_down)
140
+ assert_allclose(result, expected, atol=0.001)
141
+
142
+ sky_view = np.array([1.0, 0.5, 0.0])
143
+ expected = [-4.071, -2.036, 0.000]
144
+ result = temperature.faiman_rad(0, 0, 0, ir_down=200,
145
+ sky_view=sky_view)
146
+ assert_allclose(result, expected, atol=0.001)
147
+
148
+ emissivity = np.array([1.0, 0.88, 0.5, 0.0])
149
+ expected = [-4.626, -4.071, -2.313, 0.000]
150
+ result = temperature.faiman_rad(0, 0, 0, ir_down=200,
151
+ emissivity=emissivity)
152
+ assert_allclose(result, expected, atol=0.001)
135
153
 
136
154
 
137
155
  def test_ross():
pvlib/tests/test_tools.py CHANGED
@@ -2,6 +2,7 @@ import pytest
2
2
 
3
3
  from pvlib import tools
4
4
  import numpy as np
5
+ import pandas as pd
5
6
 
6
7
 
7
8
  @pytest.mark.parametrize('keys, input_dict, expected', [
@@ -45,6 +46,22 @@ def test__golden_sect_DataFrame_vector():
45
46
  v, x = tools._golden_sect_DataFrame(params, lower, upper,
46
47
  _obj_test_golden_sect)
47
48
  assert np.allclose(x, expected, atol=1e-8)
49
+ # some upper and lower bounds equal
50
+ params = {'c': np.array([1., 2., 1.]), 'n': np.array([1., 1., 1.])}
51
+ lower = np.array([0., 0.001, 1.])
52
+ upper = np.array([1., 1.2, 1.])
53
+ expected = np.array([0.5, 0.25, 1.0]) # x values for maxima
54
+ v, x = tools._golden_sect_DataFrame(params, lower, upper,
55
+ _obj_test_golden_sect)
56
+ assert np.allclose(x, expected, atol=1e-8)
57
+ # all upper and lower bounds equal, arrays of length 1
58
+ params = {'c': np.array([1.]), 'n': np.array([1.])}
59
+ lower = np.array([1.])
60
+ upper = np.array([1.])
61
+ expected = np.array([1.]) # x values for maxima
62
+ v, x = tools._golden_sect_DataFrame(params, lower, upper,
63
+ _obj_test_golden_sect)
64
+ assert np.allclose(x, expected, atol=1e-8)
48
65
 
49
66
 
50
67
  def test__golden_sect_DataFrame_nans():
@@ -79,3 +96,27 @@ def test_degrees_to_index_1():
79
96
  'latitude' or 'longitude' is passed."""
80
97
  with pytest.raises(IndexError): # invalid value for coordinate argument
81
98
  tools._degrees_to_index(degrees=22.0, coordinate='width')
99
+
100
+
101
+ @pytest.mark.parametrize('args, args_idx', [
102
+ # no pandas.Series or pandas.DataFrame args
103
+ ((1,), None),
104
+ (([1],), None),
105
+ ((np.array(1),), None),
106
+ ((np.array([1]),), None),
107
+ # has pandas.Series or pandas.DataFrame args
108
+ ((pd.DataFrame([1], index=[1]),), 0),
109
+ ((pd.Series([1], index=[1]),), 0),
110
+ ((1, pd.Series([1], index=[1]),), 1),
111
+ ((1, pd.DataFrame([1], index=[1]),), 1),
112
+ # first pandas.Series or pandas.DataFrame is used
113
+ ((1, pd.Series([1], index=[1]), pd.DataFrame([2], index=[2]),), 1),
114
+ ((1, pd.DataFrame([1], index=[1]), pd.Series([2], index=[2]),), 1),
115
+ ])
116
+ def test_get_pandas_index(args, args_idx):
117
+ index = tools.get_pandas_index(*args)
118
+
119
+ if args_idx is None:
120
+ assert index is None
121
+ else:
122
+ pd.testing.assert_index_equal(args[args_idx].index, index)
@@ -290,155 +290,6 @@ def test_low_sun_angles():
290
290
  assert_allclose(expected[k], v)
291
291
 
292
292
 
293
- def test_SingleAxisTracker_tracking():
294
- with pytest.warns(pvlibDeprecationWarning):
295
- system = tracking.SingleAxisTracker(max_angle=90, axis_tilt=30,
296
- axis_azimuth=180, gcr=2.0/7.0,
297
- backtrack=True)
298
-
299
- apparent_zenith = pd.Series([30])
300
- apparent_azimuth = pd.Series([135])
301
-
302
- tracker_data = system.singleaxis(apparent_zenith, apparent_azimuth)
303
-
304
- expect = pd.DataFrame({'aoi': 7.286245, 'surface_azimuth': 142.65730,
305
- 'surface_tilt': 35.98741,
306
- 'tracker_theta': -20.88121},
307
- index=[0], dtype=np.float64)
308
- expect = expect[SINGLEAXIS_COL_ORDER]
309
-
310
- assert_frame_equal(expect, tracker_data)
311
-
312
- # results calculated using PVsyst
313
- pvsyst_solar_azimuth = 7.1609
314
- pvsyst_solar_height = 27.315
315
- pvsyst_axis_tilt = 20.
316
- pvsyst_axis_azimuth = 20.
317
- with pytest.warns(pvlibDeprecationWarning):
318
- pvsyst_system = tracking.SingleAxisTracker(
319
- max_angle=60., axis_tilt=pvsyst_axis_tilt,
320
- axis_azimuth=180+pvsyst_axis_azimuth, backtrack=False)
321
- # the definition of azimuth is different from PYsyst
322
- apparent_azimuth = pd.Series([180+pvsyst_solar_azimuth])
323
- apparent_zenith = pd.Series([90-pvsyst_solar_height])
324
- tracker_data = pvsyst_system.singleaxis(apparent_zenith, apparent_azimuth)
325
- expect = pd.DataFrame({'aoi': 41.07852, 'surface_azimuth': 180-18.432,
326
- 'surface_tilt': 24.92122,
327
- 'tracker_theta': -15.18391},
328
- index=[0], dtype=np.float64)
329
- expect = expect[SINGLEAXIS_COL_ORDER]
330
-
331
- assert_frame_equal(expect, tracker_data)
332
-
333
-
334
- # see test_irradiance for more thorough testing
335
- def test_get_aoi():
336
- with pytest.warns(pvlibDeprecationWarning):
337
- system = tracking.SingleAxisTracker(max_angle=90, axis_tilt=30,
338
- axis_azimuth=180, gcr=2.0/7.0,
339
- backtrack=True)
340
- surface_tilt = np.array([30, 0])
341
- surface_azimuth = np.array([90, 270])
342
- solar_zenith = np.array([70, 10])
343
- solar_azimuth = np.array([100, 180])
344
- out = system.get_aoi(surface_tilt, surface_azimuth,
345
- solar_zenith, solar_azimuth)
346
- expected = np.array([40.632115, 10.])
347
- assert_allclose(out, expected, atol=0.000001)
348
-
349
-
350
- def test_get_irradiance():
351
- with pytest.warns(pvlibDeprecationWarning):
352
- system = tracking.SingleAxisTracker(max_angle=90, axis_tilt=30,
353
- axis_azimuth=180, gcr=2.0/7.0,
354
- backtrack=True)
355
- times = pd.date_range(start='20160101 1200-0700',
356
- end='20160101 1800-0700', freq='6H')
357
- # latitude=32, longitude=-111
358
- solar_position = pd.DataFrame(np.array(
359
- [[55.36421554, 55.38851771, 34.63578446, 34.61148229,
360
- 172.32003763, -3.44516534],
361
- [96.50000401, 96.50000401, -6.50000401, -6.50000401,
362
- 246.91581654, -3.56292888]]),
363
- columns=['apparent_zenith', 'zenith', 'apparent_elevation',
364
- 'elevation', 'azimuth', 'equation_of_time'],
365
- index=times)
366
- irrads = pd.DataFrame({'dni': [900, 0], 'ghi': [600, 0], 'dhi': [100, 0]},
367
- index=times)
368
- solar_zenith = solar_position['apparent_zenith']
369
- solar_azimuth = solar_position['azimuth']
370
-
371
- # invalid warnings already generated in horizon test above,
372
- # no need to clutter test output here
373
- with np.errstate(invalid='ignore'):
374
- tracker_data = system.singleaxis(solar_zenith, solar_azimuth)
375
-
376
- # some invalid values in irradiance.py. not our problem here
377
- with np.errstate(invalid='ignore'):
378
- irradiance = system.get_irradiance(tracker_data['surface_tilt'],
379
- tracker_data['surface_azimuth'],
380
- solar_zenith,
381
- solar_azimuth,
382
- irrads['dni'],
383
- irrads['ghi'],
384
- irrads['dhi'])
385
-
386
- expected = pd.DataFrame(data=np.array(
387
- [[961.80070, 815.94490, 145.85580, 135.32820, 10.52757492],
388
- [nan, nan, nan, nan, nan]]),
389
- columns=['poa_global', 'poa_direct',
390
- 'poa_diffuse', 'poa_sky_diffuse',
391
- 'poa_ground_diffuse'],
392
- index=times)
393
-
394
- assert_frame_equal(irradiance, expected, check_less_precise=2)
395
-
396
- # test with albedo as a Series
397
- irrads['albedo'] = [0.5, 0.5]
398
- with np.errstate(invalid='ignore'):
399
- irradiance = system.get_irradiance(tracker_data['surface_tilt'],
400
- tracker_data['surface_azimuth'],
401
- solar_zenith,
402
- solar_azimuth,
403
- irrads['dni'],
404
- irrads['ghi'],
405
- irrads['dhi'],
406
- albedo=irrads['albedo'])
407
-
408
- expected = pd.Series(data=[21.05514984, nan], index=times,
409
- name='poa_ground_diffuse')
410
-
411
- assert_series_equal(irradiance['poa_ground_diffuse'], expected,
412
- check_less_precise=2)
413
-
414
-
415
-
416
- def test_SingleAxisTracker___repr__():
417
- with pytest.warns(pvlibDeprecationWarning):
418
- system = tracking.SingleAxisTracker(
419
- max_angle=45, gcr=.25, module='blah', inverter='blarg',
420
- temperature_model_parameters={'a': -3.56})
421
- expected = """SingleAxisTracker:
422
- axis_tilt: 0
423
- axis_azimuth: 0
424
- max_angle: 45
425
- backtrack: True
426
- gcr: 0.25
427
- cross_axis_tilt: 0.0
428
- name: None
429
- Array:
430
- name: None
431
- mount: SingleAxisTrackerMount(axis_tilt=0, axis_azimuth=0, max_angle=45, backtrack=True, gcr=0.25, cross_axis_tilt=0.0, racking_model=None, module_height=None)
432
- module: blah
433
- albedo: 0.25
434
- module_type: None
435
- temperature_model_parameters: {'a': -3.56}
436
- strings: 1
437
- modules_per_string: 1
438
- inverter: blarg""" # noqa: E501
439
- assert system.__repr__() == expected
440
-
441
-
442
293
  def test_calc_axis_tilt():
443
294
  # expected values
444
295
  expected_axis_tilt = 2.239 # [degrees]
pvlib/tools.py CHANGED
@@ -341,24 +341,18 @@ def _golden_sect_DataFrame(params, lower, upper, func, atol=1e-8):
341
341
  --------
342
342
  pvlib.singlediode._pwr_optfcn
343
343
  """
344
+ if np.any(upper - lower < 0.):
345
+ raise ValueError('upper >= lower is required')
344
346
 
345
347
  phim1 = (np.sqrt(5) - 1) / 2
346
348
 
347
- df = params
349
+ df = params.copy() # shallow copy to avoid modifying caller's dict
348
350
  df['VH'] = upper
349
351
  df['VL'] = lower
350
352
 
351
353
  converged = False
352
- iterations = 0
353
354
 
354
- # handle all NaN case gracefully
355
- with warnings.catch_warnings():
356
- warnings.filterwarnings(action='ignore',
357
- message='All-NaN slice encountered')
358
- iterlimit = 1 + np.nanmax(
359
- np.trunc(np.log(atol / (df['VH'] - df['VL'])) / np.log(phim1)))
360
-
361
- while not converged and (iterations <= iterlimit):
355
+ while not converged:
362
356
 
363
357
  phi = phim1 * (df['VH'] - df['VL'])
364
358
  df['V1'] = df['VL'] + phi
@@ -373,22 +367,19 @@ def _golden_sect_DataFrame(params, lower, upper, func, atol=1e-8):
373
367
 
374
368
  err = abs(df['V2'] - df['V1'])
375
369
 
376
- # works with single value because err is np.float64
377
- converged = (err[~np.isnan(err)] < atol).all()
378
- # err will be less than atol before iterations hit the limit
379
- # but just to be safe
380
- iterations += 1
381
-
382
- if iterations > iterlimit:
383
- raise Exception("Iterations exceeded maximum. Check that func",
384
- " is not NaN in (lower, upper)") # pragma: no cover
370
+ # handle all NaN case gracefully
371
+ with warnings.catch_warnings():
372
+ warnings.filterwarnings(action='ignore',
373
+ message='All-NaN slice encountered')
374
+ converged = np.all(err[~np.isnan(err)] < atol)
385
375
 
386
- try:
387
- func_result = func(df, 'V1')
388
- x = np.where(np.isnan(func_result), np.nan, df['V1'])
389
- except KeyError:
390
- func_result = np.full_like(upper, np.nan)
391
- x = func_result.copy()
376
+ # best estimate of location of maximum
377
+ df['max'] = 0.5 * (df['V1'] + df['V2'])
378
+ func_result = func(df, 'max')
379
+ x = np.where(np.isnan(func_result), np.nan, df['max'])
380
+ if np.isscalar(df['max']):
381
+ # np.where always returns an ndarray, converting scalars to 0d-arrays
382
+ x = x.item()
392
383
 
393
384
  return func_result, x
394
385
 
@@ -470,3 +461,36 @@ def _degrees_to_index(degrees, coordinate):
470
461
  index = int(np.around(index))
471
462
 
472
463
  return index
464
+
465
+
466
+ EPS = np.finfo('float64').eps # machine precision NumPy-1.20
467
+ DX = EPS**(1/3) # optimal differential element
468
+
469
+
470
+ def _first_order_centered_difference(f, x0, dx=DX, args=()):
471
+ # simple replacement for scipy.misc.derivative, which is scheduled for
472
+ # removal in scipy 1.12.0
473
+ df = f(x0+dx, *args) - f(x0-dx, *args)
474
+ return df / 2 / dx
475
+
476
+
477
+ def get_pandas_index(*args):
478
+ """
479
+ Get the index of the first pandas DataFrame or Series in a list of
480
+ arguments.
481
+
482
+ Parameters
483
+ ----------
484
+ args: positional arguments
485
+ The numeric values to scan for a pandas index.
486
+
487
+ Returns
488
+ -------
489
+ A pandas index or None
490
+ None is returned if there are no pandas DataFrames or Series in the
491
+ args list.
492
+ """
493
+ return next(
494
+ (a.index for a in args if isinstance(a, (pd.DataFrame, pd.Series))),
495
+ None
496
+ )