pvlib 0.10.3__py3-none-any.whl → 0.10.5__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/bifacial/utils.py +2 -1
- pvlib/clearsky.py +7 -8
- pvlib/iam.py +3 -3
- pvlib/inverter.py +3 -3
- pvlib/iotools/__init__.py +2 -0
- pvlib/iotools/solargis.py +214 -0
- pvlib/iotools/solcast.py +2 -7
- pvlib/iotools/solrad.py +121 -23
- pvlib/iotools/srml.py +12 -12
- pvlib/iotools/surfrad.py +2 -2
- pvlib/irradiance.py +28 -22
- pvlib/location.py +3 -1
- pvlib/modelchain.py +10 -9
- pvlib/pvarray.py +127 -0
- pvlib/pvsystem.py +52 -43
- pvlib/scaling.py +4 -2
- pvlib/shading.py +110 -0
- pvlib/singlediode.py +37 -9
- pvlib/snow.py +3 -1
- pvlib/solarposition.py +38 -30
- pvlib/spa.py +3 -11
- pvlib/spectrum/mismatch.py +2 -1
- pvlib/temperature.py +3 -2
- pvlib/tests/bifacial/test_utils.py +6 -5
- pvlib/tests/conftest.py +13 -14
- pvlib/tests/iotools/test_sodapro.py +2 -1
- pvlib/tests/iotools/test_solargis.py +68 -0
- pvlib/tests/iotools/test_solcast.py +2 -2
- pvlib/tests/iotools/test_solrad.py +58 -7
- pvlib/tests/iotools/test_srml.py +7 -14
- pvlib/tests/test_clearsky.py +1 -1
- pvlib/tests/test_irradiance.py +24 -8
- pvlib/tests/test_location.py +1 -1
- pvlib/tests/test_modelchain.py +37 -26
- pvlib/tests/test_pvarray.py +25 -0
- pvlib/tests/test_pvsystem.py +76 -104
- pvlib/tests/test_shading.py +130 -11
- pvlib/tests/test_singlediode.py +68 -10
- pvlib/tests/test_snow.py +1 -1
- pvlib/tests/test_solarposition.py +121 -7
- pvlib/tests/test_spa.py +5 -15
- pvlib/tests/test_temperature.py +4 -4
- pvlib/tests/test_tracking.py +2 -2
- pvlib/tracking.py +8 -38
- pvlib/version.py +1 -5
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/METADATA +9 -33
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/RECORD +52 -51
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/WHEEL +1 -1
- pvlib/spa_c_files/SPA_NOTICE.md +0 -39
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/LICENSE +0 -0
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/top_level.txt +0 -0
pvlib/tests/test_pvsystem.py
CHANGED
|
@@ -12,7 +12,6 @@ from numpy.testing import assert_allclose
|
|
|
12
12
|
import unittest.mock as mock
|
|
13
13
|
|
|
14
14
|
from pvlib import inverter, pvsystem
|
|
15
|
-
from pvlib import atmosphere
|
|
16
15
|
from pvlib import iam as _iam
|
|
17
16
|
from pvlib import irradiance
|
|
18
17
|
from pvlib import spectrum
|
|
@@ -104,87 +103,62 @@ def test_PVSystem_get_iam_invalid(sapm_module_params, mocker):
|
|
|
104
103
|
system.get_iam(45, iam_model='not_a_model')
|
|
105
104
|
|
|
106
105
|
|
|
107
|
-
def
|
|
106
|
+
def test_retrieve_sam_raises_exceptions():
|
|
108
107
|
"""
|
|
109
|
-
Raise an exception if
|
|
108
|
+
Raise an exception if an invalid parameter is provided to `retrieve_sam()`.
|
|
110
109
|
"""
|
|
111
|
-
with pytest.raises(ValueError
|
|
110
|
+
with pytest.raises(ValueError, match="Please provide either"):
|
|
112
111
|
pvsystem.retrieve_sam()
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
'
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
'
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
'
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
'
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
112
|
+
with pytest.raises(ValueError, match="Please provide either.*, not both."):
|
|
113
|
+
pvsystem.retrieve_sam(name="this_surely_wont_work", path="wont_work")
|
|
114
|
+
with pytest.raises(KeyError, match="Invalid name"):
|
|
115
|
+
pvsystem.retrieve_sam(name="this_surely_wont_work")
|
|
116
|
+
with pytest.raises(FileNotFoundError):
|
|
117
|
+
pvsystem.retrieve_sam(path="this_surely_wont_work.csv")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_retrieve_sam_databases():
|
|
121
|
+
"""Test the expected keys are retrieved from each database."""
|
|
122
|
+
keys_per_database = {
|
|
123
|
+
"cecmod": {'Technology', 'Bifacial', 'STC', 'PTC', 'A_c', 'Length',
|
|
124
|
+
'Width', 'N_s', 'I_sc_ref', 'V_oc_ref', 'I_mp_ref',
|
|
125
|
+
'V_mp_ref', 'alpha_sc', 'beta_oc', 'T_NOCT', 'a_ref',
|
|
126
|
+
'I_L_ref', 'I_o_ref', 'R_s', 'R_sh_ref', 'Adjust',
|
|
127
|
+
'gamma_r', 'BIPV', 'Version', 'Date'},
|
|
128
|
+
"sandiamod": {'Vintage', 'Area', 'Material', 'Cells_in_Series',
|
|
129
|
+
'Parallel_Strings', 'Isco', 'Voco', 'Impo', 'Vmpo',
|
|
130
|
+
'Aisc', 'Aimp', 'C0', 'C1', 'Bvoco', 'Mbvoc', 'Bvmpo',
|
|
131
|
+
'Mbvmp', 'N', 'C2', 'C3', 'A0', 'A1', 'A2', 'A3', 'A4',
|
|
132
|
+
'B0', 'B1', 'B2', 'B3', 'B4', 'B5', 'DTC', 'FD', 'A',
|
|
133
|
+
'B', 'C4', 'C5', 'IXO', 'IXXO', 'C6', 'C7', 'Notes'},
|
|
134
|
+
"adrinverter": {'Manufacturer', 'Model', 'Source', 'Vac', 'Vintage',
|
|
135
|
+
'Pacmax', 'Pnom', 'Vnom', 'Vmin', 'Vmax',
|
|
136
|
+
'ADRCoefficients', 'Pnt', 'Vdcmax', 'Idcmax',
|
|
137
|
+
'MPPTLow', 'MPPTHi', 'TambLow', 'TambHi', 'Weight',
|
|
138
|
+
'PacFitErrMax', 'YearOfData'},
|
|
139
|
+
"cecinverter": {'Vac', 'Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2',
|
|
140
|
+
'C3', 'Pnt', 'Vdcmax', 'Idcmax', 'Mppt_low',
|
|
141
|
+
'Mppt_high', 'CEC_Date', 'CEC_Type'}
|
|
142
|
+
} # fmt: skip
|
|
143
|
+
item_per_database = {
|
|
144
|
+
"cecmod": "Itek_Energy_LLC_iT_300_HE",
|
|
145
|
+
"sandiamod": "Canadian_Solar_CS6X_300M__2013_",
|
|
146
|
+
"adrinverter": "Sainty_Solar__SSI_4K4U_240V__CEC_2011_",
|
|
147
|
+
"cecinverter": "ABB__PVI_3_0_OUTD_S_US__208V_",
|
|
148
|
+
}
|
|
149
|
+
# duplicate the cecinverter items for sandiainverter, for backwards compat
|
|
150
|
+
keys_per_database["sandiainverter"] = keys_per_database["cecinverter"]
|
|
151
|
+
item_per_database["sandiainverter"] = item_per_database["cecinverter"]
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
expected keys for that inverter.
|
|
160
|
-
"""
|
|
161
|
-
data = pvsystem.retrieve_sam('cecinverter')
|
|
162
|
-
keys = [
|
|
163
|
-
'Vac',
|
|
164
|
-
'Paco',
|
|
165
|
-
'Pdco',
|
|
166
|
-
'Vdco',
|
|
167
|
-
'Pso',
|
|
168
|
-
'C0',
|
|
169
|
-
'C1',
|
|
170
|
-
'C2',
|
|
171
|
-
'C3',
|
|
172
|
-
'Pnt',
|
|
173
|
-
'Vdcmax',
|
|
174
|
-
'Idcmax',
|
|
175
|
-
'Mppt_low',
|
|
176
|
-
'Mppt_high',
|
|
177
|
-
'CEC_Date',
|
|
178
|
-
'CEC_Type',
|
|
179
|
-
]
|
|
180
|
-
inverter = 'Yaskawa_Solectria_Solar__PVI_5300_208__208V_'
|
|
181
|
-
assert inverter in data
|
|
182
|
-
assert set(data[inverter].keys()) == set(keys)
|
|
153
|
+
for database in keys_per_database.keys():
|
|
154
|
+
data = pvsystem.retrieve_sam(database)
|
|
155
|
+
assert set(data.index) == keys_per_database[database]
|
|
156
|
+
assert item_per_database[database] in data.columns
|
|
183
157
|
|
|
184
158
|
|
|
185
159
|
def test_sapm(sapm_module_params):
|
|
186
160
|
|
|
187
|
-
times = pd.date_range(start='2015-01-01', periods=5, freq='
|
|
161
|
+
times = pd.date_range(start='2015-01-01', periods=5, freq='12h')
|
|
188
162
|
effective_irradiance = pd.Series([-1000, 500, 1100, np.nan, 1000],
|
|
189
163
|
index=times)
|
|
190
164
|
temp_cell = pd.Series([10, 25, 50, 25, np.nan], index=times)
|
|
@@ -192,16 +166,14 @@ def test_sapm(sapm_module_params):
|
|
|
192
166
|
out = pvsystem.sapm(effective_irradiance, temp_cell, sapm_module_params)
|
|
193
167
|
|
|
194
168
|
expected = pd.DataFrame(np.array(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
[ nan, nan, nan, nan,
|
|
204
|
-
nan, nan, nan]]),
|
|
169
|
+
[[-5.0608322, -4.65037767, np.nan, np.nan, np.nan,
|
|
170
|
+
-4.91119927, -4.16721569],
|
|
171
|
+
[2.545575, 2.28773882, 56.86182059, 47.21121608, 108.00693168,
|
|
172
|
+
2.48357383, 1.71782772],
|
|
173
|
+
[5.65584763, 5.01709903, 54.1943277, 42.51861718, 213.32011294,
|
|
174
|
+
5.52987899, 3.46796463],
|
|
175
|
+
[np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
|
|
176
|
+
[np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan]]),
|
|
205
177
|
columns=['i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'i_x', 'i_xx'],
|
|
206
178
|
index=times)
|
|
207
179
|
|
|
@@ -210,13 +182,13 @@ def test_sapm(sapm_module_params):
|
|
|
210
182
|
out = pvsystem.sapm(1000, 25, sapm_module_params)
|
|
211
183
|
|
|
212
184
|
expected = OrderedDict()
|
|
213
|
-
expected['i_sc'] =
|
|
214
|
-
expected['i_mp'] =
|
|
215
|
-
expected['v_oc'] =
|
|
216
|
-
expected['v_mp'] =
|
|
217
|
-
expected['p_mp'] =
|
|
218
|
-
expected['i_x'] =
|
|
219
|
-
expected['i_xx'] =
|
|
185
|
+
expected['i_sc'] = sapm_module_params['Isco']
|
|
186
|
+
expected['i_mp'] = sapm_module_params['Impo']
|
|
187
|
+
expected['v_oc'] = sapm_module_params['Voco']
|
|
188
|
+
expected['v_mp'] = sapm_module_params['Vmpo']
|
|
189
|
+
expected['p_mp'] = sapm_module_params['Impo'] * sapm_module_params['Vmpo']
|
|
190
|
+
expected['i_x'] = sapm_module_params['IXO']
|
|
191
|
+
expected['i_xx'] = sapm_module_params['IXXO']
|
|
220
192
|
|
|
221
193
|
for k, v in expected.items():
|
|
222
194
|
assert_allclose(out[k], v, atol=1e-4)
|
|
@@ -536,7 +508,7 @@ def test_PVSystem_noct_celltemp(mocker):
|
|
|
536
508
|
np.array(wind_speed), model='noct_sam')
|
|
537
509
|
assert_allclose(out, expected)
|
|
538
510
|
dr = pd.date_range(start='2020-01-01 12:00:00', end='2020-01-01 13:00:00',
|
|
539
|
-
freq='
|
|
511
|
+
freq='1h')
|
|
540
512
|
out = system.get_cell_temperature(pd.Series(index=dr, data=poa_global),
|
|
541
513
|
pd.Series(index=dr, data=temp_air),
|
|
542
514
|
pd.Series(index=dr, data=wind_speed),
|
|
@@ -566,7 +538,7 @@ def test_PVSystem_noct_celltemp_error():
|
|
|
566
538
|
@pytest.mark.parametrize("model",
|
|
567
539
|
['faiman', 'pvsyst', 'sapm', 'fuentes', 'noct_sam'])
|
|
568
540
|
def test_PVSystem_multi_array_celltemp_functions(model, two_array_system):
|
|
569
|
-
times = pd.date_range(start='2020-08-25 11:00', freq='
|
|
541
|
+
times = pd.date_range(start='2020-08-25 11:00', freq='h', periods=3)
|
|
570
542
|
irrad_one = pd.Series(1000, index=times)
|
|
571
543
|
irrad_two = pd.Series(500, index=times)
|
|
572
544
|
temp_air = pd.Series(25, index=times)
|
|
@@ -580,7 +552,7 @@ def test_PVSystem_multi_array_celltemp_functions(model, two_array_system):
|
|
|
580
552
|
@pytest.mark.parametrize("model",
|
|
581
553
|
['faiman', 'pvsyst', 'sapm', 'fuentes', 'noct_sam'])
|
|
582
554
|
def test_PVSystem_multi_array_celltemp_multi_temp(model, two_array_system):
|
|
583
|
-
times = pd.date_range(start='2020-08-25 11:00', freq='
|
|
555
|
+
times = pd.date_range(start='2020-08-25 11:00', freq='h', periods=3)
|
|
584
556
|
irrad = pd.Series(1000, index=times)
|
|
585
557
|
temp_air_one = pd.Series(25, index=times)
|
|
586
558
|
temp_air_two = pd.Series(5, index=times)
|
|
@@ -605,7 +577,7 @@ def test_PVSystem_multi_array_celltemp_multi_temp(model, two_array_system):
|
|
|
605
577
|
@pytest.mark.parametrize("model",
|
|
606
578
|
['faiman', 'pvsyst', 'sapm', 'fuentes', 'noct_sam'])
|
|
607
579
|
def test_PVSystem_multi_array_celltemp_multi_wind(model, two_array_system):
|
|
608
|
-
times = pd.date_range(start='2020-08-25 11:00', freq='
|
|
580
|
+
times = pd.date_range(start='2020-08-25 11:00', freq='h', periods=3)
|
|
609
581
|
irrad = pd.Series(1000, index=times)
|
|
610
582
|
temp_air = pd.Series(25, index=times)
|
|
611
583
|
wind_speed_one = pd.Series(1, index=times)
|
|
@@ -933,7 +905,7 @@ def test_calcparams_pvsyst_all_scalars(pvsyst_module_params):
|
|
|
933
905
|
|
|
934
906
|
|
|
935
907
|
def test_calcparams_desoto(cec_module_params):
|
|
936
|
-
times = pd.date_range(start='2015-01-01', periods=3, freq='
|
|
908
|
+
times = pd.date_range(start='2015-01-01', periods=3, freq='12h')
|
|
937
909
|
df = pd.DataFrame({
|
|
938
910
|
'effective_irradiance': [0.0, 800.0, 800.0],
|
|
939
911
|
'temp_cell': [25, 25, 50]
|
|
@@ -965,7 +937,7 @@ def test_calcparams_desoto(cec_module_params):
|
|
|
965
937
|
|
|
966
938
|
|
|
967
939
|
def test_calcparams_cec(cec_module_params):
|
|
968
|
-
times = pd.date_range(start='2015-01-01', periods=3, freq='
|
|
940
|
+
times = pd.date_range(start='2015-01-01', periods=3, freq='12h')
|
|
969
941
|
df = pd.DataFrame({
|
|
970
942
|
'effective_irradiance': [0.0, 800.0, 800.0],
|
|
971
943
|
'temp_cell': [25, 25, 50]
|
|
@@ -1008,7 +980,7 @@ def test_calcparams_cec_extra_params_propagation(cec_module_params, mocker):
|
|
|
1008
980
|
checks that the latter is called with the expected parameters instead of
|
|
1009
981
|
some default values.
|
|
1010
982
|
"""
|
|
1011
|
-
times = pd.date_range(start='2015-01-01', periods=3, freq='
|
|
983
|
+
times = pd.date_range(start='2015-01-01', periods=3, freq='12h')
|
|
1012
984
|
effective_irradiance = pd.Series([0.0, 800.0, 800.0], index=times)
|
|
1013
985
|
temp_cell = pd.Series([25, 25, 50], index=times)
|
|
1014
986
|
extra_parameters = dict(
|
|
@@ -1035,7 +1007,7 @@ def test_calcparams_cec_extra_params_propagation(cec_module_params, mocker):
|
|
|
1035
1007
|
|
|
1036
1008
|
|
|
1037
1009
|
def test_calcparams_pvsyst(pvsyst_module_params):
|
|
1038
|
-
times = pd.date_range(start='2015-01-01', periods=2, freq='
|
|
1010
|
+
times = pd.date_range(start='2015-01-01', periods=2, freq='12h')
|
|
1039
1011
|
df = pd.DataFrame({
|
|
1040
1012
|
'effective_irradiance': [0.0, 800.0],
|
|
1041
1013
|
'temp_cell': [25, 50]
|
|
@@ -1527,7 +1499,7 @@ def test_mpp_series():
|
|
|
1527
1499
|
|
|
1528
1500
|
|
|
1529
1501
|
def test_singlediode_series(cec_module_params):
|
|
1530
|
-
times = pd.date_range(start='2015-01-01', periods=2, freq='
|
|
1502
|
+
times = pd.date_range(start='2015-01-01', periods=2, freq='12h')
|
|
1531
1503
|
effective_irradiance = pd.Series([0.0, 800.0], index=times)
|
|
1532
1504
|
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
|
|
1533
1505
|
effective_irradiance,
|
|
@@ -1617,7 +1589,7 @@ def test_singlediode_floats_ivcurve():
|
|
|
1617
1589
|
|
|
1618
1590
|
|
|
1619
1591
|
def test_singlediode_series_ivcurve(cec_module_params):
|
|
1620
|
-
times = pd.date_range(start='2015-06-01', periods=3, freq='
|
|
1592
|
+
times = pd.date_range(start='2015-06-01', periods=3, freq='6h')
|
|
1621
1593
|
effective_irradiance = pd.Series([0.0, 400.0, 800.0], index=times)
|
|
1622
1594
|
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
|
|
1623
1595
|
effective_irradiance,
|
|
@@ -1911,7 +1883,7 @@ def test_PVSystem_multiple_array_get_aoi():
|
|
|
1911
1883
|
@pytest.fixture
|
|
1912
1884
|
def solar_pos():
|
|
1913
1885
|
times = pd.date_range(start='20160101 1200-0700',
|
|
1914
|
-
end='20160101 1800-0700', freq='
|
|
1886
|
+
end='20160101 1800-0700', freq='6h')
|
|
1915
1887
|
location = Location(latitude=32, longitude=-111)
|
|
1916
1888
|
return location.get_solarposition(times)
|
|
1917
1889
|
|
|
@@ -2369,10 +2341,10 @@ def test_PVSystem_single_array():
|
|
|
2369
2341
|
|
|
2370
2342
|
|
|
2371
2343
|
def test_combine_loss_factors():
|
|
2372
|
-
test_index = pd.date_range(start='1990/01/01T12:00', periods=365, freq='
|
|
2344
|
+
test_index = pd.date_range(start='1990/01/01T12:00', periods=365, freq='d')
|
|
2373
2345
|
loss_1 = pd.Series(.10, index=test_index)
|
|
2374
2346
|
loss_2 = pd.Series(.05, index=pd.date_range(start='1990/01/01T12:00',
|
|
2375
|
-
periods=365*2, freq='
|
|
2347
|
+
periods=365*2, freq='d'))
|
|
2376
2348
|
loss_3 = pd.Series(.02, index=pd.date_range(start='1990/01/01',
|
|
2377
2349
|
periods=12, freq='MS'))
|
|
2378
2350
|
expected = pd.Series(.1621, index=test_index)
|
pvlib/tests/test_shading.py
CHANGED
|
@@ -2,28 +2,31 @@ 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
6
|
import pytest
|
|
7
|
+
from datetime import timezone, timedelta
|
|
6
8
|
|
|
7
9
|
from pvlib import shading
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
@pytest.fixture
|
|
11
13
|
def test_system():
|
|
12
|
-
syst = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
syst = {
|
|
15
|
+
"height": 1.0,
|
|
16
|
+
"pitch": 2.0,
|
|
17
|
+
"surface_tilt": 30.0,
|
|
18
|
+
"surface_azimuth": 180.0,
|
|
19
|
+
"rotation": -30.0,
|
|
20
|
+
} # rotation of right edge relative to horizontal
|
|
21
|
+
syst["gcr"] = 1.0 / syst["pitch"]
|
|
18
22
|
return syst
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
def test__ground_angle(test_system):
|
|
22
26
|
ts = test_system
|
|
23
|
-
x = np.array([0
|
|
24
|
-
angles = shading.ground_angle(
|
|
25
|
-
|
|
26
|
-
expected_angles = np.array([0., 5.866738789543952, 9.896090638982903])
|
|
27
|
+
x = np.array([0.0, 0.5, 1.0])
|
|
28
|
+
angles = shading.ground_angle(ts["surface_tilt"], ts["gcr"], x)
|
|
29
|
+
expected_angles = np.array([0.0, 5.866738789543952, 9.896090638982903])
|
|
27
30
|
assert np.allclose(angles, expected_angles)
|
|
28
31
|
|
|
29
32
|
|
|
@@ -37,7 +40,7 @@ def test__ground_angle_zero_gcr():
|
|
|
37
40
|
|
|
38
41
|
@pytest.fixture
|
|
39
42
|
def surface_tilt():
|
|
40
|
-
idx = pd.date_range(
|
|
43
|
+
idx = pd.date_range("2019-01-01", freq="h", periods=3)
|
|
41
44
|
return pd.Series([0, 20, 90], index=idx)
|
|
42
45
|
|
|
43
46
|
|
|
@@ -104,3 +107,119 @@ def test_sky_diffuse_passias_scalar(average_masking_angle, shading_loss):
|
|
|
104
107
|
for angle, loss in zip(average_masking_angle, shading_loss):
|
|
105
108
|
actual_loss = shading.sky_diffuse_passias(angle)
|
|
106
109
|
assert np.isclose(loss, actual_loss)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@pytest.fixture
|
|
113
|
+
def true_tracking_angle_and_inputs_NREL():
|
|
114
|
+
# data from NREL 'Slope-Aware Backtracking for Single-Axis Trackers'
|
|
115
|
+
# doi.org/10.2172/1660126 ; Accessed on 2023-11-06.
|
|
116
|
+
tzinfo = timezone(timedelta(hours=-5))
|
|
117
|
+
axis_tilt_angle = 9.666 # deg
|
|
118
|
+
axis_azimuth_angle = 195.0 # deg
|
|
119
|
+
timedata = pd.DataFrame(
|
|
120
|
+
columns=("Apparent Elevation", "Solar Azimuth", "True-Tracking"),
|
|
121
|
+
data=(
|
|
122
|
+
(2.404287, 122.791770, -84.440),
|
|
123
|
+
(11.263058, 133.288729, -72.604),
|
|
124
|
+
(18.733558, 145.285552, -59.861),
|
|
125
|
+
(24.109076, 158.939435, -45.578),
|
|
126
|
+
(26.810735, 173.931802, -28.764),
|
|
127
|
+
(26.482495, 189.371536, -8.475),
|
|
128
|
+
(23.170447, 204.136810, 15.120),
|
|
129
|
+
(17.296785, 217.446538, 39.562),
|
|
130
|
+
(9.461862, 229.102218, 61.587),
|
|
131
|
+
(0.524817, 239.330401, 79.530),
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
timedata.index = pd.date_range(
|
|
135
|
+
"2019-01-01T08", "2019-01-01T17", freq="1h", tz=tzinfo
|
|
136
|
+
)
|
|
137
|
+
timedata["Apparent Zenith"] = 90.0 - timedata["Apparent Elevation"]
|
|
138
|
+
return (axis_tilt_angle, axis_azimuth_angle, timedata)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@pytest.fixture
|
|
142
|
+
def projected_solar_zenith_angle_edge_cases():
|
|
143
|
+
premises_and_result_matrix = pd.DataFrame(
|
|
144
|
+
data=[
|
|
145
|
+
# s_zen | s_azm | ax_tilt | ax_azm | psza
|
|
146
|
+
[ 0, 0, 0, 0, 0],
|
|
147
|
+
[ 0, 180, 0, 0, 0],
|
|
148
|
+
[ 0, 0, 0, 180, 0],
|
|
149
|
+
[ 0, 180, 0, 180, 0],
|
|
150
|
+
[ 45, 0, 0, 180, 0],
|
|
151
|
+
[ 45, 90, 0, 180, -45],
|
|
152
|
+
[ 45, 270, 0, 180, 45],
|
|
153
|
+
[ 45, 90, 90, 180, -90],
|
|
154
|
+
[ 45, 270, 90, 180, 90],
|
|
155
|
+
[ 45, 90, 90, 0, 90],
|
|
156
|
+
[ 45, 270, 90, 0, -90],
|
|
157
|
+
[ 45, 45, 90, 180, -135],
|
|
158
|
+
[ 45, 315, 90, 180, 135],
|
|
159
|
+
],
|
|
160
|
+
columns=["solar_zenith", "solar_azimuth", "axis_tilt", "axis_azimuth",
|
|
161
|
+
"psza"],
|
|
162
|
+
)
|
|
163
|
+
return premises_and_result_matrix
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_projected_solar_zenith_angle_numeric(
|
|
167
|
+
true_tracking_angle_and_inputs_NREL,
|
|
168
|
+
projected_solar_zenith_angle_edge_cases
|
|
169
|
+
):
|
|
170
|
+
psza_func = shading.projected_solar_zenith_angle
|
|
171
|
+
axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL
|
|
172
|
+
# test against data provided by NREL
|
|
173
|
+
psz = psza_func(
|
|
174
|
+
timedata["Apparent Zenith"],
|
|
175
|
+
timedata["Solar Azimuth"],
|
|
176
|
+
axis_tilt,
|
|
177
|
+
axis_azimuth,
|
|
178
|
+
)
|
|
179
|
+
assert_allclose(psz, timedata["True-Tracking"], atol=1e-3)
|
|
180
|
+
# test by changing axis azimuth and tilt
|
|
181
|
+
psza = psza_func(
|
|
182
|
+
timedata["Apparent Zenith"],
|
|
183
|
+
timedata["Solar Azimuth"],
|
|
184
|
+
-axis_tilt,
|
|
185
|
+
axis_azimuth - 180,
|
|
186
|
+
)
|
|
187
|
+
assert_allclose(psza, -timedata["True-Tracking"], atol=1e-3)
|
|
188
|
+
|
|
189
|
+
# test edge cases
|
|
190
|
+
solar_zenith, solar_azimuth, axis_tilt, axis_azimuth, psza_expected = (
|
|
191
|
+
v for _, v in projected_solar_zenith_angle_edge_cases.items()
|
|
192
|
+
)
|
|
193
|
+
psza = psza_func(
|
|
194
|
+
solar_zenith,
|
|
195
|
+
solar_azimuth,
|
|
196
|
+
axis_tilt,
|
|
197
|
+
axis_azimuth,
|
|
198
|
+
)
|
|
199
|
+
assert_allclose(psza, psza_expected, atol=1e-9)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@pytest.mark.parametrize(
|
|
203
|
+
"cast_type, cast_func",
|
|
204
|
+
[
|
|
205
|
+
(float, lambda x: float(x)),
|
|
206
|
+
(np.ndarray, lambda x: np.array([x])),
|
|
207
|
+
(pd.Series, lambda x: pd.Series(data=[x])),
|
|
208
|
+
],
|
|
209
|
+
)
|
|
210
|
+
def test_projected_solar_zenith_angle_datatypes(
|
|
211
|
+
cast_type, cast_func, true_tracking_angle_and_inputs_NREL
|
|
212
|
+
):
|
|
213
|
+
psz_func = shading.projected_solar_zenith_angle
|
|
214
|
+
axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL
|
|
215
|
+
sun_apparent_zenith = timedata["Apparent Zenith"].iloc[0]
|
|
216
|
+
sun_azimuth = timedata["Solar Azimuth"].iloc[0]
|
|
217
|
+
|
|
218
|
+
axis_tilt, axis_azimuth, sun_apparent_zenith, sun_azimuth = (
|
|
219
|
+
cast_func(sun_apparent_zenith),
|
|
220
|
+
cast_func(sun_azimuth),
|
|
221
|
+
cast_func(axis_tilt),
|
|
222
|
+
cast_func(axis_azimuth),
|
|
223
|
+
)
|
|
224
|
+
psz = psz_func(sun_apparent_zenith, axis_azimuth, axis_tilt, axis_azimuth)
|
|
225
|
+
assert isinstance(psz, cast_type)
|
pvlib/tests/test_singlediode.py
CHANGED
|
@@ -10,6 +10,7 @@ from pvlib.singlediode import (bishop88_mpp, estimate_voc, VOLTAGE_BUILTIN,
|
|
|
10
10
|
bishop88, bishop88_i_from_v, bishop88_v_from_i)
|
|
11
11
|
from pvlib._deprecation import pvlibDeprecationWarning
|
|
12
12
|
import pytest
|
|
13
|
+
from numpy.testing import assert_array_equal
|
|
13
14
|
from .conftest import DATA_DIR
|
|
14
15
|
|
|
15
16
|
POA = 888
|
|
@@ -167,17 +168,24 @@ def test_singlediode_precision(method, precise_iv_curves):
|
|
|
167
168
|
assert np.allclose(pc['i_xx'], outs['i_xx'], atol=1e-6, rtol=0)
|
|
168
169
|
|
|
169
170
|
|
|
170
|
-
def test_singlediode_lambert_negative_voc():
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
def test_singlediode_lambert_negative_voc(mocker):
|
|
172
|
+
"""Tests approximation to zero of v_oc when it is negative and small.
|
|
173
|
+
See singlediode.py:_lambertw > comment 'Set small elements <0 in v_oc to 0'
|
|
174
|
+
"""
|
|
175
|
+
# Next values should result in a negative v_oc out of `_lambertw_v_from_i`
|
|
176
|
+
# however, we can't ensure that the output belongs to (-1e-12, 0), so we
|
|
177
|
+
# mock it. It depends on the platform and Python distro. See issue #2000.
|
|
178
|
+
patcher = mocker.patch("pvlib.singlediode._lambertw_v_from_i")
|
|
179
|
+
x = np.array([0.0, 1.480501e-11, 0.178, 8000.0, 1.797559])
|
|
180
|
+
patcher.return_value = -9.999e-13
|
|
181
|
+
outs = pvsystem.singlediode(*x, method="lambertw")
|
|
182
|
+
assert outs["v_oc"] == 0
|
|
176
183
|
|
|
177
184
|
# Testing for an array
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
185
|
+
patcher.return_value = np.array([-9.999e-13, -1.001e-13])
|
|
186
|
+
x = np.array([x, x]).T
|
|
187
|
+
outs = pvsystem.singlediode(*x, method="lambertw")
|
|
188
|
+
assert_array_equal(outs["v_oc"], [0, 0])
|
|
181
189
|
|
|
182
190
|
|
|
183
191
|
@pytest.mark.parametrize('method', ['lambertw'])
|
|
@@ -347,7 +355,7 @@ def test_pvsyst_recombination_loss(method, poa, temp_cell, expected, tol):
|
|
|
347
355
|
# other conditions with breakdown model on and recombination model off
|
|
348
356
|
(
|
|
349
357
|
(1.e-4, -5.5, 3.28),
|
|
350
|
-
(0., np.
|
|
358
|
+
(0., np.inf),
|
|
351
359
|
POA,
|
|
352
360
|
TCELL,
|
|
353
361
|
{
|
|
@@ -567,3 +575,53 @@ def test_bishop88_pdSeries_len_one(method, bishop88_arguments):
|
|
|
567
575
|
bishop88_i_from_v(pd.Series([0]), **bishop88_arguments, method=method)
|
|
568
576
|
bishop88_v_from_i(pd.Series([0]), **bishop88_arguments, method=method)
|
|
569
577
|
bishop88_mpp(**bishop88_arguments, method=method)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _sde_check_solution(i, v, il, io, rs, rsh, a, d2mutau=0., NsVbi=np.inf):
|
|
581
|
+
vd = v + rs * i
|
|
582
|
+
return il - io*np.expm1(vd/a) - vd/rsh - il*d2mutau/(NsVbi - vd) - i
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
@pytest.mark.parametrize('method', ['newton', 'brentq'])
|
|
586
|
+
def test_bishop88_init_cond(method):
|
|
587
|
+
# GH 2013
|
|
588
|
+
p = {'alpha_sc': 0.0012256,
|
|
589
|
+
'gamma_ref': 1.2916241612804187,
|
|
590
|
+
'mu_gamma': 0.00047308959960937403,
|
|
591
|
+
'I_L_ref': 3.068717040806731,
|
|
592
|
+
'I_o_ref': 2.2691248021217617e-11,
|
|
593
|
+
'R_sh_ref': 7000,
|
|
594
|
+
'R_sh_0': 7000,
|
|
595
|
+
'R_s': 4.602,
|
|
596
|
+
'cells_in_series': 268,
|
|
597
|
+
'R_sh_exp': 5.5,
|
|
598
|
+
'EgRef': 1.5}
|
|
599
|
+
NsVbi = 268 * 0.9
|
|
600
|
+
d2mutau = 1.4
|
|
601
|
+
irrad = np.arange(20, 1100, 20)
|
|
602
|
+
tc = np.arange(-25, 74, 1)
|
|
603
|
+
weather = np.array(np.meshgrid(irrad, tc)).T.reshape(-1, 2)
|
|
604
|
+
# with the above parameters and weather conditions, a few combinations
|
|
605
|
+
# result in voc_est > NsVbi, which causes failure of brentq and newton
|
|
606
|
+
# when the recombination parameters NsVbi and d2mutau are used.
|
|
607
|
+
sde_params = pvsystem.calcparams_pvsyst(weather[:, 0], weather[:, 1], **p)
|
|
608
|
+
# test _mpp
|
|
609
|
+
result = bishop88_mpp(*sde_params, d2mutau=d2mutau, NsVbi=NsVbi)
|
|
610
|
+
imp, vmp, pmp = result
|
|
611
|
+
err = np.abs(_sde_check_solution(
|
|
612
|
+
imp, vmp, sde_params[0], sde_params[1], sde_params[2], sde_params[3],
|
|
613
|
+
sde_params[4], d2mutau=d2mutau, NsVbi=NsVbi))
|
|
614
|
+
bad_results = np.isnan(pmp) | (pmp < 0) | (err > 0.00001) # 0.01mA error
|
|
615
|
+
assert not bad_results.any()
|
|
616
|
+
# test v_from_i
|
|
617
|
+
vmp2 = bishop88_v_from_i(imp, *sde_params, d2mutau=d2mutau, NsVbi=NsVbi)
|
|
618
|
+
err = np.abs(_sde_check_solution(imp, vmp2, *sde_params, d2mutau=d2mutau,
|
|
619
|
+
NsVbi=NsVbi))
|
|
620
|
+
bad_results = np.isnan(vmp2) | (vmp2 < 0) | (err > 0.00001)
|
|
621
|
+
assert not bad_results.any()
|
|
622
|
+
# test v_from_i
|
|
623
|
+
imp2 = bishop88_i_from_v(vmp, *sde_params, d2mutau=d2mutau, NsVbi=NsVbi)
|
|
624
|
+
err = np.abs(_sde_check_solution(imp2, vmp, *sde_params, d2mutau=d2mutau,
|
|
625
|
+
NsVbi=NsVbi))
|
|
626
|
+
bad_results = np.isnan(imp2) | (imp2 < 0) | (err > 0.00001)
|
|
627
|
+
assert not bad_results.any()
|
pvlib/tests/test_snow.py
CHANGED
|
@@ -42,7 +42,7 @@ def test_coverage_nrel_subhourly():
|
|
|
42
42
|
surface_tilt = 45
|
|
43
43
|
slide_amount_coefficient = 0.197
|
|
44
44
|
dt = pd.date_range(start="2019-1-1 11:00:00", end="2019-1-1 14:00:00",
|
|
45
|
-
freq='
|
|
45
|
+
freq='15min')
|
|
46
46
|
poa_irradiance = pd.Series([400, 200, 100, 1234, 134, 982, 100, 100, 100,
|
|
47
47
|
100, 100, 100, 0],
|
|
48
48
|
index=dt)
|