pvlib 0.10.4__py3-none-any.whl → 0.11.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 +1 -0
- pvlib/albedo.py +168 -0
- pvlib/bifacial/utils.py +2 -1
- 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 +1 -61
- pvlib/iotools/surfrad.py +1 -1
- pvlib/irradiance.py +133 -95
- pvlib/location.py +16 -6
- pvlib/modelchain.py +2 -165
- pvlib/pvarray.py +7 -5
- pvlib/pvsystem.py +75 -106
- pvlib/scaling.py +4 -2
- pvlib/shading.py +350 -0
- pvlib/singlediode.py +37 -9
- pvlib/snow.py +3 -1
- pvlib/spectrum/__init__.py +5 -0
- pvlib/spectrum/mismatch.py +573 -43
- pvlib/spectrum/spectrl2.py +8 -8
- pvlib/tests/bifacial/test_utils.py +6 -5
- 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 +73 -128
- pvlib/tests/test_shading.py +167 -1
- pvlib/tests/test_singlediode.py +68 -29
- 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/version.py +1 -5
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/METADATA +3 -4
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/RECORD +46 -42
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/WHEEL +1 -1
- pvlib/data/astm_g173_am15g.csv +0 -2003
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/LICENSE +0 -0
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/top_level.txt +0 -0
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)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from numpy.testing import assert_allclose
|
|
4
|
+
|
|
5
|
+
from pvlib import transformer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_simple_efficiency():
|
|
9
|
+
|
|
10
|
+
# define test inputs
|
|
11
|
+
input_power = pd.Series([
|
|
12
|
+
-800.0,
|
|
13
|
+
436016.609823837,
|
|
14
|
+
1511820.16603752,
|
|
15
|
+
1580687.44677249,
|
|
16
|
+
1616441.79660171
|
|
17
|
+
])
|
|
18
|
+
no_load_loss = 0.002
|
|
19
|
+
load_loss = 0.007
|
|
20
|
+
transformer_rating = 2750000
|
|
21
|
+
|
|
22
|
+
# define expected test results
|
|
23
|
+
expected_output_power = pd.Series([
|
|
24
|
+
-6300.10103234071,
|
|
25
|
+
430045.854892526,
|
|
26
|
+
1500588.39919874,
|
|
27
|
+
1568921.77089526,
|
|
28
|
+
1604389.62839879
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
# run test function with test inputs
|
|
32
|
+
calculated_output_power = transformer.simple_efficiency(
|
|
33
|
+
input_power=input_power,
|
|
34
|
+
no_load_loss=no_load_loss,
|
|
35
|
+
load_loss=load_loss,
|
|
36
|
+
transformer_rating=transformer_rating
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# determine if expected results are obtained
|
|
40
|
+
assert_allclose(calculated_output_power, expected_output_power)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_simple_efficiency_known_values():
|
|
44
|
+
no_load_loss = 0.005
|
|
45
|
+
load_loss = 0.01
|
|
46
|
+
rating = 1000
|
|
47
|
+
args = (no_load_loss, load_loss, rating)
|
|
48
|
+
|
|
49
|
+
# verify correct behavior at no-load condition
|
|
50
|
+
assert_allclose(
|
|
51
|
+
transformer.simple_efficiency(no_load_loss*rating, *args),
|
|
52
|
+
0.0
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# verify correct behavior at rated condition
|
|
56
|
+
assert_allclose(
|
|
57
|
+
transformer.simple_efficiency(rating*(1 + no_load_loss + load_loss),
|
|
58
|
+
*args),
|
|
59
|
+
rating,
|
|
60
|
+
)
|
pvlib/tools.py
CHANGED
|
@@ -507,3 +507,30 @@ def get_pandas_index(*args):
|
|
|
507
507
|
(a.index for a in args if isinstance(a, (pd.DataFrame, pd.Series))),
|
|
508
508
|
None
|
|
509
509
|
)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def normalize_max2one(a):
|
|
513
|
+
r"""
|
|
514
|
+
Normalize an array so that the largest absolute value is ±1.
|
|
515
|
+
|
|
516
|
+
Handles both numpy arrays and pandas objects.
|
|
517
|
+
On 2D arrays, normalization is row-wise.
|
|
518
|
+
On pandas DataFrame, normalization is column-wise.
|
|
519
|
+
|
|
520
|
+
If all values of row are 0, the array is set to NaNs.
|
|
521
|
+
|
|
522
|
+
Parameters
|
|
523
|
+
----------
|
|
524
|
+
a : array-like
|
|
525
|
+
The array to normalize.
|
|
526
|
+
|
|
527
|
+
Returns
|
|
528
|
+
-------
|
|
529
|
+
array-like
|
|
530
|
+
The normalized array.
|
|
531
|
+
"""
|
|
532
|
+
try: # expect numpy array
|
|
533
|
+
res = a / np.max(np.absolute(a), axis=-1, keepdims=True)
|
|
534
|
+
except ValueError: # fails for pandas objects
|
|
535
|
+
res = a.div(a.abs().max(axis=0, skipna=True))
|
|
536
|
+
return res
|
pvlib/transformer.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains functions for transformer modeling.
|
|
3
|
+
|
|
4
|
+
Transformer models calculate AC power output and losses at a given input power.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def simple_efficiency(
|
|
9
|
+
input_power, no_load_loss, load_loss, transformer_rating
|
|
10
|
+
):
|
|
11
|
+
r'''
|
|
12
|
+
Calculate the power at the output terminal of the transformer
|
|
13
|
+
after taking into account efficiency using a simple calculation.
|
|
14
|
+
|
|
15
|
+
The equation used in this function can be derived from [1]_.
|
|
16
|
+
|
|
17
|
+
For a zero input power, the output power will be negative.
|
|
18
|
+
This means the transformer will consume energy from the grid at night if
|
|
19
|
+
it stays connected (due to the parallel impedance in the equivalent
|
|
20
|
+
circuit).
|
|
21
|
+
If the input power is negative, the output power will be even more
|
|
22
|
+
negative; so the model can be used bidirectionally when drawing
|
|
23
|
+
energy from the grid.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
input_power : numeric
|
|
28
|
+
The real AC power input to the transformer. [W]
|
|
29
|
+
|
|
30
|
+
no_load_loss : numeric
|
|
31
|
+
The constant losses experienced by a transformer, even
|
|
32
|
+
when the transformer is not under load. Fraction of transformer rating,
|
|
33
|
+
value from 0 to 1. [unitless]
|
|
34
|
+
|
|
35
|
+
load_loss: numeric
|
|
36
|
+
The load dependent losses experienced by the transformer.
|
|
37
|
+
Fraction of transformer rating, value from 0 to 1. [unitless]
|
|
38
|
+
|
|
39
|
+
transformer_rating: numeric
|
|
40
|
+
The nominal output power of the transformer. [VA]
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
output_power : numeric
|
|
45
|
+
Real AC power output. [W]
|
|
46
|
+
|
|
47
|
+
Notes
|
|
48
|
+
-------
|
|
49
|
+
First, assume that the load loss :math:`L_{load}` (as a fraction of rated power
|
|
50
|
+
:math:`P_{nom}`) is proportional to the square of output power:
|
|
51
|
+
|
|
52
|
+
.. math::
|
|
53
|
+
|
|
54
|
+
L_{load}(P_{out}) &= L_{load}(P_{rated}) \times (P_{out} / P_{nom})^2
|
|
55
|
+
|
|
56
|
+
&= L_{full, load} \times (P_{out} / P_{nom})^2
|
|
57
|
+
|
|
58
|
+
Total loss is the constant no-load loss plus the variable load loss:
|
|
59
|
+
|
|
60
|
+
.. math::
|
|
61
|
+
|
|
62
|
+
L_{total}(P_{out}) &= L_{no, load} + L_{load}(P_{out})
|
|
63
|
+
|
|
64
|
+
&= L_{no, load} + L_{full, load} \times (P_{out} / P_{nom})^2
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
By conservation of energy, total loss is the difference between input and
|
|
68
|
+
output power:
|
|
69
|
+
|
|
70
|
+
.. math::
|
|
71
|
+
|
|
72
|
+
\frac{P_{in}}{P_{nom}} &= \frac{P_{out}}{P_{nom}} + L_{total}(P_{out})
|
|
73
|
+
|
|
74
|
+
&= \frac{P_{out}}{P_{nom}} + L_{no, load} + L_{full, load} \times (P_{out} / P_{nom})^2
|
|
75
|
+
|
|
76
|
+
Now use the quadratic formula to solve for :math:`P_{out}` as a function of
|
|
77
|
+
:math:`P_{in}`.
|
|
78
|
+
|
|
79
|
+
.. math::
|
|
80
|
+
|
|
81
|
+
\frac{P_{out}}{P_{nom}} &= \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
|
|
82
|
+
|
|
83
|
+
a &= L_{full, load}
|
|
84
|
+
|
|
85
|
+
b &= 1
|
|
86
|
+
|
|
87
|
+
c &= L_{no, load} - P_{in} / P_{nom}
|
|
88
|
+
|
|
89
|
+
Therefore:
|
|
90
|
+
|
|
91
|
+
.. math::
|
|
92
|
+
|
|
93
|
+
P_{out} = P_{nom} \frac{-1 \pm \sqrt{1 - 4 L_{full, load}
|
|
94
|
+
|
|
95
|
+
\times (L_{no, load} - P_{in}/P_{nom})}}{2 L_{full, load}}
|
|
96
|
+
|
|
97
|
+
The positive root should be chosen, so that the output power is
|
|
98
|
+
positive.
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
References
|
|
102
|
+
----------
|
|
103
|
+
.. [1] Central Station Engineers of the Westinghouse Electric Corporation,
|
|
104
|
+
"Electrical Transmission and Distribution Reference Book" 4th Edition.
|
|
105
|
+
pg. 101.
|
|
106
|
+
''' # noqa: E501
|
|
107
|
+
|
|
108
|
+
input_power_normalized = input_power / transformer_rating
|
|
109
|
+
|
|
110
|
+
a = load_loss
|
|
111
|
+
b = 1
|
|
112
|
+
c = no_load_loss - input_power_normalized
|
|
113
|
+
|
|
114
|
+
output_power_normalized = (-b + (b**2 - 4*a*c)**0.5) / (2 * a)
|
|
115
|
+
|
|
116
|
+
output_power = output_power_normalized * transformer_rating
|
|
117
|
+
return output_power
|
pvlib/version.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
from importlib.metadata import PackageNotFoundError, version
|
|
3
|
-
except ImportError:
|
|
4
|
-
# for python < 3.8
|
|
5
|
-
from importlib_metadata import PackageNotFoundError, version
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
2
|
|
|
7
3
|
try:
|
|
8
4
|
__version__ = version(__package__)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pvlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: A set of functions and classes for simulating the performance of photovoltaic energy systems.
|
|
5
5
|
Home-page: https://github.com/pvlib/pvlib-python
|
|
6
6
|
Author-email: pvlib python Developers <pvlib-admin@googlegroups.com>
|
|
@@ -15,7 +15,7 @@ Classifier: Intended Audience :: Science/Research
|
|
|
15
15
|
Classifier: Programming Language :: Python
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering
|
|
18
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
19
|
Description-Content-Type: text/x-rst
|
|
20
20
|
License-File: LICENSE
|
|
21
21
|
License-File: AUTHORS.md
|
|
@@ -23,9 +23,8 @@ Requires-Dist: numpy >=1.17.3
|
|
|
23
23
|
Requires-Dist: pandas >=1.3.0
|
|
24
24
|
Requires-Dist: pytz
|
|
25
25
|
Requires-Dist: requests
|
|
26
|
-
Requires-Dist: scipy >=1.
|
|
26
|
+
Requires-Dist: scipy >=1.6.0
|
|
27
27
|
Requires-Dist: h5py
|
|
28
|
-
Requires-Dist: importlib-metadata ; python_version < "3.8"
|
|
29
28
|
Provides-Extra: all
|
|
30
29
|
Requires-Dist: pvlib[doc,optional,test] ; extra == 'all'
|
|
31
30
|
Provides-Extra: doc
|