pvlib 0.11.1__py3-none-any.whl → 0.12.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.
Files changed (149) hide show
  1. pvlib/__init__.py +1 -0
  2. pvlib/_deprecation.py +73 -0
  3. pvlib/atmosphere.py +77 -7
  4. pvlib/bifacial/infinite_sheds.py +4 -3
  5. pvlib/bifacial/utils.py +2 -1
  6. pvlib/clearsky.py +35 -22
  7. pvlib/iam.py +4 -4
  8. pvlib/iotools/midc.py +1 -1
  9. pvlib/iotools/psm3.py +1 -1
  10. pvlib/iotools/pvgis.py +10 -12
  11. pvlib/iotools/tmy.py +3 -69
  12. pvlib/irradiance.py +112 -55
  13. pvlib/ivtools/sdm.py +75 -52
  14. pvlib/location.py +73 -33
  15. pvlib/modelchain.py +18 -35
  16. pvlib/pvsystem.py +139 -94
  17. pvlib/snow.py +64 -28
  18. pvlib/solarposition.py +46 -30
  19. pvlib/spa.py +4 -2
  20. pvlib/spectrum/__init__.py +0 -1
  21. pvlib/spectrum/irradiance.py +2 -64
  22. pvlib/spectrum/mismatch.py +3 -3
  23. pvlib/spectrum/spectrl2.py +2 -1
  24. pvlib/temperature.py +49 -3
  25. pvlib/tools.py +6 -5
  26. {pvlib-0.11.1.dist-info → pvlib-0.12.0.dist-info}/METADATA +14 -11
  27. pvlib-0.12.0.dist-info/RECORD +75 -0
  28. {pvlib-0.11.1.dist-info → pvlib-0.12.0.dist-info}/WHEEL +1 -1
  29. pvlib/data/BIRD_08_16_2012.csv +0 -8761
  30. pvlib/data/BIRD_08_16_2012_patm.csv +0 -8761
  31. pvlib/data/Burlington, United States SolarAnywhere Time Series 2021 Lat_44_465 Lon_-73_205 TMY3 format.csv +0 -8762
  32. pvlib/data/Burlington, United States SolarAnywhere Time Series 20210101 to 20210103 Lat_44_4675 Lon_-73_2075 SA format.csv +0 -578
  33. pvlib/data/Burlington, United States SolarAnywhere Typical GHI Year Lat_44_465 Lon_-73_205 SA format.csv +0 -74
  34. pvlib/data/CPS SCH275KTL-DO-US-800-250kW_275kVA_1.OND +0 -146
  35. pvlib/data/CRNS0101-05-2019-AZ_Tucson_11_W.txt +0 -4
  36. pvlib/data/CRN_with_problems.txt +0 -3
  37. pvlib/data/ET-M772BH550GL.PAN +0 -75
  38. pvlib/data/NLD_Amsterdam062400_IWEC.epw +0 -8768
  39. pvlib/data/PVsyst_demo.csv +0 -10757
  40. pvlib/data/PVsyst_demo_model.csv +0 -3588
  41. pvlib/data/SRML-day-EUPO1801.txt +0 -1441
  42. pvlib/data/abq19056.dat +0 -6
  43. pvlib/data/aod550_tcwv_20121101_test.nc +0 -0
  44. pvlib/data/bishop88_numerical_precision.csv +0 -101
  45. pvlib/data/bsrn-lr0100-pay0616.dat +0 -86901
  46. pvlib/data/bsrn-pay0616.dat.gz +0 -0
  47. pvlib/data/cams_mcclear_1min_verbose.csv +0 -60
  48. pvlib/data/cams_mcclear_monthly.csv +0 -42
  49. pvlib/data/cams_radiation_1min_verbose.csv +0 -72
  50. pvlib/data/cams_radiation_monthly.csv +0 -47
  51. pvlib/data/detect_clearsky_data.csv +0 -35
  52. pvlib/data/detect_clearsky_threshold_data.csv +0 -126
  53. pvlib/data/greensboro_kimber_soil_manwash.dat +0 -8761
  54. pvlib/data/greensboro_kimber_soil_nowash.dat +0 -8761
  55. pvlib/data/inverter_fit_snl_meas.csv +0 -127
  56. pvlib/data/inverter_fit_snl_sim.csv +0 -19
  57. pvlib/data/ivtools_numdiff.csv +0 -52
  58. pvlib/data/midc_20181014.txt +0 -1441
  59. pvlib/data/midc_raw_20181018.txt +0 -1441
  60. pvlib/data/midc_raw_short_header_20191115.txt +0 -1441
  61. pvlib/data/msn19056.dat +0 -6
  62. pvlib/data/precise_iv_curves1.json +0 -10251
  63. pvlib/data/precise_iv_curves2.json +0 -10251
  64. pvlib/data/precise_iv_curves_parameter_sets1.csv +0 -33
  65. pvlib/data/precise_iv_curves_parameter_sets2.csv +0 -33
  66. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA2_10kWp_CIS_5_2a_2013_2014.json +0 -1
  67. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA_30deg_0deg_2016_2016.csv +0 -35
  68. pvlib/data/pvgis_tmy_meta.json +0 -32
  69. pvlib/data/pvgis_tmy_test.dat +0 -8761
  70. pvlib/data/pvwatts_8760_rackmount.csv +0 -8779
  71. pvlib/data/pvwatts_8760_roofmount.csv +0 -8779
  72. pvlib/data/singleaxis_tracker_wslope.csv +0 -8761
  73. pvlib/data/spectrl2_example_spectra.csv +0 -123
  74. pvlib/data/surfrad-slv16001.dat +0 -1442
  75. pvlib/data/test_psm3_2017.csv +0 -17521
  76. pvlib/data/test_psm3_2019_5min.csv +0 -289
  77. pvlib/data/test_psm3_tmy-2017.csv +0 -8761
  78. pvlib/data/test_read_psm3.csv +0 -17523
  79. pvlib/data/test_read_pvgis_horizon.csv +0 -49
  80. pvlib/data/tmy_45.000_8.000_2005_2020.csv +0 -8789
  81. pvlib/data/tmy_45.000_8.000_2005_2020.epw +0 -8768
  82. pvlib/data/tmy_45.000_8.000_2005_2020.json +0 -1
  83. pvlib/data/tmy_45.000_8.000_2005_2020.txt +0 -8761
  84. pvlib/data/tmy_45.000_8.000_userhorizon.json +0 -1
  85. pvlib/data/variables_style_rules.csv +0 -56
  86. pvlib/spa_c_files/README.md +0 -81
  87. pvlib/spa_c_files/cspa_py.pxd +0 -43
  88. pvlib/spa_c_files/spa_py.pyx +0 -30
  89. pvlib/tests/__init__.py +0 -0
  90. pvlib/tests/bifacial/__init__.py +0 -0
  91. pvlib/tests/bifacial/test_infinite_sheds.py +0 -317
  92. pvlib/tests/bifacial/test_losses_models.py +0 -54
  93. pvlib/tests/bifacial/test_pvfactors.py +0 -82
  94. pvlib/tests/bifacial/test_utils.py +0 -192
  95. pvlib/tests/conftest.py +0 -476
  96. pvlib/tests/iotools/__init__.py +0 -0
  97. pvlib/tests/iotools/test_acis.py +0 -213
  98. pvlib/tests/iotools/test_bsrn.py +0 -131
  99. pvlib/tests/iotools/test_crn.py +0 -95
  100. pvlib/tests/iotools/test_epw.py +0 -23
  101. pvlib/tests/iotools/test_midc.py +0 -89
  102. pvlib/tests/iotools/test_panond.py +0 -32
  103. pvlib/tests/iotools/test_psm3.py +0 -198
  104. pvlib/tests/iotools/test_pvgis.py +0 -644
  105. pvlib/tests/iotools/test_sodapro.py +0 -298
  106. pvlib/tests/iotools/test_solaranywhere.py +0 -287
  107. pvlib/tests/iotools/test_solargis.py +0 -68
  108. pvlib/tests/iotools/test_solcast.py +0 -324
  109. pvlib/tests/iotools/test_solrad.py +0 -152
  110. pvlib/tests/iotools/test_srml.py +0 -124
  111. pvlib/tests/iotools/test_surfrad.py +0 -75
  112. pvlib/tests/iotools/test_tmy.py +0 -133
  113. pvlib/tests/ivtools/__init__.py +0 -0
  114. pvlib/tests/ivtools/test_sde.py +0 -230
  115. pvlib/tests/ivtools/test_sdm.py +0 -407
  116. pvlib/tests/ivtools/test_utils.py +0 -173
  117. pvlib/tests/spectrum/__init__.py +0 -0
  118. pvlib/tests/spectrum/conftest.py +0 -40
  119. pvlib/tests/spectrum/test_irradiance.py +0 -138
  120. pvlib/tests/spectrum/test_mismatch.py +0 -304
  121. pvlib/tests/spectrum/test_response.py +0 -124
  122. pvlib/tests/spectrum/test_spectrl2.py +0 -72
  123. pvlib/tests/test_albedo.py +0 -84
  124. pvlib/tests/test_atmosphere.py +0 -204
  125. pvlib/tests/test_clearsky.py +0 -878
  126. pvlib/tests/test_conftest.py +0 -81
  127. pvlib/tests/test_iam.py +0 -555
  128. pvlib/tests/test_inverter.py +0 -213
  129. pvlib/tests/test_irradiance.py +0 -1441
  130. pvlib/tests/test_location.py +0 -356
  131. pvlib/tests/test_modelchain.py +0 -2020
  132. pvlib/tests/test_numerical_precision.py +0 -124
  133. pvlib/tests/test_pvarray.py +0 -71
  134. pvlib/tests/test_pvsystem.py +0 -2495
  135. pvlib/tests/test_scaling.py +0 -207
  136. pvlib/tests/test_shading.py +0 -391
  137. pvlib/tests/test_singlediode.py +0 -608
  138. pvlib/tests/test_snow.py +0 -212
  139. pvlib/tests/test_soiling.py +0 -230
  140. pvlib/tests/test_solarposition.py +0 -933
  141. pvlib/tests/test_spa.py +0 -425
  142. pvlib/tests/test_temperature.py +0 -470
  143. pvlib/tests/test_tools.py +0 -146
  144. pvlib/tests/test_tracking.py +0 -474
  145. pvlib/tests/test_transformer.py +0 -60
  146. pvlib-0.11.1.dist-info/RECORD +0 -192
  147. {pvlib-0.11.1.dist-info → pvlib-0.12.0.dist-info/licenses}/AUTHORS.md +0 -0
  148. {pvlib-0.11.1.dist-info → pvlib-0.12.0.dist-info/licenses}/LICENSE +0 -0
  149. {pvlib-0.11.1.dist-info → pvlib-0.12.0.dist-info}/top_level.txt +0 -0
@@ -1,207 +0,0 @@
1
- import numpy as np
2
- import pandas as pd
3
-
4
- import pytest
5
- from numpy.testing import assert_almost_equal
6
-
7
- from pvlib import scaling
8
-
9
-
10
- # Sample cloud speed
11
- cloud_speed = 5
12
-
13
- # Sample dt
14
- dt = 1
15
-
16
-
17
- @pytest.fixture
18
- def coordinates():
19
- # Sample positions in lat/lon
20
- lat = np.array((9.99, 10, 10.01))
21
- lon = np.array((4.99, 5, 5.01))
22
- coordinates = np.array([(lati, loni) for (lati, loni) in zip(lat, lon)])
23
- return coordinates
24
-
25
-
26
- @pytest.fixture
27
- def clear_sky_index():
28
- # Generate a sample clear_sky_index
29
- clear_sky_index = np.ones(10000)
30
- clear_sky_index[5000:5005] = np.array([1, 1, 1.1, 0.9, 1])
31
- return clear_sky_index
32
-
33
-
34
- @pytest.fixture
35
- def time(clear_sky_index):
36
- # Sample time vector
37
- return np.arange(0, len(clear_sky_index))
38
-
39
-
40
- @pytest.fixture
41
- def time_60s(clear_sky_index):
42
- # Sample time vector 60s resolution
43
- return np.arange(0, len(clear_sky_index))*60
44
-
45
-
46
- @pytest.fixture
47
- def time_500ms(clear_sky_index):
48
- # Sample time vector 0.5s resolution
49
- return np.arange(0, len(clear_sky_index))*0.5
50
-
51
-
52
- @pytest.fixture
53
- def positions():
54
- # Sample positions based on the previous lat/lon (calculated manually)
55
- expect_xpos = np.array([554863.4, 555975.4, 557087.3])
56
- expect_ypos = np.array([1110838.8, 1111950.8, 1113062.7])
57
- return np.array([pt for pt in zip(expect_xpos, expect_ypos)])
58
-
59
-
60
- @pytest.fixture
61
- def expect_tmscale():
62
- # Expected timescales for dt = 1
63
- return [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]
64
-
65
-
66
- @pytest.fixture
67
- def expect_tmscale_1min():
68
- # Expected timescales for dt = 60
69
- return [60, 120, 240, 480, 960, 1920, 3840]
70
-
71
-
72
- @pytest.fixture
73
- def expect_tmscale_500ms():
74
- # Expected timescales for dt = 0.5
75
- return [0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]
76
-
77
-
78
- @pytest.fixture
79
- def expect_wavelet():
80
- # Expected wavelet for indices 5000:5004 for clear_sky_index above (Matlab)
81
- e = np.zeros([13, 5])
82
- e[0, :] = np.array([0, -0.05, 0.1, -0.05, 0])
83
- e[1, :] = np.array([-0.025, 0.05, 0., -0.05, 0.025])
84
- e[2, :] = np.array([0.025, 0., 0., 0., -0.025])
85
- e[-1, :] = np.array([1, 1, 1, 1, 1])
86
- return e
87
-
88
-
89
- @pytest.fixture
90
- def expect_cs_smooth():
91
- # Expected smoothed clear sky index for indices 5000:5004 (Matlab)
92
- return np.array([1., 1., 1.05774, 0.94226, 1.])
93
-
94
-
95
- @pytest.fixture
96
- def expect_vr():
97
- # Expected VR for expecttmscale
98
- return np.array([3., 3., 3., 3., 3., 3., 2.9997844, 2.9708118, 2.6806291,
99
- 2.0726611, 1.5653324, 1.2812714, 1.1389995])
100
-
101
-
102
- def test_latlon_to_xy_zero():
103
- coord = [0, 0]
104
- pos_e = [0, 0]
105
- pos = scaling.latlon_to_xy(coord)
106
- assert_almost_equal(pos, pos_e, decimal=1)
107
-
108
-
109
- def test_latlon_to_xy_single(coordinates, positions):
110
- # Must test against central value, because latlon_to_xy uses the mean
111
- coord = coordinates[1]
112
- pos = scaling.latlon_to_xy(coord)
113
- assert_almost_equal(pos, positions[1], decimal=1)
114
-
115
-
116
- def test_latlon_to_xy_array(coordinates, positions):
117
- pos = scaling.latlon_to_xy(coordinates)
118
- assert_almost_equal(pos, positions, decimal=1)
119
-
120
-
121
- def test_latlon_to_xy_list(coordinates, positions):
122
- pos = scaling.latlon_to_xy(coordinates.tolist())
123
- assert_almost_equal(pos, positions, decimal=1)
124
-
125
-
126
- def test_compute_wavelet_series(clear_sky_index, time,
127
- expect_tmscale, expect_wavelet):
128
- csi_series = pd.Series(clear_sky_index, index=time)
129
- wavelet, tmscale = scaling._compute_wavelet(csi_series)
130
- assert_almost_equal(tmscale, expect_tmscale)
131
- assert_almost_equal(wavelet[:, 5000:5005], expect_wavelet)
132
-
133
-
134
- def test_compute_wavelet_series_numindex(clear_sky_index, time,
135
- expect_tmscale, expect_wavelet):
136
- dtindex = pd.to_datetime(time, unit='s')
137
- csi_series = pd.Series(clear_sky_index, index=dtindex)
138
- wavelet, tmscale = scaling._compute_wavelet(csi_series)
139
- assert_almost_equal(tmscale, expect_tmscale)
140
- assert_almost_equal(wavelet[:, 5000:5005], expect_wavelet)
141
-
142
-
143
- def test_compute_wavelet_series_highres(clear_sky_index, time_500ms,
144
- expect_tmscale_500ms, expect_wavelet):
145
- dtindex = pd.to_datetime(time_500ms, unit='s')
146
- csi_series = pd.Series(clear_sky_index, index=dtindex)
147
- wavelet, tmscale = scaling._compute_wavelet(csi_series)
148
- assert_almost_equal(tmscale, expect_tmscale_500ms)
149
- assert_almost_equal(wavelet[:, 5000:5005].shape, (14, 5))
150
-
151
-
152
- def test_compute_wavelet_series_minuteres(clear_sky_index, time_60s,
153
- expect_tmscale_1min, expect_wavelet):
154
- dtindex = pd.to_datetime(time_60s, unit='s')
155
- csi_series = pd.Series(clear_sky_index, index=dtindex)
156
- wavelet, tmscale = scaling._compute_wavelet(csi_series)
157
- assert_almost_equal(tmscale, expect_tmscale_1min)
158
- assert_almost_equal(wavelet[:, 5000:5005].shape,
159
- expect_wavelet[0:len(tmscale), :].shape)
160
-
161
-
162
- def test_compute_wavelet_array(clear_sky_index,
163
- expect_tmscale, expect_wavelet):
164
- wavelet, tmscale = scaling._compute_wavelet(clear_sky_index, dt)
165
- assert_almost_equal(tmscale, expect_tmscale)
166
- assert_almost_equal(wavelet[:, 5000:5005], expect_wavelet)
167
-
168
-
169
- def test_compute_wavelet_array_invalid(clear_sky_index):
170
- with pytest.raises(ValueError):
171
- scaling._compute_wavelet(clear_sky_index)
172
-
173
-
174
- def test_compute_wavelet_dwttheory(clear_sky_index, time,
175
- expect_tmscale, expect_wavelet):
176
- # Confirm detail coeffs sum to original signal
177
- csi_series = pd.Series(clear_sky_index, index=time)
178
- wavelet, tmscale = scaling._compute_wavelet(csi_series)
179
- assert_almost_equal(np.sum(wavelet, 0), csi_series)
180
-
181
-
182
- def test_compute_vr(positions, expect_tmscale, expect_vr):
183
- vr = scaling._compute_vr(positions, cloud_speed, np.array(expect_tmscale))
184
- assert_almost_equal(vr, expect_vr)
185
-
186
-
187
- def test_wvm_series(clear_sky_index, time, positions, expect_cs_smooth):
188
- csi_series = pd.Series(clear_sky_index, index=time)
189
- cs_sm, _, _ = scaling.wvm(csi_series, positions, cloud_speed)
190
- assert_almost_equal(cs_sm[5000:5005], expect_cs_smooth, decimal=4)
191
-
192
-
193
- def test_wvm_array(clear_sky_index, positions, expect_cs_smooth):
194
- cs_sm, _, _ = scaling.wvm(clear_sky_index, positions, cloud_speed, dt=dt)
195
- assert_almost_equal(cs_sm[5000:5005], expect_cs_smooth, decimal=4)
196
-
197
-
198
- def test_wvm_series_xyaslist(clear_sky_index, time, positions,
199
- expect_cs_smooth):
200
- csi_series = pd.Series(clear_sky_index, index=time)
201
- cs_sm, _, _ = scaling.wvm(csi_series, positions.tolist(), cloud_speed)
202
- assert_almost_equal(cs_sm[5000:5005], expect_cs_smooth, decimal=4)
203
-
204
-
205
- def test_wvm_invalid(clear_sky_index, positions):
206
- with pytest.raises(ValueError):
207
- scaling.wvm(clear_sky_index, positions, cloud_speed)
@@ -1,391 +0,0 @@
1
- import numpy as np
2
- import pandas as pd
3
-
4
- from pandas.testing import assert_series_equal
5
- from numpy.testing import assert_allclose, assert_approx_equal
6
- import pytest
7
- from datetime import timezone, timedelta
8
-
9
- from pvlib import shading
10
- from pvlib.tools import atand
11
-
12
-
13
- @pytest.fixture
14
- def test_system():
15
- syst = {
16
- "height": 1.0,
17
- "pitch": 2.0,
18
- "surface_tilt": 30.0,
19
- "surface_azimuth": 180.0,
20
- "rotation": -30.0,
21
- } # rotation of right edge relative to horizontal
22
- syst["gcr"] = 1.0 / syst["pitch"]
23
- return syst
24
-
25
-
26
- def test__ground_angle(test_system):
27
- ts = test_system
28
- x = np.array([0.0, 0.5, 1.0])
29
- angles = shading.ground_angle(ts["surface_tilt"], ts["gcr"], x)
30
- expected_angles = np.array([0.0, 5.866738789543952, 9.896090638982903])
31
- assert np.allclose(angles, expected_angles)
32
-
33
-
34
- def test__ground_angle_zero_gcr():
35
- surface_tilt = 30.0
36
- x = np.array([0.0, 0.5, 1.0])
37
- angles = shading.ground_angle(surface_tilt, 0, x)
38
- expected_angles = np.array([0, 0, 0])
39
- assert np.allclose(angles, expected_angles)
40
-
41
-
42
- @pytest.fixture
43
- def surface_tilt():
44
- idx = pd.date_range("2019-01-01", freq="h", periods=3)
45
- return pd.Series([0, 20, 90], index=idx)
46
-
47
-
48
- @pytest.fixture
49
- def masking_angle(surface_tilt):
50
- # masking angles for the surface_tilt fixture,
51
- # assuming GCR=0.5 and height=0.25
52
- return pd.Series([0.0, 11.20223712, 20.55604522], index=surface_tilt.index)
53
-
54
-
55
- @pytest.fixture
56
- def average_masking_angle(surface_tilt):
57
- # average masking angles for the surface_tilt fixture, assuming GCR=0.5
58
- return pd.Series([0.0, 7.20980655, 13.779867461], index=surface_tilt.index)
59
-
60
-
61
- @pytest.fixture
62
- def shading_loss(surface_tilt):
63
- # diffuse shading loss values for the average_masking_angle fixture
64
- return pd.Series([0, 0.00395338, 0.01439098], index=surface_tilt.index)
65
-
66
-
67
- def test_masking_angle_series(surface_tilt, masking_angle):
68
- # series inputs and outputs
69
- masking_angle_actual = shading.masking_angle(surface_tilt, 0.5, 0.25)
70
- assert_series_equal(masking_angle_actual, masking_angle)
71
-
72
-
73
- def test_masking_angle_scalar(surface_tilt, masking_angle):
74
- # scalar inputs and outputs, including zero
75
- for tilt, angle in zip(surface_tilt, masking_angle):
76
- masking_angle_actual = shading.masking_angle(tilt, 0.5, 0.25)
77
- assert np.isclose(masking_angle_actual, angle)
78
-
79
-
80
- def test_masking_angle_zero_gcr(surface_tilt):
81
- # scalar inputs and outputs, including zero
82
- for tilt in surface_tilt:
83
- masking_angle_actual = shading.masking_angle(tilt, 0, 0.25)
84
- assert np.isclose(masking_angle_actual, 0)
85
-
86
-
87
- def test_masking_angle_passias_series(surface_tilt, average_masking_angle):
88
- # pandas series inputs and outputs
89
- masking_angle_actual = shading.masking_angle_passias(surface_tilt, 0.5)
90
- assert_series_equal(masking_angle_actual, average_masking_angle)
91
-
92
-
93
- def test_masking_angle_passias_scalar(surface_tilt, average_masking_angle):
94
- # scalar inputs and outputs, including zero
95
- for tilt, angle in zip(surface_tilt, average_masking_angle):
96
- masking_angle_actual = shading.masking_angle_passias(tilt, 0.5)
97
- assert np.isclose(masking_angle_actual, angle)
98
-
99
-
100
- def test_sky_diffuse_passias_series(average_masking_angle, shading_loss):
101
- # pandas series inputs and outputs
102
- actual_loss = shading.sky_diffuse_passias(average_masking_angle)
103
- assert_series_equal(shading_loss, actual_loss)
104
-
105
-
106
- def test_sky_diffuse_passias_scalar(average_masking_angle, shading_loss):
107
- # scalar inputs and outputs
108
- for angle, loss in zip(average_masking_angle, shading_loss):
109
- actual_loss = shading.sky_diffuse_passias(angle)
110
- assert np.isclose(loss, actual_loss)
111
-
112
-
113
- @pytest.fixture
114
- def true_tracking_angle_and_inputs_NREL():
115
- # data from NREL 'Slope-Aware Backtracking for Single-Axis Trackers'
116
- # doi.org/10.2172/1660126 ; Accessed on 2023-11-06.
117
- tzinfo = timezone(timedelta(hours=-5))
118
- axis_tilt_angle = 9.666 # deg
119
- axis_azimuth_angle = 195.0 # deg
120
- timedata = pd.DataFrame(
121
- columns=("Apparent Elevation", "Solar Azimuth", "True-Tracking"),
122
- data=(
123
- (2.404287, 122.791770, -84.440),
124
- (11.263058, 133.288729, -72.604),
125
- (18.733558, 145.285552, -59.861),
126
- (24.109076, 158.939435, -45.578),
127
- (26.810735, 173.931802, -28.764),
128
- (26.482495, 189.371536, -8.475),
129
- (23.170447, 204.136810, 15.120),
130
- (17.296785, 217.446538, 39.562),
131
- (9.461862, 229.102218, 61.587),
132
- (0.524817, 239.330401, 79.530),
133
- ),
134
- )
135
- timedata.index = pd.date_range(
136
- "2019-01-01T08", "2019-01-01T17", freq="1h", tz=tzinfo
137
- )
138
- timedata["Apparent Zenith"] = 90.0 - timedata["Apparent Elevation"]
139
- return (axis_tilt_angle, axis_azimuth_angle, timedata)
140
-
141
-
142
- @pytest.fixture
143
- def projected_solar_zenith_angle_edge_cases():
144
- premises_and_result_matrix = pd.DataFrame(
145
- data=[
146
- # s_zen | s_azm | ax_tilt | ax_azm | psza
147
- [ 0, 0, 0, 0, 0],
148
- [ 0, 180, 0, 0, 0],
149
- [ 0, 0, 0, 180, 0],
150
- [ 0, 180, 0, 180, 0],
151
- [ 45, 0, 0, 180, 0],
152
- [ 45, 90, 0, 180, -45],
153
- [ 45, 270, 0, 180, 45],
154
- [ 45, 90, 90, 180, -90],
155
- [ 45, 270, 90, 180, 90],
156
- [ 45, 90, 90, 0, 90],
157
- [ 45, 270, 90, 0, -90],
158
- [ 45, 45, 90, 180, -135],
159
- [ 45, 315, 90, 180, 135],
160
- ],
161
- columns=["solar_zenith", "solar_azimuth", "axis_tilt", "axis_azimuth",
162
- "psza"],
163
- )
164
- return premises_and_result_matrix
165
-
166
-
167
- def test_projected_solar_zenith_angle_numeric(
168
- true_tracking_angle_and_inputs_NREL,
169
- projected_solar_zenith_angle_edge_cases
170
- ):
171
- psza_func = shading.projected_solar_zenith_angle
172
- axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL
173
- # test against data provided by NREL
174
- psz = psza_func(
175
- timedata["Apparent Zenith"],
176
- timedata["Solar Azimuth"],
177
- axis_tilt,
178
- axis_azimuth,
179
- )
180
- assert_allclose(psz, timedata["True-Tracking"], atol=1e-3)
181
- # test by changing axis azimuth and tilt
182
- psza = psza_func(
183
- timedata["Apparent Zenith"],
184
- timedata["Solar Azimuth"],
185
- -axis_tilt,
186
- axis_azimuth - 180,
187
- )
188
- assert_allclose(psza, -timedata["True-Tracking"], atol=1e-3)
189
-
190
- # test edge cases
191
- solar_zenith, solar_azimuth, axis_tilt, axis_azimuth, psza_expected = (
192
- v for _, v in projected_solar_zenith_angle_edge_cases.items()
193
- )
194
- psza = psza_func(
195
- solar_zenith,
196
- solar_azimuth,
197
- axis_tilt,
198
- axis_azimuth,
199
- )
200
- assert_allclose(psza, psza_expected, atol=1e-9)
201
-
202
-
203
- @pytest.mark.parametrize(
204
- "cast_type, cast_func",
205
- [
206
- (float, lambda x: float(x)),
207
- (np.ndarray, lambda x: np.array([x])),
208
- (pd.Series, lambda x: pd.Series(data=[x])),
209
- ],
210
- )
211
- def test_projected_solar_zenith_angle_datatypes(
212
- cast_type, cast_func, true_tracking_angle_and_inputs_NREL
213
- ):
214
- psz_func = shading.projected_solar_zenith_angle
215
- axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL
216
- sun_apparent_zenith = timedata["Apparent Zenith"].iloc[0]
217
- sun_azimuth = timedata["Solar Azimuth"].iloc[0]
218
-
219
- axis_tilt, axis_azimuth, sun_apparent_zenith, sun_azimuth = (
220
- cast_func(sun_apparent_zenith),
221
- cast_func(sun_azimuth),
222
- cast_func(axis_tilt),
223
- cast_func(axis_azimuth),
224
- )
225
- psz = psz_func(sun_apparent_zenith, axis_azimuth, axis_tilt, axis_azimuth)
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)