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.
Files changed (53) hide show
  1. pvlib/__init__.py +1 -0
  2. pvlib/bifacial/utils.py +2 -1
  3. pvlib/clearsky.py +7 -8
  4. pvlib/iam.py +3 -3
  5. pvlib/inverter.py +3 -3
  6. pvlib/iotools/__init__.py +2 -0
  7. pvlib/iotools/solargis.py +214 -0
  8. pvlib/iotools/solcast.py +2 -7
  9. pvlib/iotools/solrad.py +121 -23
  10. pvlib/iotools/srml.py +12 -12
  11. pvlib/iotools/surfrad.py +2 -2
  12. pvlib/irradiance.py +28 -22
  13. pvlib/location.py +3 -1
  14. pvlib/modelchain.py +10 -9
  15. pvlib/pvarray.py +127 -0
  16. pvlib/pvsystem.py +52 -43
  17. pvlib/scaling.py +4 -2
  18. pvlib/shading.py +110 -0
  19. pvlib/singlediode.py +37 -9
  20. pvlib/snow.py +3 -1
  21. pvlib/solarposition.py +38 -30
  22. pvlib/spa.py +3 -11
  23. pvlib/spectrum/mismatch.py +2 -1
  24. pvlib/temperature.py +3 -2
  25. pvlib/tests/bifacial/test_utils.py +6 -5
  26. pvlib/tests/conftest.py +13 -14
  27. pvlib/tests/iotools/test_sodapro.py +2 -1
  28. pvlib/tests/iotools/test_solargis.py +68 -0
  29. pvlib/tests/iotools/test_solcast.py +2 -2
  30. pvlib/tests/iotools/test_solrad.py +58 -7
  31. pvlib/tests/iotools/test_srml.py +7 -14
  32. pvlib/tests/test_clearsky.py +1 -1
  33. pvlib/tests/test_irradiance.py +24 -8
  34. pvlib/tests/test_location.py +1 -1
  35. pvlib/tests/test_modelchain.py +37 -26
  36. pvlib/tests/test_pvarray.py +25 -0
  37. pvlib/tests/test_pvsystem.py +76 -104
  38. pvlib/tests/test_shading.py +130 -11
  39. pvlib/tests/test_singlediode.py +68 -10
  40. pvlib/tests/test_snow.py +1 -1
  41. pvlib/tests/test_solarposition.py +121 -7
  42. pvlib/tests/test_spa.py +5 -15
  43. pvlib/tests/test_temperature.py +4 -4
  44. pvlib/tests/test_tracking.py +2 -2
  45. pvlib/tracking.py +8 -38
  46. pvlib/version.py +1 -5
  47. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/METADATA +9 -33
  48. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/RECORD +52 -51
  49. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/WHEEL +1 -1
  50. pvlib/spa_c_files/SPA_NOTICE.md +0 -39
  51. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/AUTHORS.md +0 -0
  52. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/LICENSE +0 -0
  53. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/top_level.txt +0 -0
@@ -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 test_retrieve_sam_raise_no_parameters():
106
+ def test_retrieve_sam_raises_exceptions():
108
107
  """
109
- Raise an exception if no parameters are provided to `retrieve_sam()`.
108
+ Raise an exception if an invalid parameter is provided to `retrieve_sam()`.
110
109
  """
111
- with pytest.raises(ValueError) as error:
110
+ with pytest.raises(ValueError, match="Please provide either"):
112
111
  pvsystem.retrieve_sam()
113
- assert 'A name or path must be provided!' == str(error.value)
114
-
115
-
116
- def test_retrieve_sam_cecmod():
117
- """
118
- Test the expected data is retrieved from the CEC module database. In
119
- particular, check for a known module in the database and check for the
120
- expected keys for that module.
121
- """
122
- data = pvsystem.retrieve_sam('cecmod')
123
- keys = [
124
- 'BIPV',
125
- 'Date',
126
- 'T_NOCT',
127
- 'A_c',
128
- 'N_s',
129
- 'I_sc_ref',
130
- 'V_oc_ref',
131
- 'I_mp_ref',
132
- 'V_mp_ref',
133
- 'alpha_sc',
134
- 'beta_oc',
135
- 'a_ref',
136
- 'I_L_ref',
137
- 'I_o_ref',
138
- 'R_s',
139
- 'R_sh_ref',
140
- 'Adjust',
141
- 'gamma_r',
142
- 'Version',
143
- 'STC',
144
- 'PTC',
145
- 'Technology',
146
- 'Bifacial',
147
- 'Length',
148
- 'Width',
149
- ]
150
- module = 'Itek_Energy_LLC_iT_300_HE'
151
- assert module in data
152
- assert set(data[module].keys()) == set(keys)
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
- def test_retrieve_sam_cecinverter():
156
- """
157
- Test the expected data is retrieved from the CEC inverter database. In
158
- particular, check for a known inverter in the database and check for the
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='12H')
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
- [[ -5.0608322 , -4.65037767, nan, nan,
196
- nan, -4.91119927, -4.15367716],
197
- [ 2.545575 , 2.28773882, 56.86182059, 47.21121608,
198
- 108.00693168, 2.48357383, 1.71782772],
199
- [ 5.65584763, 5.01709903, 54.1943277 , 42.51861718,
200
- 213.32011294, 5.52987899, 3.48660728],
201
- [ nan, nan, nan, nan,
202
- nan, nan, nan],
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'] = 5.09115
214
- expected['i_mp'] = 4.5462909092579995
215
- expected['v_oc'] = 59.260800000000003
216
- expected['v_mp'] = 48.315600000000003
217
- expected['p_mp'] = 219.65677305534581
218
- expected['i_x'] = 4.9759899999999995
219
- expected['i_xx'] = 3.1880204359100004
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='1H')
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='H', periods=3)
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='H', periods=3)
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='H', periods=3)
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='12H')
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='12H')
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='12H')
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='12H')
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='12H')
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='6H')
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='6H')
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='D')
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='D'))
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)
@@ -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 = {'height': 1.0,
13
- 'pitch': 2.,
14
- 'surface_tilt': 30.,
15
- 'surface_azimuth': 180.,
16
- 'rotation': -30.} # rotation of right edge relative to horizontal
17
- syst['gcr'] = 1.0 / syst['pitch']
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., 0.5, 1.0])
24
- angles = shading.ground_angle(
25
- ts['surface_tilt'], ts['gcr'], x)
26
- expected_angles = np.array([0., 5.866738789543952, 9.896090638982903])
27
+ 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('2019-01-01', freq='h', periods=3)
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)
@@ -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
- # Those values result in a negative v_oc out of `_lambertw_v_from_i`
173
- x = np.array([0., 1.480501e-11, 0.178, 8000., 1.797559])
174
- outs = pvsystem.singlediode(*x, method='lambertw')
175
- assert outs['v_oc'] == 0
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
- x = np.array([x, x]).T
179
- outs = pvsystem.singlediode(*x, method='lambertw')
180
- assert np.array_equal(outs['v_oc'], [0, 0])
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.Inf),
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='15T')
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)