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
@@ -7,6 +7,34 @@ import pytest
7
7
  from pvlib import shading
8
8
 
9
9
 
10
+ @pytest.fixture
11
+ def test_system():
12
+ syst = {'height': 1.0,
13
+ 'pitch': 2.,
14
+ 'surface_tilt': 30.,
15
+ 'surface_azimuth': 180.,
16
+ 'rotation': -30.} # rotation of right edge relative to horizontal
17
+ syst['gcr'] = 1.0 / syst['pitch']
18
+ return syst
19
+
20
+
21
+ def test__ground_angle(test_system):
22
+ ts = test_system
23
+ x = np.array([0., 0.5, 1.0])
24
+ angles = shading.ground_angle(
25
+ ts['surface_tilt'], ts['gcr'], x)
26
+ expected_angles = np.array([0., 5.866738789543952, 9.896090638982903])
27
+ assert np.allclose(angles, expected_angles)
28
+
29
+
30
+ def test__ground_angle_zero_gcr():
31
+ surface_tilt = 30.0
32
+ x = np.array([0.0, 0.5, 1.0])
33
+ angles = shading.ground_angle(surface_tilt, 0, x)
34
+ expected_angles = np.array([0, 0, 0])
35
+ assert np.allclose(angles, expected_angles)
36
+
37
+
10
38
  @pytest.fixture
11
39
  def surface_tilt():
12
40
  idx = pd.date_range('2019-01-01', freq='h', periods=3)
@@ -45,6 +73,13 @@ def test_masking_angle_scalar(surface_tilt, masking_angle):
45
73
  assert np.isclose(masking_angle_actual, angle)
46
74
 
47
75
 
76
+ def test_masking_angle_zero_gcr(surface_tilt):
77
+ # scalar inputs and outputs, including zero
78
+ for tilt in surface_tilt:
79
+ masking_angle_actual = shading.masking_angle(tilt, 0, 0.25)
80
+ assert np.isclose(masking_angle_actual, 0)
81
+
82
+
48
83
  def test_masking_angle_passias_series(surface_tilt, average_masking_angle):
49
84
  # pandas series inputs and outputs
50
85
  masking_angle_actual = shading.masking_angle_passias(surface_tilt, 0.5)
@@ -3,10 +3,14 @@ testing single-diode methods using JW Bishop 1988
3
3
  """
4
4
 
5
5
  import numpy as np
6
+ import pandas as pd
7
+ import scipy
6
8
  from pvlib import pvsystem
7
9
  from pvlib.singlediode import (bishop88_mpp, estimate_voc, VOLTAGE_BUILTIN,
8
10
  bishop88, bishop88_i_from_v, bishop88_v_from_i)
11
+ from pvlib._deprecation import pvlibDeprecationWarning
9
12
  import pytest
13
+ from .conftest import DATA_DIR
10
14
 
11
15
  POA = 888
12
16
  TCELL = 55
@@ -22,22 +26,16 @@ def test_method_spr_e20_327(method, cec_module_spr_e20_327):
22
26
  I_L_ref=spr_e20_327['I_L_ref'], I_o_ref=spr_e20_327['I_o_ref'],
23
27
  R_sh_ref=spr_e20_327['R_sh_ref'], R_s=spr_e20_327['R_s'],
24
28
  EgRef=1.121, dEgdT=-0.0002677)
25
- il, io, rs, rsh, nnsvt = x
26
29
  pvs = pvsystem.singlediode(*x, method='lambertw')
27
30
  out = pvsystem.singlediode(*x, method=method)
28
- isc, voc, imp, vmp, pmp, ix, ixx = out.values()
29
- assert np.isclose(pvs['i_sc'], isc)
30
- assert np.isclose(pvs['v_oc'], voc)
31
- # the singlediode method doesn't actually get the MPP correct
32
- pvs_imp = pvsystem.i_from_v(rsh, rs, nnsvt, vmp, io, il, method='lambertw')
33
- pvs_vmp = pvsystem.v_from_i(rsh, rs, nnsvt, imp, io, il, method='lambertw')
34
- assert np.isclose(pvs_imp, imp)
35
- assert np.isclose(pvs_vmp, vmp)
36
- assert np.isclose(pvs['p_mp'], pmp)
37
- assert np.isclose(pvs['i_x'], ix)
38
- pvs_ixx = pvsystem.i_from_v(rsh, rs, nnsvt, (voc + vmp)/2, io, il,
39
- method='lambertw')
40
- assert np.isclose(pvs_ixx, ixx)
31
+
32
+ assert np.isclose(pvs['i_sc'], out['i_sc'])
33
+ assert np.isclose(pvs['v_oc'], out['v_oc'])
34
+ assert np.isclose(pvs['i_mp'], out['i_mp'])
35
+ assert np.isclose(pvs['v_mp'], out['v_mp'])
36
+ assert np.isclose(pvs['p_mp'], out['p_mp'])
37
+ assert np.isclose(pvs['i_x'], out['i_x'])
38
+ assert np.isclose(pvs['i_xx'], out['i_xx'])
41
39
 
42
40
 
43
41
  @pytest.mark.parametrize('method', ['brentq', 'newton'])
@@ -50,23 +48,170 @@ def test_newton_fs_495(method, cec_module_fs_495):
50
48
  I_L_ref=fs_495['I_L_ref'], I_o_ref=fs_495['I_o_ref'],
51
49
  R_sh_ref=fs_495['R_sh_ref'], R_s=fs_495['R_s'],
52
50
  EgRef=1.475, dEgdT=-0.0003)
53
- il, io, rs, rsh, nnsvt = x
54
- x += (101, )
55
51
  pvs = pvsystem.singlediode(*x, method='lambertw')
56
52
  out = pvsystem.singlediode(*x, method=method)
57
- isc, voc, imp, vmp, pmp, ix, ixx, i, v = out.values()
58
- assert np.isclose(pvs['i_sc'], isc)
59
- assert np.isclose(pvs['v_oc'], voc)
60
- # the singlediode method doesn't actually get the MPP correct
61
- pvs_imp = pvsystem.i_from_v(rsh, rs, nnsvt, vmp, io, il, method='lambertw')
62
- pvs_vmp = pvsystem.v_from_i(rsh, rs, nnsvt, imp, io, il, method='lambertw')
63
- assert np.isclose(pvs_imp, imp)
64
- assert np.isclose(pvs_vmp, vmp)
65
- assert np.isclose(pvs['p_mp'], pmp)
66
- assert np.isclose(pvs['i_x'], ix)
67
- pvs_ixx = pvsystem.i_from_v(rsh, rs, nnsvt, (voc + vmp)/2, io, il,
68
- method='lambertw')
69
- assert np.isclose(pvs_ixx, ixx)
53
+
54
+ assert np.isclose(pvs['i_sc'], out['i_sc'])
55
+ assert np.isclose(pvs['v_oc'], out['v_oc'])
56
+ assert np.isclose(pvs['i_mp'], out['i_mp'])
57
+ assert np.isclose(pvs['v_mp'], out['v_mp'])
58
+ assert np.isclose(pvs['p_mp'], out['p_mp'])
59
+ assert np.isclose(pvs['i_x'], out['i_x'])
60
+ assert np.isclose(pvs['i_xx'], out['i_xx'])
61
+
62
+
63
+ def build_precise_iv_curve_dataframe(file_csv, file_json):
64
+ """
65
+ Reads a precise IV curve parameter set CSV and JSON to create a DataFrame.
66
+ The CSV contains the parameters of the single diode equation which are used
67
+ to generate the JSON data. The data are calculated using [1]_ with 40
68
+ decimal digits of precision in order have at least 16 decimal digits of
69
+ precision when they are stored in JSON. The precision is sufficient for the
70
+ difference between the left and right side of the single diode equation to
71
+ be less than :math:`1 \times 10^{-16}` when the numbers from the JSON are
72
+ read as mpmath floats. The code to generate these IV curve data is from
73
+ [2]_. The data and tests that use this function were added in :pull:`1573`.
74
+
75
+ Parameters
76
+ ----------
77
+ file_csv: str
78
+ Path to a CSV file of IV curve parameter sets.
79
+
80
+ file_json: str
81
+ Path to a JSON file of precise IV curves.
82
+
83
+ Returns
84
+ -------
85
+ A DataFrame with these columns: ``Index``, ``photocurrent``,
86
+ ``saturation_current``, ``resistance_series``, ``resistance_shunt``,
87
+ ``n``, ``cells_in_series``, ``Voltages``, ``Currents``,
88
+ ``diode_voltage``, ``v_oc``, ``i_sc``, ``v_mp``, ``i_mp``, ``p_mp``,
89
+ ``i_x``, ``i_xx`, ``Temperature``, ``Irradiance``, ``Sweep Direction``,
90
+ ``Datetime``, ``Boltzmann``, ``Elementary Charge``, and ``Vth``. The
91
+ columns ``Irradiance``, ``Sweep Direction`` are None or empty strings.
92
+
93
+ References
94
+ ----------
95
+ .. [1] The mpmath development team. (2023). mpmath: a Python library for
96
+ arbitrary-precision floating-point arithmetic (version 1.2.1).
97
+ `mpmath <mpmath.org>`_
98
+
99
+ .. [2] The ivcurves development team. (2022). Code to generate precise
100
+ solutions to the single diode equation.
101
+ `ivcurves <github.com/cwhanse/ivcurves>`_
102
+ """
103
+ params = pd.read_csv(file_csv)
104
+ curves_metadata = pd.read_json(file_json)
105
+ curves = pd.DataFrame(curves_metadata['IV Curves'].values.tolist())
106
+ curves['cells_in_series'] = curves_metadata['cells_in_series']
107
+ joined = params.merge(curves, on='Index', how='inner',
108
+ suffixes=(None, '_drop'), validate='one_to_one')
109
+ joined = joined[(c for c in joined.columns if not c.endswith('_drop'))]
110
+
111
+ # parse strings to np.float64
112
+ is_array = ['Currents', 'Voltages', 'diode_voltage']
113
+ joined[is_array] = joined[is_array].applymap(
114
+ lambda a: np.asarray(a, dtype=np.float64)
115
+ )
116
+ is_number = ['v_oc', 'i_sc', 'v_mp', 'i_mp', 'p_mp', 'i_x', 'i_xx',
117
+ 'Temperature']
118
+ joined[is_number] = joined[is_number].applymap(np.float64)
119
+
120
+ joined['Boltzmann'] = scipy.constants.Boltzmann
121
+ joined['Elementary Charge'] = scipy.constants.elementary_charge
122
+ joined['Vth'] = (
123
+ joined['Boltzmann'] * joined['Temperature']
124
+ / joined['Elementary Charge']
125
+ )
126
+
127
+ return joined
128
+
129
+
130
+ @pytest.fixture(scope='function', params=[
131
+ {
132
+ 'csv': f'{DATA_DIR}/precise_iv_curves_parameter_sets1.csv',
133
+ 'json': f'{DATA_DIR}/precise_iv_curves1.json'
134
+ },
135
+ {
136
+ 'csv': f'{DATA_DIR}/precise_iv_curves_parameter_sets2.csv',
137
+ 'json': f'{DATA_DIR}/precise_iv_curves2.json'
138
+ }
139
+ ], ids=[1, 2])
140
+ def precise_iv_curves(request):
141
+ file_csv, file_json = request.param['csv'], request.param['json']
142
+ pc = build_precise_iv_curve_dataframe(file_csv, file_json)
143
+ params = ['photocurrent', 'saturation_current', 'resistance_series',
144
+ 'resistance_shunt']
145
+ singlediode_params = pc.loc[:, params]
146
+ singlediode_params['nNsVth'] = pc['n'] * pc['cells_in_series'] * pc['Vth']
147
+ return singlediode_params, pc
148
+
149
+
150
+ @pytest.mark.parametrize('method', ['lambertw', 'brentq', 'newton'])
151
+ def test_singlediode_precision(method, precise_iv_curves):
152
+ """
153
+ Tests the accuracy of singlediode. ivcurve_pnts is not tested.
154
+ """
155
+ x, pc = precise_iv_curves
156
+ outs = pvsystem.singlediode(method=method, **x)
157
+
158
+ assert np.allclose(pc['i_sc'], outs['i_sc'], atol=1e-10, rtol=0)
159
+ assert np.allclose(pc['v_oc'], outs['v_oc'], atol=1e-10, rtol=0)
160
+ assert np.allclose(pc['i_mp'], outs['i_mp'], atol=7e-8, rtol=0)
161
+ assert np.allclose(pc['v_mp'], outs['v_mp'], atol=1e-6, rtol=0)
162
+ assert np.allclose(pc['p_mp'], outs['p_mp'], atol=1e-10, rtol=0)
163
+ assert np.allclose(pc['i_x'], outs['i_x'], atol=1e-10, rtol=0)
164
+
165
+ # This test should pass with atol=9e-8 on MacOS and Windows.
166
+ # The atol was lowered to pass on Linux when the vectorized umath module
167
+ # introduced in NumPy 1.22.0 is used.
168
+ assert np.allclose(pc['i_xx'], outs['i_xx'], atol=1e-6, rtol=0)
169
+
170
+
171
+ def test_singlediode_lambert_negative_voc():
172
+
173
+ # Those values result in a negative v_oc out of `_lambertw_v_from_i`
174
+ x = np.array([0., 1.480501e-11, 0.178, 8000., 1.797559])
175
+ outs = pvsystem.singlediode(*x, method='lambertw')
176
+ assert outs['v_oc'] == 0
177
+
178
+ # Testing for an array
179
+ x = np.array([x, x]).T
180
+ outs = pvsystem.singlediode(*x, method='lambertw')
181
+ assert np.array_equal(outs['v_oc'], [0, 0])
182
+
183
+
184
+ @pytest.mark.parametrize('method', ['lambertw'])
185
+ def test_ivcurve_pnts_precision(method, precise_iv_curves):
186
+ """
187
+ Tests the accuracy of the IV curve points calcuated by singlediode. Only
188
+ methods of singlediode that linearly spaced points are tested.
189
+ """
190
+ x, pc = precise_iv_curves
191
+ pc_i, pc_v = np.stack(pc['Currents']), np.stack(pc['Voltages'])
192
+ ivcurve_pnts = len(pc['Currents'][0])
193
+
194
+ with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
195
+ outs = pvsystem.singlediode(method=method, ivcurve_pnts=ivcurve_pnts,
196
+ **x)
197
+
198
+ assert np.allclose(pc_i, outs['i'], atol=1e-10, rtol=0)
199
+ assert np.allclose(pc_v, outs['v'], atol=1e-10, rtol=0)
200
+
201
+
202
+ @pytest.mark.parametrize('method', ['lambertw', 'brentq', 'newton'])
203
+ def test_v_from_i_i_from_v_precision(method, precise_iv_curves):
204
+ """
205
+ Tests the accuracy of pvsystem.v_from_i and pvsystem.i_from_v.
206
+ """
207
+ x, pc = precise_iv_curves
208
+ pc_i, pc_v = pc['Currents'], pc['Voltages']
209
+ for i, v, (_, x_one_curve) in zip(pc_i, pc_v, x.iterrows()):
210
+ out_i = pvsystem.i_from_v(voltage=v, method=method, **x_one_curve)
211
+ out_v = pvsystem.v_from_i(current=i, method=method, **x_one_curve)
212
+
213
+ assert np.allclose(i, out_i, atol=1e-10, rtol=0)
214
+ assert np.allclose(v, out_v, atol=1e-10, rtol=0)
70
215
 
71
216
 
72
217
  def get_pvsyst_fs_495():
@@ -280,3 +425,135 @@ def test_pvsyst_breakdown(method, brk_params, recomb_params, poa, temp_cell,
280
425
 
281
426
  vsc_88 = bishop88_v_from_i(isc_88, *x, **y, method=method)
282
427
  assert np.isclose(vsc_88, 0.0, *tol)
428
+
429
+
430
+ @pytest.fixture
431
+ def bishop88_arguments():
432
+ pvsyst_fs_495 = get_pvsyst_fs_495()
433
+ # evaluate PVSyst model with thin-film recombination loss current
434
+ # at reference conditions
435
+ x = pvsystem.calcparams_pvsyst(
436
+ effective_irradiance=pvsyst_fs_495['irrad_ref'],
437
+ temp_cell=pvsyst_fs_495['temp_ref'],
438
+ alpha_sc=pvsyst_fs_495['alpha_sc'],
439
+ gamma_ref=pvsyst_fs_495['gamma_ref'],
440
+ mu_gamma=pvsyst_fs_495['mu_gamma'], I_L_ref=pvsyst_fs_495['I_L_ref'],
441
+ I_o_ref=pvsyst_fs_495['I_o_ref'], R_sh_ref=pvsyst_fs_495['R_sh_ref'],
442
+ R_sh_0=pvsyst_fs_495['R_sh_0'], R_sh_exp=pvsyst_fs_495['R_sh_exp'],
443
+ R_s=pvsyst_fs_495['R_s'],
444
+ cells_in_series=pvsyst_fs_495['cells_in_series'],
445
+ EgRef=pvsyst_fs_495['EgRef']
446
+ )
447
+ y = dict(d2mutau=pvsyst_fs_495['d2mutau'],
448
+ NsVbi=VOLTAGE_BUILTIN*pvsyst_fs_495['cells_in_series'])
449
+ # Convert (*x, **y) in a bishop88_.* call to dict of arguments
450
+ args_dict = {
451
+ 'photocurrent': x[0],
452
+ 'saturation_current': x[1],
453
+ 'resistance_series': x[2],
454
+ 'resistance_shunt': x[3],
455
+ 'nNsVth': x[4],
456
+ }
457
+ args_dict.update(y)
458
+ return args_dict
459
+
460
+
461
+ @pytest.mark.parametrize('method, method_kwargs', [
462
+ ('newton', {
463
+ 'tol': 1e-8,
464
+ 'rtol': 1e-8,
465
+ 'maxiter': 30,
466
+ }),
467
+ ('brentq', {
468
+ 'xtol': 1e-8,
469
+ 'rtol': 1e-8,
470
+ 'maxiter': 30,
471
+ })
472
+ ])
473
+ def test_bishop88_kwargs_transfer(method, method_kwargs, mocker,
474
+ bishop88_arguments):
475
+ """test method_kwargs modifying optimizer does not break anything"""
476
+ # patch method namespace at singlediode module namespace
477
+ optimizer_mock = mocker.patch('pvlib.singlediode.' + method)
478
+
479
+ # check kwargs passed to bishop_.* are a subset of the call args
480
+ # since they are called with more keyword arguments
481
+
482
+ bishop88_i_from_v(0, **bishop88_arguments, method=method,
483
+ method_kwargs=method_kwargs)
484
+ _, kwargs = optimizer_mock.call_args
485
+ assert method_kwargs.items() <= kwargs.items()
486
+
487
+ bishop88_v_from_i(0, **bishop88_arguments, method=method,
488
+ method_kwargs=method_kwargs)
489
+ _, kwargs = optimizer_mock.call_args
490
+ assert method_kwargs.items() <= kwargs.items()
491
+
492
+ bishop88_mpp(**bishop88_arguments, method=method,
493
+ method_kwargs=method_kwargs)
494
+ _, kwargs = optimizer_mock.call_args
495
+ assert method_kwargs.items() <= kwargs.items()
496
+
497
+
498
+ @pytest.mark.parametrize('method, method_kwargs', [
499
+ ('newton', {
500
+ 'tol': 1e-4,
501
+ 'rtol': 1e-4,
502
+ 'maxiter': 20,
503
+ '_inexistent_param': "0.01"
504
+ }),
505
+ ('brentq', {
506
+ 'xtol': 1e-4,
507
+ 'rtol': 1e-4,
508
+ 'maxiter': 20,
509
+ '_inexistent_param': "0.01"
510
+ })
511
+ ])
512
+ def test_bishop88_kwargs_fails(method, method_kwargs, bishop88_arguments):
513
+ """test invalid method_kwargs passed onto the optimizer fail"""
514
+
515
+ pytest.raises(TypeError, bishop88_i_from_v,
516
+ 0, **bishop88_arguments, method=method,
517
+ method_kwargs=method_kwargs)
518
+
519
+ pytest.raises(TypeError, bishop88_v_from_i,
520
+ 0, **bishop88_arguments, method=method,
521
+ method_kwargs=method_kwargs)
522
+
523
+ pytest.raises(TypeError, bishop88_mpp,
524
+ **bishop88_arguments, method=method,
525
+ method_kwargs=method_kwargs)
526
+
527
+
528
+ @pytest.mark.parametrize('method', ['newton', 'brentq'])
529
+ def test_bishop88_full_output_kwarg(method, bishop88_arguments):
530
+ """test call to bishop88_.* with full_output=True return values are ok"""
531
+ method_kwargs = {'full_output': True}
532
+
533
+ ret_val = bishop88_i_from_v(0, **bishop88_arguments, method=method,
534
+ method_kwargs=method_kwargs)
535
+ assert isinstance(ret_val, tuple) # ret_val must be a tuple
536
+ assert len(ret_val) == 2 # of two elements
537
+ assert isinstance(ret_val[0], float) # first one has bishop88 result
538
+ assert isinstance(ret_val[1], tuple) # second is output from optimizer
539
+ # any root finder returns at least 2 elements with full_output=True
540
+ assert len(ret_val[1]) >= 2
541
+
542
+ ret_val = bishop88_v_from_i(0, **bishop88_arguments, method=method,
543
+ method_kwargs=method_kwargs)
544
+ assert isinstance(ret_val, tuple) # ret_val must be a tuple
545
+ assert len(ret_val) == 2 # of two elements
546
+ assert isinstance(ret_val[0], float) # first one has bishop88 result
547
+ assert isinstance(ret_val[1], tuple) # second is output from optimizer
548
+ # any root finder returns at least 2 elements with full_output=True
549
+ assert len(ret_val[1]) >= 2
550
+
551
+ ret_val = bishop88_mpp(**bishop88_arguments, method=method,
552
+ method_kwargs=method_kwargs)
553
+ assert isinstance(ret_val, tuple) # ret_val must be a tuple
554
+ assert len(ret_val) == 2 # of two elements
555
+ assert isinstance(ret_val[0], tuple) # first one has bishop88 result
556
+ assert len(ret_val[0]) == 3 # of three elements (I,V,P)
557
+ assert isinstance(ret_val[1], tuple) # second is output from optimizer
558
+ # any root finder returns at least 2 elements with full_output=True
559
+ assert len(ret_val[1]) >= 2
pvlib/tests/test_snow.py CHANGED
@@ -6,6 +6,8 @@ from .conftest import assert_series_equal
6
6
  from pvlib import snow
7
7
  from pvlib.tools import sind
8
8
 
9
+ import pytest
10
+
9
11
 
10
12
  def test_fully_covered_nrel():
11
13
  dt = pd.date_range(start="2019-1-1 12:00:00", end="2019-1-1 18:00:00",
@@ -108,6 +110,7 @@ def test__townsend_effective_snow():
108
110
 
109
111
 
110
112
  def test_loss_townsend():
113
+ # hand-calculated solution
111
114
  snow_total = np.array([25.4, 25.4, 12.7, 2.54, 0, 0, 0, 0, 0, 0, 12.7,
112
115
  25.4])
113
116
  snow_events = np.array([2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3])
@@ -118,6 +121,7 @@ def test_loss_townsend():
118
121
  poa_global = np.array([350000, 350000, 350000, 350000, 350000, 350000,
119
122
  350000, 350000, 350000, 350000, 350000, 350000])
120
123
  angle_of_repose = 40
124
+ string_factor = 1.0
121
125
  slant_height = 2.54
122
126
  lower_edge_height = 0.254
123
127
  expected = np.array([0.07696253, 0.07992262, 0.06216201, 0.01715392, 0, 0,
@@ -125,5 +129,84 @@ def test_loss_townsend():
125
129
  actual = snow.loss_townsend(snow_total, snow_events, surface_tilt,
126
130
  relative_humidity, temp_air,
127
131
  poa_global, slant_height,
128
- lower_edge_height, angle_of_repose)
132
+ lower_edge_height, string_factor,
133
+ angle_of_repose)
129
134
  np.testing.assert_allclose(expected, actual, rtol=1e-05)
135
+
136
+
137
+ @pytest.mark.parametrize(
138
+ 'poa_global,surface_tilt,slant_height,lower_edge_height,string_factor,expected', # noQA: E501
139
+ [
140
+ (np.asarray(
141
+ [60., 80., 100., 125., 175., 225., 225., 210., 175., 125., 90.,
142
+ 60.], dtype=float) * 1000.,
143
+ 2.,
144
+ 79. / 39.37,
145
+ 3. / 39.37,
146
+ 1.0,
147
+ np.asarray(
148
+ [44, 34, 20, 9, 3, 1, 0, 0, 0, 2, 6, 25], dtype=float)
149
+ ),
150
+ (np.asarray(
151
+ [60., 80., 100., 125., 175., 225., 225., 210., 175., 125., 90.,
152
+ 60.], dtype=float) * 1000.,
153
+ 5.,
154
+ 316 / 39.37,
155
+ 120. / 39.37,
156
+ 0.75,
157
+ np.asarray(
158
+ [22, 16, 9, 4, 1, 0, 0, 0, 0, 1, 2, 12], dtype=float)
159
+ ),
160
+ (np.asarray(
161
+ [60., 80., 100., 125., 175., 225., 225., 210., 175., 125., 90.,
162
+ 60.], dtype=float) * 1000.,
163
+ 23.,
164
+ 158 / 39.27,
165
+ 12 / 39.37,
166
+ 0.75,
167
+ np.asarray(
168
+ [28, 21, 13, 6, 2, 0, 0, 0, 0, 1, 4, 16], dtype=float)
169
+ ),
170
+ (np.asarray(
171
+ [80., 100., 125., 150., 225., 300., 300., 275., 225., 150., 115.,
172
+ 80.], dtype=float) * 1000.,
173
+ 52.,
174
+ 39.5 / 39.37,
175
+ 34. / 39.37,
176
+ 0.75,
177
+ np.asarray(
178
+ [7, 5, 3, 1, 0, 0, 0, 0, 0, 0, 1, 4], dtype=float)
179
+ ),
180
+ (np.asarray(
181
+ [80., 100., 125., 150., 225., 300., 300., 275., 225., 150., 115.,
182
+ 80.], dtype=float) * 1000.,
183
+ 60.,
184
+ 39.5 / 39.37,
185
+ 25. / 39.37,
186
+ 1.,
187
+ np.asarray(
188
+ [7, 5, 3, 1, 0, 0, 0, 0, 0, 0, 1, 3], dtype=float)
189
+ )
190
+ ]
191
+ )
192
+ def test_loss_townsend_cases(poa_global, surface_tilt, slant_height,
193
+ lower_edge_height, string_factor, expected):
194
+ # test cases from Townsend, 1/27/2023, addeed by cwh
195
+ # snow_total in inches, convert to cm for pvlib
196
+ snow_total = np.asarray(
197
+ [20, 15, 10, 4, 1.5, 0, 0, 0, 0, 1.5, 4, 15], dtype=float) * 2.54
198
+ # snow events are an average for each month
199
+ snow_events = np.asarray(
200
+ [5, 4.2, 2.8, 1.3, 0.8, 0, 0, 0, 0, 0.5, 1.5, 4.5], dtype=float)
201
+ # air temperature in C
202
+ temp_air = np.asarray(
203
+ [-6., -2., 1., 4., 7., 10., 13., 16., 14., 12., 7., -3.], dtype=float)
204
+ # relative humidity in %
205
+ relative_humidity = np.asarray(
206
+ [78., 80., 75., 65., 60., 55., 55., 55., 50., 55., 60., 70.],
207
+ dtype=float)
208
+ actual = snow.loss_townsend(
209
+ snow_total, snow_events, surface_tilt, relative_humidity, temp_air,
210
+ poa_global, slant_height, lower_edge_height, string_factor)
211
+ actual = np.around(actual * 100)
212
+ assert np.allclose(expected, actual)
@@ -92,7 +92,7 @@ def test_hsu_no_cleaning(rainfall_input, expected_output):
92
92
  tilt = 0.
93
93
  expected_no_cleaning = expected_output
94
94
 
95
- result = hsu(rainfall=rainfall, cleaning_threshold=10., tilt=tilt,
95
+ result = hsu(rainfall=rainfall, cleaning_threshold=10., surface_tilt=tilt,
96
96
  pm2_5=pm2_5, pm10=pm10, depo_veloc=depo_veloc,
97
97
  rain_accum_period=pd.Timedelta('1h'))
98
98
  assert_series_equal(result, expected_no_cleaning)
@@ -108,7 +108,7 @@ def test_hsu(rainfall_input, expected_output_2):
108
108
  tilt = 0.
109
109
 
110
110
  # three cleaning events at 4:00-6:00, 8:00-11:00, and 17:00-20:00
111
- result = hsu(rainfall=rainfall, cleaning_threshold=0.5, tilt=tilt,
111
+ result = hsu(rainfall=rainfall, cleaning_threshold=0.5, surface_tilt=tilt,
112
112
  pm2_5=pm2_5, pm10=pm10, depo_veloc=depo_veloc,
113
113
  rain_accum_period=pd.Timedelta('3h'))
114
114
 
@@ -120,8 +120,8 @@ def test_hsu_defaults(rainfall_input, expected_output_1):
120
120
  Test Soiling HSU function with default deposition velocity and default rain
121
121
  accumulation period.
122
122
  """
123
- result = hsu(rainfall=rainfall_input, cleaning_threshold=0.5, tilt=0.0,
124
- pm2_5=1.0e-2, pm10=2.0e-2)
123
+ result = hsu(rainfall=rainfall_input, cleaning_threshold=0.5,
124
+ surface_tilt=0.0, pm2_5=1.0e-2, pm10=2.0e-2)
125
125
  assert np.allclose(result.values, expected_output_1)
126
126
 
127
127
 
@@ -138,7 +138,7 @@ def test_hsu_variable_time_intervals(rainfall_input, expected_output_3):
138
138
  rain['new_time'] = rain.index + rain['mins_added']
139
139
  rain_var_times = rain.set_index('new_time').iloc[:, 0]
140
140
  result = hsu(
141
- rainfall=rain_var_times, cleaning_threshold=0.5, tilt=50.0,
141
+ rainfall=rain_var_times, cleaning_threshold=0.5, surface_tilt=50.0,
142
142
  pm2_5=1, pm10=2, depo_veloc=depo_veloc,
143
143
  rain_accum_period=pd.Timedelta('2h'))
144
144
  assert np.allclose(result, expected_output_3)
@@ -147,8 +147,9 @@ def test_hsu_variable_time_intervals(rainfall_input, expected_output_3):
147
147
  @pytest.fixture
148
148
  def greensboro_rain():
149
149
  # get TMY3 data with rain
150
- greensboro, _ = read_tmy3(DATA_DIR / '723170TYA.CSV', coerce_year=1990)
151
- return greensboro.Lprecipdepth
150
+ greensboro, _ = read_tmy3(DATA_DIR / '723170TYA.CSV', coerce_year=1990,
151
+ map_variables=True)
152
+ return greensboro['Lprecip depth (mm)']
152
153
 
153
154
 
154
155
  @pytest.fixture
@@ -163,7 +163,7 @@ def test_sun_rise_set_transit_spa(expected_rise_set_spa, golden):
163
163
  result_rounded = pd.DataFrame(index=result.index)
164
164
  # need to iterate because to_datetime does not accept 2D data
165
165
  # the rounding fails on pandas < 0.17
166
- for col, data in result.iteritems():
166
+ for col, data in result.items():
167
167
  result_rounded[col] = data.dt.round('1s')
168
168
 
169
169
  assert_frame_equal(frame, result_rounded)
@@ -176,7 +176,7 @@ def test_sun_rise_set_transit_spa(expected_rise_set_spa, golden):
176
176
  # round to nearest minute
177
177
  result_rounded = pd.DataFrame(index=result.index)
178
178
  # need to iterate because to_datetime does not accept 2D data
179
- for col, data in result.iteritems():
179
+ for col, data in result.items():
180
180
  result_rounded[col] = data.dt.round('s').tz_convert('MST')
181
181
 
182
182
  assert_frame_equal(expected_rise_set_spa, result_rounded)
@@ -191,7 +191,7 @@ def test_sun_rise_set_transit_ephem(expected_rise_set_ephem, golden):
191
191
  temperature=11, horizon='-0:34')
192
192
  # round to nearest minute
193
193
  result_rounded = pd.DataFrame(index=result.index)
194
- for col, data in result.iteritems():
194
+ for col, data in result.items():
195
195
  result_rounded[col] = data.dt.round('min').tz_convert('MST')
196
196
  assert_frame_equal(expected_rise_set_ephem, result_rounded)
197
197
 
@@ -227,7 +227,7 @@ def test_sun_rise_set_transit_ephem(expected_rise_set_ephem, golden):
227
227
  horizon='-0:34')
228
228
  # round to nearest minute
229
229
  result_rounded = pd.DataFrame(index=result.index)
230
- for col, data in result.iteritems():
230
+ for col, data in result.items():
231
231
  result_rounded[col] = data.dt.round('min').tz_convert('MST')
232
232
  assert_frame_equal(expected, result_rounded)
233
233
 
@@ -259,14 +259,14 @@ def test_sun_rise_set_transit_ephem(expected_rise_set_ephem, golden):
259
259
  altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34')
260
260
  # round to nearest minute
261
261
  result_rounded = pd.DataFrame(index=result.index)
262
- for col, data in result.iteritems():
262
+ for col, data in result.items():
263
263
  result_rounded[col] = data.dt.round('min').tz_convert('MST')
264
264
  assert_frame_equal(expected, result_rounded)
265
265
 
266
266
  # test with different timezone
267
267
  times = times.tz_convert('UTC')
268
268
  expected = expected.tz_convert('UTC') # resuse result from previous
269
- for col, data in expected.iteritems():
269
+ for col, data in expected.items():
270
270
  expected[col] = data.dt.tz_convert('UTC')
271
271
  result = solarposition.sun_rise_set_transit_ephem(
272
272
  times,
@@ -274,7 +274,7 @@ def test_sun_rise_set_transit_ephem(expected_rise_set_ephem, golden):
274
274
  altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34')
275
275
  # round to nearest minute
276
276
  result_rounded = pd.DataFrame(index=result.index)
277
- for col, data in result.iteritems():
277
+ for col, data in result.items():
278
278
  result_rounded[col] = data.dt.round('min').tz_convert(times.tz)
279
279
  assert_frame_equal(expected, result_rounded)
280
280
 
pvlib/tests/test_spa.py CHANGED
@@ -154,13 +154,12 @@ class SpaBase:
154
154
  def test_moon_ascending_longitude(self):
155
155
  assert_almost_equal(X4, self.spa.moon_ascending_longitude(JCE), 6)
156
156
 
157
- def test_longitude_nutation(self):
158
- assert_almost_equal(dPsi, self.spa.longitude_nutation(
159
- JCE, X0, X1, X2, X3, X4), 6)
160
-
161
- def test_obliquity_nutation(self):
162
- assert_almost_equal(dEpsilon, self.spa.obliquity_nutation(
163
- JCE, X0, X1, X2, X3, X4), 6)
157
+ def test_longitude_obliquity_nutation(self):
158
+ out = np.empty((2,))
159
+ self.spa.longitude_obliquity_nutation(JCE, X0, X1, X2, X3, X4, out)
160
+ _dPsi, _dEpsilon = out[0], out[1]
161
+ assert_almost_equal(dPsi, _dPsi, 6)
162
+ assert_almost_equal(dEpsilon, _dEpsilon, 6)
164
163
 
165
164
  def test_mean_ecliptic_obliquity(self):
166
165
  assert_almost_equal(epsilon0, self.spa.mean_ecliptic_obliquity(JME), 6)