pvlib 0.10.5__py3-none-any.whl → 0.11.0a1__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 +1 -0
- pvlib/albedo.py +168 -0
- pvlib/data/ASTMG173.csv +2004 -0
- pvlib/iam.py +28 -28
- pvlib/iotools/__init__.py +0 -1
- pvlib/iotools/midc.py +15 -10
- pvlib/iotools/psm3.py +10 -25
- pvlib/iotools/srml.py +0 -61
- pvlib/irradiance.py +133 -95
- pvlib/location.py +13 -5
- pvlib/modelchain.py +2 -165
- pvlib/pvsystem.py +23 -63
- pvlib/shading.py +350 -0
- pvlib/spectrum/__init__.py +5 -0
- pvlib/spectrum/mismatch.py +572 -43
- pvlib/spectrum/spectrl2.py +8 -8
- pvlib/tests/iotools/test_psm3.py +0 -18
- pvlib/tests/iotools/test_srml.py +1 -43
- pvlib/tests/test_albedo.py +84 -0
- pvlib/tests/test_inverter.py +2 -2
- pvlib/tests/test_irradiance.py +35 -2
- pvlib/tests/test_location.py +26 -18
- pvlib/tests/test_modelchain.py +0 -57
- pvlib/tests/test_pvsystem.py +11 -39
- pvlib/tests/test_shading.py +167 -1
- pvlib/tests/test_singlediode.py +0 -19
- pvlib/tests/test_spectrum.py +283 -22
- pvlib/tests/test_temperature.py +7 -7
- pvlib/tests/test_tools.py +24 -0
- pvlib/tests/test_transformer.py +60 -0
- pvlib/tools.py +27 -0
- pvlib/transformer.py +117 -0
- {pvlib-0.10.5.dist-info → pvlib-0.11.0a1.dist-info}/METADATA +1 -1
- {pvlib-0.10.5.dist-info → pvlib-0.11.0a1.dist-info}/RECORD +38 -34
- {pvlib-0.10.5.dist-info → pvlib-0.11.0a1.dist-info}/WHEEL +1 -1
- pvlib/data/astm_g173_am15g.csv +0 -2003
- {pvlib-0.10.5.dist-info → pvlib-0.11.0a1.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.10.5.dist-info → pvlib-0.11.0a1.dist-info}/LICENSE +0 -0
- {pvlib-0.10.5.dist-info → pvlib-0.11.0a1.dist-info}/top_level.txt +0 -0
pvlib/tests/test_shading.py
CHANGED
|
@@ -2,11 +2,12 @@ import numpy as np
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
|
|
4
4
|
from pandas.testing import assert_series_equal
|
|
5
|
-
from numpy.testing import assert_allclose
|
|
5
|
+
from numpy.testing import assert_allclose, assert_approx_equal
|
|
6
6
|
import pytest
|
|
7
7
|
from datetime import timezone, timedelta
|
|
8
8
|
|
|
9
9
|
from pvlib import shading
|
|
10
|
+
from pvlib.tools import atand
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@pytest.fixture
|
|
@@ -223,3 +224,168 @@ def test_projected_solar_zenith_angle_datatypes(
|
|
|
223
224
|
)
|
|
224
225
|
psz = psz_func(sun_apparent_zenith, axis_azimuth, axis_tilt, axis_azimuth)
|
|
225
226
|
assert isinstance(psz, cast_type)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@pytest.fixture
|
|
230
|
+
def sf1d_premises_and_expected():
|
|
231
|
+
"""Data comprised of solar position, rows parameters and terrain slope
|
|
232
|
+
with respective shade fractions (sf). Returns a 2-tuple with the premises
|
|
233
|
+
to be used directly in shaded_fraction1d(...) in the first element and
|
|
234
|
+
the expected shaded fractions in the second element.
|
|
235
|
+
See [1] in shaded_fraction1d()
|
|
236
|
+
Test data sourced from http://doi.org/10.5281/zenodo.10513987
|
|
237
|
+
"""
|
|
238
|
+
test_data = pd.DataFrame(
|
|
239
|
+
columns=["x_L", "z_L", "theta_L", "x_R", "z_R", "theta_R", "z_0", "l",
|
|
240
|
+
"theta_s", "f_s"],
|
|
241
|
+
data=(
|
|
242
|
+
(1, 0.2, 50, 0, 0, 25, 0, 0.5, 80, 1),
|
|
243
|
+
(1, 0.1, 50, 0, 0, 25, 0.05, 0.5, 80, 0.937191),
|
|
244
|
+
(1, 0, 50, 0, 0.1, 25, 0, 0.5, 80, 0.30605),
|
|
245
|
+
(1, 0, 50, 0, 0.2, 25, 0, 0.5, 80, 0),
|
|
246
|
+
(1, 0.2, -25, 0, 0, -50, 0, 0.5, -80, 0),
|
|
247
|
+
(1, 0.1, -25, 0, 0, -50, 0, 0.5, -80, 0.30605),
|
|
248
|
+
(1, 0, -25, 0, 0.1, -50, 0.1, 0.5, -80, 0.881549),
|
|
249
|
+
(1, 0, -25, 0, 0.2, -50, 0, 0.5, -80, 1),
|
|
250
|
+
(1, 0.2, 5, 0, 0, 25, 0.05, 0.5, 80, 0.832499),
|
|
251
|
+
(1, 0.2, -25, 0, 0, 25, 0.05, 0.5, 80, 0.832499),
|
|
252
|
+
(1, 0.2, 5, 0, 0, -45, 0.05, 0.5, 80, 0.832499),
|
|
253
|
+
(1, 0.2, -25, 0, 0, -45, 0.05, 0.5, 80, 0.832499),
|
|
254
|
+
(1, 0, -25, 0, 0.2, 25, 0.05, 0.5, -80, 0.832499),
|
|
255
|
+
(1, 0, -25, 0, 0.2, -5, 0.05, 0.5, -80, 0.832499),
|
|
256
|
+
(1, 0, 45, 0, 0.2, 25, 0.05, 0.5, -80, 0.832499),
|
|
257
|
+
(1, 0, 45, 0, 0.2, -5, 0.05, 0.5, -80, 0.832499),
|
|
258
|
+
),
|
|
259
|
+
) # fmt: skip
|
|
260
|
+
|
|
261
|
+
test_data["cross_axis_slope"] = atand(
|
|
262
|
+
(test_data["z_R"] - test_data["z_L"])
|
|
263
|
+
/ (test_data["x_L"] - test_data["x_R"])
|
|
264
|
+
)
|
|
265
|
+
test_data["pitch"] = test_data["x_L"] - test_data["x_R"]
|
|
266
|
+
# switch Left/Right rows if needed to make the right one the shaded
|
|
267
|
+
where_switch = test_data["theta_s"] >= 0
|
|
268
|
+
test_data["theta_L"], test_data["theta_R"] = np.where(
|
|
269
|
+
where_switch,
|
|
270
|
+
(test_data["theta_L"], test_data["theta_R"]),
|
|
271
|
+
(test_data["theta_R"], test_data["theta_L"]),
|
|
272
|
+
)
|
|
273
|
+
test_data.rename(
|
|
274
|
+
columns={
|
|
275
|
+
"theta_L": "shading_row_rotation",
|
|
276
|
+
"theta_R": "shaded_row_rotation",
|
|
277
|
+
"z_0": "surface_to_axis_offset",
|
|
278
|
+
"l": "collector_width",
|
|
279
|
+
"theta_s": "solar_zenith", # for the projected solar zenith angle
|
|
280
|
+
"f_s": "shaded_fraction",
|
|
281
|
+
},
|
|
282
|
+
inplace=True,
|
|
283
|
+
)
|
|
284
|
+
test_data.drop(columns=["x_L", "z_L", "x_R", "z_R"], inplace=True)
|
|
285
|
+
# for the projected solar zenith angle
|
|
286
|
+
# this returns the same psz angle as test_data["solar_zenith"]
|
|
287
|
+
test_data["solar_azimuth"], test_data["axis_azimuth"] = 180, 90
|
|
288
|
+
|
|
289
|
+
# return 1st: premises dataframe first and 2nd: shaded fraction series
|
|
290
|
+
return (
|
|
291
|
+
test_data.drop(columns=["shaded_fraction"]),
|
|
292
|
+
test_data["shaded_fraction"],
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_shaded_fraction1d(sf1d_premises_and_expected):
|
|
297
|
+
"""Tests shaded_fraction1d"""
|
|
298
|
+
# unwrap sf_premises_and_expected values premises and expected results
|
|
299
|
+
premises, expected_sf_array = sf1d_premises_and_expected
|
|
300
|
+
# test scalar input
|
|
301
|
+
expected_result = expected_sf_array.iloc[0]
|
|
302
|
+
sf = shading.shaded_fraction1d(**premises.iloc[0])
|
|
303
|
+
assert_approx_equal(sf, expected_result)
|
|
304
|
+
assert isinstance(sf, float)
|
|
305
|
+
|
|
306
|
+
# test Series inputs
|
|
307
|
+
sf_vec = shading.shaded_fraction1d(**premises)
|
|
308
|
+
assert_allclose(sf_vec, expected_sf_array, atol=1e-6)
|
|
309
|
+
assert isinstance(sf_vec, pd.Series)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_shaded_fraction1d_unprovided_shading_row_rotation():
|
|
313
|
+
"""Tests shaded_fraction1d without providing shading_row_rotation"""
|
|
314
|
+
test_data = pd.DataFrame(
|
|
315
|
+
columns=[
|
|
316
|
+
"shaded_row_rotation", "surface_to_axis_offset", "collector_width",
|
|
317
|
+
"solar_zenith", "cross_axis_slope", "pitch", "solar_azimuth",
|
|
318
|
+
"axis_azimuth", "expected_sf",
|
|
319
|
+
],
|
|
320
|
+
data=[
|
|
321
|
+
(30, 0, 5.7735, 60, 0, 5, 90, 180, 0),
|
|
322
|
+
(30, 0, 5.7735, 79, 0, 5, 90, 180, 0.5),
|
|
323
|
+
(30, 0, 5.7735, 90, 0, 5, 90, 180, 1),
|
|
324
|
+
],
|
|
325
|
+
) # fmt: skip
|
|
326
|
+
expected_sf = test_data["expected_sf"]
|
|
327
|
+
premises = test_data.drop(columns=["expected_sf"])
|
|
328
|
+
sf = shading.shaded_fraction1d(**premises)
|
|
329
|
+
assert_allclose(sf, expected_sf, atol=1e-2)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@pytest.fixture
|
|
333
|
+
def direct_martinez_Table2():
|
|
334
|
+
"""
|
|
335
|
+
Original data used in [1] (see pvlib.shading.direct_martinez) to validate
|
|
336
|
+
the model. Some of the data is provided in Table 2.
|
|
337
|
+
Returns tuple with (input: pandas.DataFrame, output: pandas.Series)
|
|
338
|
+
Output is power loss: 1 - (P_shaded / P_unshaded)
|
|
339
|
+
"""
|
|
340
|
+
test_data = pd.DataFrame(
|
|
341
|
+
columns=[
|
|
342
|
+
"F_GS-H",
|
|
343
|
+
"F_GS-V",
|
|
344
|
+
"shaded_blocks",
|
|
345
|
+
"poa_direct",
|
|
346
|
+
"poa_diffuse",
|
|
347
|
+
"power_loss_model",
|
|
348
|
+
],
|
|
349
|
+
data=[
|
|
350
|
+
# F-H, F-V, Nsb, direct, diffuse, power_loss
|
|
351
|
+
# original data sourced from researchers
|
|
352
|
+
[1.00, 0.09, 16, 846.59, 59.42, 0.8844],
|
|
353
|
+
[1.00, 0.18, 16, 841.85, 59.69, 0.8888],
|
|
354
|
+
[1.00, 0.36, 16, 843.38, 59.22, 0.8994],
|
|
355
|
+
[0.04, 0.64, 1, 851.90, 59.40, 0.0783],
|
|
356
|
+
[0.17, 0.45, 3, 862.86, 58.40, 0.2237],
|
|
357
|
+
[0.29, 0.27, 5, 864.14, 58.11, 0.3282],
|
|
358
|
+
[0.50, 0.09, 8, 863.23, 58.31, 0.4634],
|
|
359
|
+
[0.13, 1.00, 2, 870.14, 58.02, 0.2137],
|
|
360
|
+
[0.25, 1.00, 4, 876.57, 57.98, 0.4000],
|
|
361
|
+
[0.38, 1.00, 6, 866.86, 58.89, 0.5577],
|
|
362
|
+
[0.50, 1.00, 8, 874.58, 58.44, 0.6892],
|
|
363
|
+
[0.58, 0.82, 10, 876.80, 58.16, 0.7359],
|
|
364
|
+
[0.75, 0.73, 12, 866.89, 58.73, 0.8113],
|
|
365
|
+
[0.92, 0.64, 15, 861.48, 59.66, 0.8894],
|
|
366
|
+
# custom edge cases
|
|
367
|
+
[0.00, 0.00, 0, 800.00, 50.00, 0.0000],
|
|
368
|
+
[1.00, 1.00, 16, 900.00, 00.00, 1.0000],
|
|
369
|
+
[0.00, 1.00, 16, 000.00, 00.00, np.nan],
|
|
370
|
+
[1.00, 0.00, 0, 000.00, 00.00, np.nan],
|
|
371
|
+
[1.00, 0.00, 0, -50.00, 50.00, np.nan], # zero poa_global
|
|
372
|
+
[1.00, 0.00, 0, 50.00, -50.00, np.nan], # zero poa_global
|
|
373
|
+
]
|
|
374
|
+
) # fmt: skip
|
|
375
|
+
test_data["total_blocks"] = 16 # total blocks is 16 for all cases
|
|
376
|
+
test_data["shaded_fraction"] = test_data["F_GS-H"] * test_data["F_GS-V"]
|
|
377
|
+
test_data["poa_global"] = (
|
|
378
|
+
test_data["poa_direct"] + test_data["poa_diffuse"]
|
|
379
|
+
)
|
|
380
|
+
test_data = test_data.drop(columns=["F_GS-H", "F_GS-V", "poa_diffuse"])
|
|
381
|
+
return (
|
|
382
|
+
test_data.drop(columns="power_loss_model"),
|
|
383
|
+
test_data["power_loss_model"],
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def test_direct_martinez(direct_martinez_Table2):
|
|
388
|
+
"""Tests pvlib.shading.direct_martinez"""
|
|
389
|
+
test_data, power_losses_expected = direct_martinez_Table2
|
|
390
|
+
power_losses = shading.direct_martinez(**test_data)
|
|
391
|
+
assert_allclose(power_losses, power_losses_expected, atol=5e-3)
|
pvlib/tests/test_singlediode.py
CHANGED
|
@@ -8,7 +8,6 @@ import scipy
|
|
|
8
8
|
from pvlib import pvsystem
|
|
9
9
|
from pvlib.singlediode import (bishop88_mpp, estimate_voc, VOLTAGE_BUILTIN,
|
|
10
10
|
bishop88, bishop88_i_from_v, bishop88_v_from_i)
|
|
11
|
-
from pvlib._deprecation import pvlibDeprecationWarning
|
|
12
11
|
import pytest
|
|
13
12
|
from numpy.testing import assert_array_equal
|
|
14
13
|
from .conftest import DATA_DIR
|
|
@@ -188,24 +187,6 @@ def test_singlediode_lambert_negative_voc(mocker):
|
|
|
188
187
|
assert_array_equal(outs["v_oc"], [0, 0])
|
|
189
188
|
|
|
190
189
|
|
|
191
|
-
@pytest.mark.parametrize('method', ['lambertw'])
|
|
192
|
-
def test_ivcurve_pnts_precision(method, precise_iv_curves):
|
|
193
|
-
"""
|
|
194
|
-
Tests the accuracy of the IV curve points calcuated by singlediode. Only
|
|
195
|
-
methods of singlediode that linearly spaced points are tested.
|
|
196
|
-
"""
|
|
197
|
-
x, pc = precise_iv_curves
|
|
198
|
-
pc_i, pc_v = np.stack(pc['Currents']), np.stack(pc['Voltages'])
|
|
199
|
-
ivcurve_pnts = len(pc['Currents'][0])
|
|
200
|
-
|
|
201
|
-
with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
|
|
202
|
-
outs = pvsystem.singlediode(method=method, ivcurve_pnts=ivcurve_pnts,
|
|
203
|
-
**x)
|
|
204
|
-
|
|
205
|
-
assert np.allclose(pc_i, outs['i'], atol=1e-10, rtol=0)
|
|
206
|
-
assert np.allclose(pc_v, outs['v'], atol=1e-10, rtol=0)
|
|
207
|
-
|
|
208
|
-
|
|
209
190
|
@pytest.mark.parametrize('method', ['lambertw', 'brentq', 'newton'])
|
|
210
191
|
def test_v_from_i_i_from_v_precision(method, precise_iv_curves):
|
|
211
192
|
"""
|
pvlib/tests/test_spectrum.py
CHANGED
|
@@ -3,11 +3,13 @@ from numpy.testing import assert_allclose, assert_approx_equal, assert_equal
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import numpy as np
|
|
5
5
|
from pvlib import spectrum
|
|
6
|
+
from pvlib._deprecation import pvlibDeprecationWarning
|
|
6
7
|
|
|
7
|
-
from .conftest import DATA_DIR, assert_series_equal
|
|
8
|
+
from .conftest import DATA_DIR, assert_series_equal, fail_on_pvlib_version
|
|
8
9
|
|
|
9
10
|
SPECTRL2_TEST_DATA = DATA_DIR / 'spectrl2_example_spectra.csv'
|
|
10
11
|
|
|
12
|
+
|
|
11
13
|
@pytest.fixture
|
|
12
14
|
def spectrl2_data():
|
|
13
15
|
# reference spectra generated with solar_utils==0.3
|
|
@@ -123,24 +125,75 @@ def test_get_example_spectral_response():
|
|
|
123
125
|
assert_allclose(sr, expected, rtol=1e-5)
|
|
124
126
|
|
|
125
127
|
|
|
128
|
+
@fail_on_pvlib_version('0.12')
|
|
126
129
|
def test_get_am15g():
|
|
127
130
|
# test that the reference spectrum is read and interpolated correctly
|
|
128
|
-
|
|
131
|
+
with pytest.warns(pvlibDeprecationWarning,
|
|
132
|
+
match="get_reference_spectra instead"):
|
|
133
|
+
e = spectrum.get_am15g()
|
|
129
134
|
assert_equal(len(e), 2002)
|
|
130
135
|
assert_equal(np.sum(e.index), 2761442)
|
|
131
136
|
assert_approx_equal(np.sum(e), 1002.88, significant=6)
|
|
132
137
|
|
|
133
|
-
wavelength = [270, 850, 950, 1200, 4001]
|
|
134
|
-
expected = [0.0, 0.893720, 0.147260, 0.448250, 0.0]
|
|
138
|
+
wavelength = [270, 850, 950, 1200, 1201.25, 4001]
|
|
139
|
+
expected = [0.0, 0.893720, 0.147260, 0.448250, 0.4371025, 0.0]
|
|
135
140
|
|
|
136
|
-
|
|
141
|
+
with pytest.warns(pvlibDeprecationWarning,
|
|
142
|
+
match="get_reference_spectra instead"):
|
|
143
|
+
e = spectrum.get_am15g(wavelength)
|
|
137
144
|
assert_equal(len(e), len(wavelength))
|
|
138
145
|
assert_allclose(e, expected, rtol=1e-6)
|
|
139
146
|
|
|
140
147
|
|
|
148
|
+
@pytest.mark.parametrize(
|
|
149
|
+
"reference_identifier,expected_sums",
|
|
150
|
+
[
|
|
151
|
+
(
|
|
152
|
+
"ASTM G173-03", # reference_identifier
|
|
153
|
+
{ # expected_sums
|
|
154
|
+
"extraterrestrial": 1356.15,
|
|
155
|
+
"global": 1002.88,
|
|
156
|
+
"direct": 887.65,
|
|
157
|
+
},
|
|
158
|
+
),
|
|
159
|
+
],
|
|
160
|
+
)
|
|
161
|
+
def test_get_reference_spectra(reference_identifier, expected_sums):
|
|
162
|
+
# test reading of a standard spectrum
|
|
163
|
+
standard = spectrum.get_reference_spectra(standard=reference_identifier)
|
|
164
|
+
assert set(standard.columns) == expected_sums.keys()
|
|
165
|
+
assert standard.index.name == "wavelength"
|
|
166
|
+
assert standard.index.is_monotonic_increasing is True
|
|
167
|
+
expected_sums = pd.Series(expected_sums) # convert prior to comparison
|
|
168
|
+
assert_series_equal(np.sum(standard, axis=0), expected_sums, atol=1e-2)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_get_reference_spectra_custom_wavelengths():
|
|
172
|
+
# test that the spectrum is interpolated correctly when custom wavelengths
|
|
173
|
+
# are specified
|
|
174
|
+
# only checked for ASTM G173-03 reference spectrum
|
|
175
|
+
wavelength = [270, 850, 951.634, 1200, 4001]
|
|
176
|
+
expected_sums = pd.Series(
|
|
177
|
+
{"extraterrestrial": 2.23266, "global": 1.68952, "direct": 1.58480}
|
|
178
|
+
) # for given ``wavelength``
|
|
179
|
+
standard = spectrum.get_reference_spectra(
|
|
180
|
+
wavelength, standard="ASTM G173-03"
|
|
181
|
+
)
|
|
182
|
+
assert_equal(len(standard), len(wavelength))
|
|
183
|
+
# check no NaN values were returned
|
|
184
|
+
assert not standard.isna().any().any() # double any to return one value
|
|
185
|
+
assert_series_equal(np.sum(standard, axis=0), expected_sums, atol=1e-4)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_get_reference_spectra_invalid_reference():
|
|
189
|
+
# test that an invalid reference identifier raises a ValueError
|
|
190
|
+
with pytest.raises(ValueError, match="Invalid standard identifier"):
|
|
191
|
+
spectrum.get_reference_spectra(standard="invalid")
|
|
192
|
+
|
|
193
|
+
|
|
141
194
|
def test_calc_spectral_mismatch_field(spectrl2_data):
|
|
142
195
|
# test that the mismatch is calculated correctly with
|
|
143
|
-
# - default and custom reference
|
|
196
|
+
# - default and custom reference spectrum
|
|
144
197
|
# - single or multiple sun spectra
|
|
145
198
|
|
|
146
199
|
# sample data
|
|
@@ -148,7 +201,7 @@ def test_calc_spectral_mismatch_field(spectrl2_data):
|
|
|
148
201
|
e_sun = e_sun.set_index('wavelength')
|
|
149
202
|
e_sun = e_sun.transpose()
|
|
150
203
|
|
|
151
|
-
e_ref = spectrum.
|
|
204
|
+
e_ref = spectrum.get_reference_spectra(standard='ASTM G173-03')["global"]
|
|
152
205
|
sr = spectrum.get_example_spectral_response()
|
|
153
206
|
|
|
154
207
|
# test with single sun spectrum, same as ref spectrum
|
|
@@ -175,25 +228,25 @@ def test_calc_spectral_mismatch_field(spectrl2_data):
|
|
|
175
228
|
|
|
176
229
|
@pytest.mark.parametrize("module_type,expect", [
|
|
177
230
|
('cdte', np.array(
|
|
178
|
-
[[
|
|
179
|
-
[
|
|
180
|
-
[
|
|
231
|
+
[[0.99051020, 0.97640320, 0.93975028],
|
|
232
|
+
[1.02928735, 1.01881074, 0.98578821],
|
|
233
|
+
[1.04750335, 1.03814456, 1.00623986]])),
|
|
181
234
|
('monosi', np.array(
|
|
182
|
-
[[
|
|
183
|
-
[
|
|
184
|
-
[
|
|
235
|
+
[[0.97769770, 1.02043409, 1.03574032],
|
|
236
|
+
[0.98630905, 1.03055092, 1.04736262],
|
|
237
|
+
[0.98828494, 1.03299036, 1.05026561]])),
|
|
185
238
|
('polysi', np.array(
|
|
186
|
-
[[
|
|
187
|
-
[
|
|
188
|
-
[
|
|
239
|
+
[[0.97704080, 1.01705849, 1.02613202],
|
|
240
|
+
[0.98992828, 1.03173953, 1.04260662],
|
|
241
|
+
[0.99352435, 1.03588785, 1.04730718]])),
|
|
189
242
|
('cigs', np.array(
|
|
190
|
-
[[
|
|
191
|
-
[
|
|
192
|
-
[
|
|
243
|
+
[[0.97459190, 1.02821696, 1.05067895],
|
|
244
|
+
[0.97529378, 1.02967497, 1.05289307],
|
|
245
|
+
[0.97269159, 1.02730558, 1.05075651]])),
|
|
193
246
|
('asi', np.array(
|
|
194
|
-
[[
|
|
195
|
-
[
|
|
196
|
-
[
|
|
247
|
+
[[1.05552750, 0.87707583, 0.72243772],
|
|
248
|
+
[1.11225204, 0.93665901, 0.78487953],
|
|
249
|
+
[1.14555295, 0.97084011, 0.81994083]]))
|
|
197
250
|
])
|
|
198
251
|
def test_spectral_factor_firstsolar(module_type, expect):
|
|
199
252
|
ams = np.array([1, 3, 5])
|
|
@@ -315,3 +368,211 @@ def test_spectral_factor_caballero_supplied_ambiguous():
|
|
|
315
368
|
with pytest.raises(ValueError):
|
|
316
369
|
spectrum.spectral_factor_caballero(1, 1, 1, module_type=None,
|
|
317
370
|
coefficients=None)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
@pytest.mark.parametrize("module_type,expected", [
|
|
374
|
+
('asi', np.array([1.15534029, 1.1123772, 1.08286684, 1.01915462])),
|
|
375
|
+
('fs-2', np.array([1.0694323, 1.04948777, 1.03556288, 0.9881471])),
|
|
376
|
+
('fs-4', np.array([1.05234725, 1.037771, 1.0275516, 0.98820533])),
|
|
377
|
+
('multisi', np.array([1.03310403, 1.02391703, 1.01744833, 0.97947605])),
|
|
378
|
+
('monosi', np.array([1.03225083, 1.02335353, 1.01708734, 0.97950110])),
|
|
379
|
+
('cigs', np.array([1.01475834, 1.01143927, 1.00909094, 0.97852966])),
|
|
380
|
+
])
|
|
381
|
+
def test_spectral_factor_pvspec(module_type, expected):
|
|
382
|
+
ams = np.array([1.0, 1.5, 2.0, 1.5])
|
|
383
|
+
kcs = np.array([0.4, 0.6, 0.8, 1.4])
|
|
384
|
+
out = spectrum.spectral_factor_pvspec(ams, kcs,
|
|
385
|
+
module_type=module_type)
|
|
386
|
+
assert np.allclose(expected, out, atol=1e-8)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@pytest.mark.parametrize("module_type,expected", [
|
|
390
|
+
('asi', pd.Series([1.15534029, 1.1123772, 1.08286684, 1.01915462])),
|
|
391
|
+
('fs-2', pd.Series([1.0694323, 1.04948777, 1.03556288, 0.9881471])),
|
|
392
|
+
('fs-4', pd.Series([1.05234725, 1.037771, 1.0275516, 0.98820533])),
|
|
393
|
+
('multisi', pd.Series([1.03310403, 1.02391703, 1.01744833, 0.97947605])),
|
|
394
|
+
('monosi', pd.Series([1.03225083, 1.02335353, 1.01708734, 0.97950110])),
|
|
395
|
+
('cigs', pd.Series([1.01475834, 1.01143927, 1.00909094, 0.97852966])),
|
|
396
|
+
])
|
|
397
|
+
def test_spectral_factor_pvspec_series(module_type, expected):
|
|
398
|
+
ams = pd.Series([1.0, 1.5, 2.0, 1.5])
|
|
399
|
+
kcs = pd.Series([0.4, 0.6, 0.8, 1.4])
|
|
400
|
+
out = spectrum.spectral_factor_pvspec(ams, kcs,
|
|
401
|
+
module_type=module_type)
|
|
402
|
+
assert isinstance(out, pd.Series)
|
|
403
|
+
assert np.allclose(expected, out, atol=1e-8)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def test_spectral_factor_pvspec_supplied():
|
|
407
|
+
# use the multisi coeffs
|
|
408
|
+
coeffs = (0.9847, -0.05237, 0.03034)
|
|
409
|
+
out = spectrum.spectral_factor_pvspec(1.5, 0.8, coefficients=coeffs)
|
|
410
|
+
expected = 1.00860641
|
|
411
|
+
assert_allclose(out, expected, atol=1e-8)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def test_spectral_factor_pvspec_supplied_redundant():
|
|
415
|
+
# Error when specifying both module_type and coefficients
|
|
416
|
+
coeffs = (0.9847, -0.05237, 0.03034)
|
|
417
|
+
with pytest.raises(ValueError, match='supply only one of'):
|
|
418
|
+
spectrum.spectral_factor_pvspec(1.5, 0.8, module_type='multisi',
|
|
419
|
+
coefficients=coeffs)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def test_spectral_factor_pvspec_supplied_ambiguous():
|
|
423
|
+
# Error when specifying neither module_type nor coefficients
|
|
424
|
+
with pytest.raises(ValueError, match='No valid input provided'):
|
|
425
|
+
spectrum.spectral_factor_pvspec(1.5, 0.8, module_type=None,
|
|
426
|
+
coefficients=None)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
@pytest.mark.parametrize("module_type,expected", [
|
|
430
|
+
('multisi', np.array([1.06129, 1.03098, 1.01155, 0.99849])),
|
|
431
|
+
('cdte', np.array([1.09657, 1.05594, 1.02763, 0.97740])),
|
|
432
|
+
])
|
|
433
|
+
def test_spectral_factor_jrc(module_type, expected):
|
|
434
|
+
ams = np.array([1.0, 1.5, 2.0, 1.5])
|
|
435
|
+
kcs = np.array([0.4, 0.6, 0.8, 1.4])
|
|
436
|
+
out = spectrum.spectral_factor_jrc(ams, kcs,
|
|
437
|
+
module_type=module_type)
|
|
438
|
+
assert np.allclose(expected, out, atol=1e-4)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@pytest.mark.parametrize("module_type,expected", [
|
|
442
|
+
('multisi', np.array([1.06129, 1.03098, 1.01155, 0.99849])),
|
|
443
|
+
('cdte', np.array([1.09657, 1.05594, 1.02763, 0.97740])),
|
|
444
|
+
])
|
|
445
|
+
def test_spectral_factor_jrc_series(module_type, expected):
|
|
446
|
+
ams = pd.Series([1.0, 1.5, 2.0, 1.5])
|
|
447
|
+
kcs = pd.Series([0.4, 0.6, 0.8, 1.4])
|
|
448
|
+
out = spectrum.spectral_factor_jrc(ams, kcs,
|
|
449
|
+
module_type=module_type)
|
|
450
|
+
assert isinstance(out, pd.Series)
|
|
451
|
+
assert np.allclose(expected, out, atol=1e-4)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def test_spectral_factor_jrc_supplied():
|
|
455
|
+
# use the multisi coeffs
|
|
456
|
+
coeffs = (0.494, 0.146, 0.00103)
|
|
457
|
+
out = spectrum.spectral_factor_jrc(1.0, 0.8, coefficients=coeffs)
|
|
458
|
+
expected = 1.01052106
|
|
459
|
+
assert_allclose(out, expected, atol=1e-4)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def test_spectral_factor_jrc_supplied_redundant():
|
|
463
|
+
# Error when specifying both module_type and coefficients
|
|
464
|
+
coeffs = (0.494, 0.146, 0.00103)
|
|
465
|
+
with pytest.raises(ValueError, match='supply only one of'):
|
|
466
|
+
spectrum.spectral_factor_jrc(1.0, 0.8, module_type='multisi',
|
|
467
|
+
coefficients=coeffs)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def test_spectral_factor_jrc_supplied_ambiguous():
|
|
471
|
+
# Error when specifying neither module_type nor coefficients
|
|
472
|
+
with pytest.raises(ValueError, match='No valid input provided'):
|
|
473
|
+
spectrum.spectral_factor_jrc(1.0, 0.8, module_type=None,
|
|
474
|
+
coefficients=None)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@pytest.fixture
|
|
478
|
+
def sr_and_eqe_fixture():
|
|
479
|
+
# Just some arbitrary data for testing the conversion functions
|
|
480
|
+
df = pd.DataFrame(
|
|
481
|
+
columns=("wavelength", "quantum_efficiency", "spectral_response"),
|
|
482
|
+
data=[
|
|
483
|
+
# nm, [0,1], A/W
|
|
484
|
+
[300, 0.85, 0.205671370402405],
|
|
485
|
+
[350, 0.86, 0.242772872514211],
|
|
486
|
+
[400, 0.87, 0.280680929019753],
|
|
487
|
+
[450, 0.88, 0.319395539919029],
|
|
488
|
+
[500, 0.89, 0.358916705212040],
|
|
489
|
+
[550, 0.90, 0.399244424898786],
|
|
490
|
+
[600, 0.91, 0.440378698979267],
|
|
491
|
+
[650, 0.92, 0.482319527453483],
|
|
492
|
+
[700, 0.93, 0.525066910321434],
|
|
493
|
+
[750, 0.94, 0.568620847583119],
|
|
494
|
+
[800, 0.95, 0.612981339238540],
|
|
495
|
+
[850, 0.90, 0.617014111207215],
|
|
496
|
+
[900, 0.80, 0.580719163489143],
|
|
497
|
+
[950, 0.70, 0.536358671833723],
|
|
498
|
+
[1000, 0.6, 0.483932636240953],
|
|
499
|
+
[1050, 0.4, 0.338752845368667],
|
|
500
|
+
],
|
|
501
|
+
)
|
|
502
|
+
df.set_index("wavelength", inplace=True)
|
|
503
|
+
return df
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def test_sr_to_qe(sr_and_eqe_fixture):
|
|
507
|
+
# vector type
|
|
508
|
+
qe = spectrum.sr_to_qe(
|
|
509
|
+
sr_and_eqe_fixture["spectral_response"].values,
|
|
510
|
+
sr_and_eqe_fixture.index.values, # wavelength, nm
|
|
511
|
+
)
|
|
512
|
+
assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"])
|
|
513
|
+
# pandas series type
|
|
514
|
+
# note: output Series' name should match the input
|
|
515
|
+
qe = spectrum.sr_to_qe(
|
|
516
|
+
sr_and_eqe_fixture["spectral_response"]
|
|
517
|
+
)
|
|
518
|
+
pd.testing.assert_series_equal(
|
|
519
|
+
qe, sr_and_eqe_fixture["quantum_efficiency"],
|
|
520
|
+
check_names=False
|
|
521
|
+
)
|
|
522
|
+
assert qe.name == "spectral_response"
|
|
523
|
+
# series normalization
|
|
524
|
+
qe = spectrum.sr_to_qe(
|
|
525
|
+
sr_and_eqe_fixture["spectral_response"] * 10, normalize=True
|
|
526
|
+
)
|
|
527
|
+
pd.testing.assert_series_equal(
|
|
528
|
+
qe,
|
|
529
|
+
sr_and_eqe_fixture["quantum_efficiency"]
|
|
530
|
+
/ max(sr_and_eqe_fixture["quantum_efficiency"]),
|
|
531
|
+
check_names=False,
|
|
532
|
+
)
|
|
533
|
+
# error on lack of wavelength parameter if no pandas object is provided
|
|
534
|
+
with pytest.raises(TypeError, match="must have an '.index' attribute"):
|
|
535
|
+
_ = spectrum.sr_to_qe(sr_and_eqe_fixture["spectral_response"].values)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def test_qe_to_sr(sr_and_eqe_fixture):
|
|
539
|
+
# vector type
|
|
540
|
+
sr = spectrum.qe_to_sr(
|
|
541
|
+
sr_and_eqe_fixture["quantum_efficiency"].values,
|
|
542
|
+
sr_and_eqe_fixture.index.values, # wavelength, nm
|
|
543
|
+
)
|
|
544
|
+
assert_allclose(sr, sr_and_eqe_fixture["spectral_response"])
|
|
545
|
+
# pandas series type
|
|
546
|
+
# note: output Series' name should match the input
|
|
547
|
+
sr = spectrum.qe_to_sr(
|
|
548
|
+
sr_and_eqe_fixture["quantum_efficiency"]
|
|
549
|
+
)
|
|
550
|
+
pd.testing.assert_series_equal(
|
|
551
|
+
sr, sr_and_eqe_fixture["spectral_response"],
|
|
552
|
+
check_names=False
|
|
553
|
+
)
|
|
554
|
+
assert sr.name == "quantum_efficiency"
|
|
555
|
+
# series normalization
|
|
556
|
+
sr = spectrum.qe_to_sr(
|
|
557
|
+
sr_and_eqe_fixture["quantum_efficiency"] * 10, normalize=True
|
|
558
|
+
)
|
|
559
|
+
pd.testing.assert_series_equal(
|
|
560
|
+
sr,
|
|
561
|
+
sr_and_eqe_fixture["spectral_response"]
|
|
562
|
+
/ max(sr_and_eqe_fixture["spectral_response"]),
|
|
563
|
+
check_names=False,
|
|
564
|
+
)
|
|
565
|
+
# error on lack of wavelength parameter if no pandas object is provided
|
|
566
|
+
with pytest.raises(TypeError, match="must have an '.index' attribute"):
|
|
567
|
+
_ = spectrum.qe_to_sr(
|
|
568
|
+
sr_and_eqe_fixture["quantum_efficiency"].values
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def test_qe_and_sr_reciprocal_conversion(sr_and_eqe_fixture):
|
|
573
|
+
# test that the conversion functions are reciprocal
|
|
574
|
+
qe = spectrum.sr_to_qe(sr_and_eqe_fixture["spectral_response"])
|
|
575
|
+
sr = spectrum.qe_to_sr(qe)
|
|
576
|
+
assert_allclose(sr, sr_and_eqe_fixture["spectral_response"])
|
|
577
|
+
qe = spectrum.sr_to_qe(sr)
|
|
578
|
+
assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"])
|
pvlib/tests/test_temperature.py
CHANGED
|
@@ -20,13 +20,13 @@ def sapm_default():
|
|
|
20
20
|
def test_sapm_cell(sapm_default):
|
|
21
21
|
default = temperature.sapm_cell(900, 20, 5, sapm_default['a'],
|
|
22
22
|
sapm_default['b'], sapm_default['deltaT'])
|
|
23
|
-
assert_allclose(default, 43.509, 3)
|
|
23
|
+
assert_allclose(default, 43.509, 1e-3)
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def test_sapm_module(sapm_default):
|
|
27
27
|
default = temperature.sapm_module(900, 20, 5, sapm_default['a'],
|
|
28
28
|
sapm_default['b'])
|
|
29
|
-
assert_allclose(default, 40.809, 3)
|
|
29
|
+
assert_allclose(default, 40.809, 1e-3)
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def test_sapm_cell_from_module(sapm_default):
|
|
@@ -47,8 +47,8 @@ def test_sapm_ndarray(sapm_default):
|
|
|
47
47
|
sapm_default['b'])
|
|
48
48
|
expected_cell = np.array([0., 23.06066166, 5.])
|
|
49
49
|
expected_module = np.array([0., 21.56066166, 5.])
|
|
50
|
-
assert_allclose(expected_cell, cell_temps, 3)
|
|
51
|
-
assert_allclose(expected_module, module_temps, 3)
|
|
50
|
+
assert_allclose(expected_cell, cell_temps, 1e-3)
|
|
51
|
+
assert_allclose(expected_module, module_temps, 1e-3)
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def test_sapm_series(sapm_default):
|
|
@@ -84,8 +84,8 @@ def test_pvsyst_cell_ndarray():
|
|
|
84
84
|
irrads = np.array([0, 500, 0])
|
|
85
85
|
winds = np.array([10, 5, 0])
|
|
86
86
|
result = temperature.pvsyst_cell(irrads, temps, wind_speed=winds)
|
|
87
|
-
expected = np.array([0.0, 23.
|
|
88
|
-
assert_allclose(expected, result
|
|
87
|
+
expected = np.array([0.0, 23.965517, 5.0])
|
|
88
|
+
assert_allclose(expected, result)
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def test_pvsyst_cell_series():
|
|
@@ -95,7 +95,7 @@ def test_pvsyst_cell_series():
|
|
|
95
95
|
winds = pd.Series([10, 5, 0], index=times)
|
|
96
96
|
|
|
97
97
|
result = temperature.pvsyst_cell(irrads, temps, wind_speed=winds)
|
|
98
|
-
expected = pd.Series([0.0, 23.
|
|
98
|
+
expected = pd.Series([0.0, 23.965517, 5.0], index=times)
|
|
99
99
|
assert_series_equal(expected, result)
|
|
100
100
|
|
|
101
101
|
|
pvlib/tests/test_tools.py
CHANGED
|
@@ -3,6 +3,7 @@ import pytest
|
|
|
3
3
|
from pvlib import tools
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pandas as pd
|
|
6
|
+
from numpy.testing import assert_allclose
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@pytest.mark.parametrize('keys, input_dict, expected', [
|
|
@@ -120,3 +121,26 @@ def test_get_pandas_index(args, args_idx):
|
|
|
120
121
|
assert index is None
|
|
121
122
|
else:
|
|
122
123
|
pd.testing.assert_index_equal(args[args_idx].index, index)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.mark.parametrize('data_in,expected', [
|
|
127
|
+
(np.array([1, 2, 3, 4, 5]),
|
|
128
|
+
np.array([0.2, 0.4, 0.6, 0.8, 1])),
|
|
129
|
+
(np.array([[0, 1, 2], [0, 3, 6]]),
|
|
130
|
+
np.array([[0, 0.5, 1], [0, 0.5, 1]])),
|
|
131
|
+
(pd.Series([1, 2, 3, 4, 5]),
|
|
132
|
+
pd.Series([0.2, 0.4, 0.6, 0.8, 1])),
|
|
133
|
+
(pd.DataFrame({"a": [0, 1, 2], "b": [0, 2, 8]}),
|
|
134
|
+
pd.DataFrame({"a": [0, 0.5, 1], "b": [0, 0.25, 1]})),
|
|
135
|
+
# test with NaN and all zeroes
|
|
136
|
+
(pd.DataFrame({"a": [0, np.nan, 1], "b": [0, 0, 0]}),
|
|
137
|
+
pd.DataFrame({"a": [0, np.nan, 1], "b": [np.nan]*3})),
|
|
138
|
+
# test with negative values
|
|
139
|
+
(np.array([1, 2, -3, 4, -5]),
|
|
140
|
+
np.array([0.2, 0.4, -0.6, 0.8, -1])),
|
|
141
|
+
(pd.Series([-2, np.nan, 1]),
|
|
142
|
+
pd.Series([-1, np.nan, 0.5])),
|
|
143
|
+
])
|
|
144
|
+
def test_normalize_max2one(data_in, expected):
|
|
145
|
+
result = tools.normalize_max2one(data_in)
|
|
146
|
+
assert_allclose(result, expected)
|