pvlib 0.11.2__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 (139) hide show
  1. pvlib/__init__.py +1 -0
  2. pvlib/atmosphere.py +0 -9
  3. pvlib/bifacial/infinite_sheds.py +4 -3
  4. pvlib/bifacial/utils.py +2 -1
  5. pvlib/iotools/psm3.py +1 -1
  6. pvlib/iotools/pvgis.py +10 -2
  7. pvlib/iotools/tmy.py +3 -69
  8. pvlib/irradiance.py +14 -0
  9. pvlib/location.py +73 -33
  10. pvlib/modelchain.py +18 -35
  11. pvlib/pvsystem.py +7 -10
  12. pvlib/snow.py +64 -28
  13. pvlib/spectrum/__init__.py +0 -1
  14. pvlib/spectrum/irradiance.py +0 -63
  15. pvlib/spectrum/mismatch.py +3 -3
  16. pvlib/tools.py +6 -5
  17. {pvlib-0.11.2.dist-info → pvlib-0.12.0.dist-info}/METADATA +5 -3
  18. pvlib-0.12.0.dist-info/RECORD +75 -0
  19. {pvlib-0.11.2.dist-info → pvlib-0.12.0.dist-info}/WHEEL +1 -1
  20. pvlib/data/BIRD_08_16_2012.csv +0 -8761
  21. pvlib/data/BIRD_08_16_2012_patm.csv +0 -8761
  22. pvlib/data/Burlington, United States SolarAnywhere Time Series 2021 Lat_44_465 Lon_-73_205 TMY3 format.csv +0 -8762
  23. pvlib/data/Burlington, United States SolarAnywhere Time Series 20210101 to 20210103 Lat_44_4675 Lon_-73_2075 SA format.csv +0 -578
  24. pvlib/data/Burlington, United States SolarAnywhere Typical GHI Year Lat_44_465 Lon_-73_205 SA format.csv +0 -74
  25. pvlib/data/CPS SCH275KTL-DO-US-800-250kW_275kVA_1.OND +0 -146
  26. pvlib/data/CRNS0101-05-2019-AZ_Tucson_11_W.txt +0 -4
  27. pvlib/data/CRN_with_problems.txt +0 -3
  28. pvlib/data/ET-M772BH550GL.PAN +0 -75
  29. pvlib/data/NLD_Amsterdam062400_IWEC.epw +0 -8768
  30. pvlib/data/PVsyst_demo.csv +0 -10757
  31. pvlib/data/PVsyst_demo_model.csv +0 -3588
  32. pvlib/data/SRML-day-EUPO1801.txt +0 -1441
  33. pvlib/data/abq19056.dat +0 -6
  34. pvlib/data/bishop88_numerical_precision.csv +0 -101
  35. pvlib/data/bsrn-lr0100-pay0616.dat +0 -86901
  36. pvlib/data/bsrn-pay0616.dat.gz +0 -0
  37. pvlib/data/cams_mcclear_1min_verbose.csv +0 -60
  38. pvlib/data/cams_mcclear_monthly.csv +0 -42
  39. pvlib/data/cams_radiation_1min_verbose.csv +0 -72
  40. pvlib/data/cams_radiation_monthly.csv +0 -47
  41. pvlib/data/detect_clearsky_data.csv +0 -35
  42. pvlib/data/detect_clearsky_threshold_data.csv +0 -126
  43. pvlib/data/greensboro_kimber_soil_manwash.dat +0 -8761
  44. pvlib/data/greensboro_kimber_soil_nowash.dat +0 -8761
  45. pvlib/data/inverter_fit_snl_meas.csv +0 -127
  46. pvlib/data/inverter_fit_snl_sim.csv +0 -19
  47. pvlib/data/ivtools_numdiff.csv +0 -52
  48. pvlib/data/midc_20181014.txt +0 -1441
  49. pvlib/data/midc_raw_20181018.txt +0 -1441
  50. pvlib/data/midc_raw_short_header_20191115.txt +0 -1441
  51. pvlib/data/msn19056.dat +0 -6
  52. pvlib/data/precise_iv_curves1.json +0 -10251
  53. pvlib/data/precise_iv_curves2.json +0 -10251
  54. pvlib/data/precise_iv_curves_parameter_sets1.csv +0 -33
  55. pvlib/data/precise_iv_curves_parameter_sets2.csv +0 -33
  56. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA2_10kWp_CIS_5_2a_2013_2014.json +0 -1
  57. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA_30deg_0deg_2016_2016.csv +0 -35
  58. pvlib/data/pvgis_tmy_meta.json +0 -32
  59. pvlib/data/pvgis_tmy_test.csv +0 -8761
  60. pvlib/data/pvwatts_8760_rackmount.csv +0 -8779
  61. pvlib/data/pvwatts_8760_roofmount.csv +0 -8779
  62. pvlib/data/singleaxis_tracker_wslope.csv +0 -8761
  63. pvlib/data/spectrl2_example_spectra.csv +0 -123
  64. pvlib/data/surfrad-slv16001.dat +0 -1442
  65. pvlib/data/test_psm3_2017.csv +0 -17521
  66. pvlib/data/test_psm3_2019_5min.csv +0 -289
  67. pvlib/data/test_psm3_tmy-2017.csv +0 -8761
  68. pvlib/data/test_read_psm3.csv +0 -17523
  69. pvlib/data/test_read_pvgis_horizon.csv +0 -49
  70. pvlib/data/tmy_45.000_8.000_2005_2023.csv +0 -8789
  71. pvlib/data/tmy_45.000_8.000_2005_2023.epw +0 -8768
  72. pvlib/data/tmy_45.000_8.000_2005_2023.json +0 -1
  73. pvlib/data/tmy_45.000_8.000_2005_2023.txt +0 -8761
  74. pvlib/data/tmy_45.000_8.000_userhorizon.json +0 -1
  75. pvlib/spa_c_files/README.md +0 -81
  76. pvlib/spa_c_files/cspa_py.pxd +0 -43
  77. pvlib/spa_c_files/spa_py.pyx +0 -30
  78. pvlib/tests/__init__.py +0 -0
  79. pvlib/tests/bifacial/__init__.py +0 -0
  80. pvlib/tests/bifacial/test_infinite_sheds.py +0 -317
  81. pvlib/tests/bifacial/test_losses_models.py +0 -54
  82. pvlib/tests/bifacial/test_pvfactors.py +0 -82
  83. pvlib/tests/bifacial/test_utils.py +0 -192
  84. pvlib/tests/conftest.py +0 -476
  85. pvlib/tests/iotools/__init__.py +0 -0
  86. pvlib/tests/iotools/test_acis.py +0 -213
  87. pvlib/tests/iotools/test_bsrn.py +0 -131
  88. pvlib/tests/iotools/test_crn.py +0 -95
  89. pvlib/tests/iotools/test_epw.py +0 -23
  90. pvlib/tests/iotools/test_midc.py +0 -89
  91. pvlib/tests/iotools/test_panond.py +0 -32
  92. pvlib/tests/iotools/test_psm3.py +0 -198
  93. pvlib/tests/iotools/test_pvgis.py +0 -644
  94. pvlib/tests/iotools/test_sodapro.py +0 -298
  95. pvlib/tests/iotools/test_solaranywhere.py +0 -287
  96. pvlib/tests/iotools/test_solargis.py +0 -68
  97. pvlib/tests/iotools/test_solcast.py +0 -324
  98. pvlib/tests/iotools/test_solrad.py +0 -152
  99. pvlib/tests/iotools/test_srml.py +0 -124
  100. pvlib/tests/iotools/test_surfrad.py +0 -75
  101. pvlib/tests/iotools/test_tmy.py +0 -133
  102. pvlib/tests/ivtools/__init__.py +0 -0
  103. pvlib/tests/ivtools/test_sde.py +0 -230
  104. pvlib/tests/ivtools/test_sdm.py +0 -429
  105. pvlib/tests/ivtools/test_utils.py +0 -173
  106. pvlib/tests/spectrum/__init__.py +0 -0
  107. pvlib/tests/spectrum/conftest.py +0 -40
  108. pvlib/tests/spectrum/test_irradiance.py +0 -138
  109. pvlib/tests/spectrum/test_mismatch.py +0 -304
  110. pvlib/tests/spectrum/test_response.py +0 -124
  111. pvlib/tests/spectrum/test_spectrl2.py +0 -72
  112. pvlib/tests/test__deprecation.py +0 -97
  113. pvlib/tests/test_albedo.py +0 -84
  114. pvlib/tests/test_atmosphere.py +0 -351
  115. pvlib/tests/test_clearsky.py +0 -884
  116. pvlib/tests/test_conftest.py +0 -37
  117. pvlib/tests/test_iam.py +0 -555
  118. pvlib/tests/test_inverter.py +0 -213
  119. pvlib/tests/test_irradiance.py +0 -1487
  120. pvlib/tests/test_location.py +0 -356
  121. pvlib/tests/test_modelchain.py +0 -2020
  122. pvlib/tests/test_numerical_precision.py +0 -124
  123. pvlib/tests/test_pvarray.py +0 -71
  124. pvlib/tests/test_pvsystem.py +0 -2511
  125. pvlib/tests/test_scaling.py +0 -207
  126. pvlib/tests/test_shading.py +0 -391
  127. pvlib/tests/test_singlediode.py +0 -608
  128. pvlib/tests/test_snow.py +0 -212
  129. pvlib/tests/test_soiling.py +0 -230
  130. pvlib/tests/test_solarposition.py +0 -966
  131. pvlib/tests/test_spa.py +0 -454
  132. pvlib/tests/test_temperature.py +0 -470
  133. pvlib/tests/test_tools.py +0 -146
  134. pvlib/tests/test_tracking.py +0 -474
  135. pvlib/tests/test_transformer.py +0 -60
  136. pvlib-0.11.2.dist-info/RECORD +0 -191
  137. {pvlib-0.11.2.dist-info → pvlib-0.12.0.dist-info/licenses}/AUTHORS.md +0 -0
  138. {pvlib-0.11.2.dist-info → pvlib-0.12.0.dist-info/licenses}/LICENSE +0 -0
  139. {pvlib-0.11.2.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)