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.
- pvlib/__init__.py +3 -2
- pvlib/atmosphere.py +23 -173
- pvlib/bifacial/infinite_sheds.py +88 -277
- pvlib/bifacial/utils.py +270 -28
- pvlib/data/adr-library-cec-inverters-2019-03-05.csv +5009 -0
- pvlib/data/precise_iv_curves1.json +10251 -0
- pvlib/data/precise_iv_curves2.json +10251 -0
- pvlib/data/precise_iv_curves_parameter_sets1.csv +33 -0
- pvlib/data/precise_iv_curves_parameter_sets2.csv +33 -0
- pvlib/data/test_psm3_2017.csv +17521 -17521
- pvlib/data/test_psm3_2019_5min.csv +288 -288
- pvlib/data/test_read_psm3.csv +17522 -17522
- pvlib/data/test_read_pvgis_horizon.csv +49 -0
- pvlib/data/variables_style_rules.csv +3 -0
- pvlib/iam.py +207 -51
- pvlib/inverter.py +6 -1
- pvlib/iotools/__init__.py +7 -2
- pvlib/iotools/acis.py +516 -0
- pvlib/iotools/midc.py +4 -4
- pvlib/iotools/psm3.py +59 -42
- pvlib/iotools/pvgis.py +84 -28
- pvlib/iotools/sodapro.py +8 -6
- pvlib/iotools/srml.py +121 -18
- pvlib/iotools/surfrad.py +2 -2
- pvlib/iotools/tmy.py +146 -102
- pvlib/irradiance.py +270 -15
- pvlib/ivtools/sde.py +14 -20
- pvlib/ivtools/sdm.py +31 -20
- pvlib/ivtools/utils.py +127 -6
- pvlib/location.py +3 -2
- pvlib/modelchain.py +67 -70
- pvlib/pvarray.py +225 -0
- pvlib/pvsystem.py +169 -539
- pvlib/shading.py +43 -2
- pvlib/singlediode.py +216 -66
- pvlib/snow.py +36 -15
- pvlib/soiling.py +3 -3
- pvlib/spa.py +327 -368
- pvlib/spectrum/__init__.py +8 -2
- pvlib/spectrum/mismatch.py +335 -0
- pvlib/temperature.py +124 -13
- pvlib/tests/bifacial/test_infinite_sheds.py +44 -106
- pvlib/tests/bifacial/test_utils.py +102 -5
- pvlib/tests/conftest.py +0 -31
- pvlib/tests/iotools/test_acis.py +213 -0
- pvlib/tests/iotools/test_midc.py +6 -6
- pvlib/tests/iotools/test_psm3.py +7 -5
- pvlib/tests/iotools/test_pvgis.py +21 -14
- pvlib/tests/iotools/test_sodapro.py +1 -1
- pvlib/tests/iotools/test_srml.py +71 -6
- pvlib/tests/iotools/test_tmy.py +43 -8
- pvlib/tests/ivtools/test_sde.py +19 -17
- pvlib/tests/ivtools/test_sdm.py +9 -4
- pvlib/tests/ivtools/test_utils.py +96 -1
- pvlib/tests/test_atmosphere.py +8 -64
- pvlib/tests/test_clearsky.py +0 -1
- pvlib/tests/test_iam.py +74 -1
- pvlib/tests/test_irradiance.py +56 -2
- pvlib/tests/test_location.py +1 -1
- pvlib/tests/test_modelchain.py +33 -76
- pvlib/tests/test_pvarray.py +46 -0
- pvlib/tests/test_pvsystem.py +366 -201
- pvlib/tests/test_shading.py +35 -0
- pvlib/tests/test_singlediode.py +306 -29
- pvlib/tests/test_snow.py +84 -1
- pvlib/tests/test_soiling.py +8 -7
- pvlib/tests/test_solarposition.py +7 -7
- pvlib/tests/test_spa.py +6 -7
- pvlib/tests/test_spectrum.py +145 -1
- pvlib/tests/test_temperature.py +29 -11
- pvlib/tests/test_tools.py +41 -0
- pvlib/tests/test_tracking.py +0 -149
- pvlib/tools.py +49 -25
- pvlib/tracking.py +1 -269
- pvlib-0.10.0.dist-info/AUTHORS.md +35 -0
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/LICENSE +5 -2
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/METADATA +3 -13
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/RECORD +80 -75
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/WHEEL +1 -1
- pvlib/data/adr-library-2013-10-01.csv +0 -1762
- pvlib/forecast.py +0 -1211
- pvlib/iotools/ecmwf_macc.py +0 -312
- pvlib/tests/iotools/test_ecmwf_macc.py +0 -162
- pvlib/tests/test_forecast.py +0 -228
- pvlib-0.9.4a1.dist-info/AUTHORS.md +0 -32
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/top_level.txt +0 -0
pvlib/tests/test_spectrum.py
CHANGED
|
@@ -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)
|
pvlib/tests/test_temperature.py
CHANGED
|
@@ -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,
|
|
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,
|
|
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)
|
pvlib/tests/test_tracking.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
#
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
+
)
|