pvlib 0.11.2__py3-none-any.whl → 0.12.1a1__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/atmosphere.py +40 -40
- pvlib/bifacial/infinite_sheds.py +4 -3
- pvlib/bifacial/utils.py +2 -1
- pvlib/iotools/__init__.py +6 -0
- pvlib/iotools/psm3.py +1 -1
- pvlib/iotools/psm4.py +819 -0
- pvlib/iotools/pvgis.py +10 -2
- pvlib/iotools/tmy.py +3 -69
- pvlib/irradiance.py +38 -15
- pvlib/ivtools/sdm/__init__.py +20 -0
- pvlib/ivtools/sdm/_fit_desoto_pvsyst_sandia.py +585 -0
- pvlib/ivtools/sdm/cec.py +93 -0
- pvlib/ivtools/sdm/desoto.py +401 -0
- pvlib/ivtools/sdm/pvsyst.py +630 -0
- pvlib/location.py +73 -33
- pvlib/modelchain.py +19 -36
- pvlib/pvsystem.py +114 -65
- pvlib/snow.py +64 -28
- pvlib/spectrum/__init__.py +0 -1
- pvlib/spectrum/irradiance.py +2 -64
- pvlib/spectrum/mismatch.py +3 -3
- pvlib/tools.py +6 -5
- {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info}/METADATA +6 -5
- pvlib-0.12.1a1.dist-info/RECORD +80 -0
- {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info}/WHEEL +1 -1
- pvlib/data/BIRD_08_16_2012.csv +0 -8761
- pvlib/data/BIRD_08_16_2012_patm.csv +0 -8761
- pvlib/data/Burlington, United States SolarAnywhere Time Series 2021 Lat_44_465 Lon_-73_205 TMY3 format.csv +0 -8762
- pvlib/data/Burlington, United States SolarAnywhere Time Series 20210101 to 20210103 Lat_44_4675 Lon_-73_2075 SA format.csv +0 -578
- pvlib/data/Burlington, United States SolarAnywhere Typical GHI Year Lat_44_465 Lon_-73_205 SA format.csv +0 -74
- pvlib/data/CPS SCH275KTL-DO-US-800-250kW_275kVA_1.OND +0 -146
- pvlib/data/CRNS0101-05-2019-AZ_Tucson_11_W.txt +0 -4
- pvlib/data/CRN_with_problems.txt +0 -3
- pvlib/data/ET-M772BH550GL.PAN +0 -75
- pvlib/data/NLD_Amsterdam062400_IWEC.epw +0 -8768
- pvlib/data/PVsyst_demo.csv +0 -10757
- pvlib/data/PVsyst_demo_model.csv +0 -3588
- pvlib/data/SRML-day-EUPO1801.txt +0 -1441
- pvlib/data/abq19056.dat +0 -6
- pvlib/data/bishop88_numerical_precision.csv +0 -101
- pvlib/data/bsrn-lr0100-pay0616.dat +0 -86901
- pvlib/data/bsrn-pay0616.dat.gz +0 -0
- pvlib/data/cams_mcclear_1min_verbose.csv +0 -60
- pvlib/data/cams_mcclear_monthly.csv +0 -42
- pvlib/data/cams_radiation_1min_verbose.csv +0 -72
- pvlib/data/cams_radiation_monthly.csv +0 -47
- pvlib/data/detect_clearsky_data.csv +0 -35
- pvlib/data/detect_clearsky_threshold_data.csv +0 -126
- pvlib/data/greensboro_kimber_soil_manwash.dat +0 -8761
- pvlib/data/greensboro_kimber_soil_nowash.dat +0 -8761
- pvlib/data/inverter_fit_snl_meas.csv +0 -127
- pvlib/data/inverter_fit_snl_sim.csv +0 -19
- pvlib/data/ivtools_numdiff.csv +0 -52
- pvlib/data/midc_20181014.txt +0 -1441
- pvlib/data/midc_raw_20181018.txt +0 -1441
- pvlib/data/midc_raw_short_header_20191115.txt +0 -1441
- pvlib/data/msn19056.dat +0 -6
- pvlib/data/precise_iv_curves1.json +0 -10251
- pvlib/data/precise_iv_curves2.json +0 -10251
- pvlib/data/precise_iv_curves_parameter_sets1.csv +0 -33
- pvlib/data/precise_iv_curves_parameter_sets2.csv +0 -33
- pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA2_10kWp_CIS_5_2a_2013_2014.json +0 -1
- pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA_30deg_0deg_2016_2016.csv +0 -35
- pvlib/data/pvgis_tmy_meta.json +0 -32
- pvlib/data/pvgis_tmy_test.csv +0 -8761
- pvlib/data/pvwatts_8760_rackmount.csv +0 -8779
- pvlib/data/pvwatts_8760_roofmount.csv +0 -8779
- pvlib/data/singleaxis_tracker_wslope.csv +0 -8761
- pvlib/data/spectrl2_example_spectra.csv +0 -123
- pvlib/data/surfrad-slv16001.dat +0 -1442
- pvlib/data/test_psm3_2017.csv +0 -17521
- pvlib/data/test_psm3_2019_5min.csv +0 -289
- pvlib/data/test_psm3_tmy-2017.csv +0 -8761
- pvlib/data/test_read_psm3.csv +0 -17523
- pvlib/data/test_read_pvgis_horizon.csv +0 -49
- pvlib/data/tmy_45.000_8.000_2005_2023.csv +0 -8789
- pvlib/data/tmy_45.000_8.000_2005_2023.epw +0 -8768
- pvlib/data/tmy_45.000_8.000_2005_2023.json +0 -1
- pvlib/data/tmy_45.000_8.000_2005_2023.txt +0 -8761
- pvlib/data/tmy_45.000_8.000_userhorizon.json +0 -1
- pvlib/ivtools/sdm.py +0 -1379
- pvlib/spa_c_files/README.md +0 -81
- pvlib/spa_c_files/cspa_py.pxd +0 -43
- pvlib/spa_c_files/spa_py.pyx +0 -30
- pvlib/tests/__init__.py +0 -0
- pvlib/tests/bifacial/__init__.py +0 -0
- pvlib/tests/bifacial/test_infinite_sheds.py +0 -317
- pvlib/tests/bifacial/test_losses_models.py +0 -54
- pvlib/tests/bifacial/test_pvfactors.py +0 -82
- pvlib/tests/bifacial/test_utils.py +0 -192
- pvlib/tests/conftest.py +0 -476
- pvlib/tests/iotools/__init__.py +0 -0
- pvlib/tests/iotools/test_acis.py +0 -213
- pvlib/tests/iotools/test_bsrn.py +0 -131
- pvlib/tests/iotools/test_crn.py +0 -95
- pvlib/tests/iotools/test_epw.py +0 -23
- pvlib/tests/iotools/test_midc.py +0 -89
- pvlib/tests/iotools/test_panond.py +0 -32
- pvlib/tests/iotools/test_psm3.py +0 -198
- pvlib/tests/iotools/test_pvgis.py +0 -644
- pvlib/tests/iotools/test_sodapro.py +0 -298
- pvlib/tests/iotools/test_solaranywhere.py +0 -287
- pvlib/tests/iotools/test_solargis.py +0 -68
- pvlib/tests/iotools/test_solcast.py +0 -324
- pvlib/tests/iotools/test_solrad.py +0 -152
- pvlib/tests/iotools/test_srml.py +0 -124
- pvlib/tests/iotools/test_surfrad.py +0 -75
- pvlib/tests/iotools/test_tmy.py +0 -133
- pvlib/tests/ivtools/__init__.py +0 -0
- pvlib/tests/ivtools/test_sde.py +0 -230
- pvlib/tests/ivtools/test_sdm.py +0 -429
- pvlib/tests/ivtools/test_utils.py +0 -173
- pvlib/tests/spectrum/__init__.py +0 -0
- pvlib/tests/spectrum/conftest.py +0 -40
- pvlib/tests/spectrum/test_irradiance.py +0 -138
- pvlib/tests/spectrum/test_mismatch.py +0 -304
- pvlib/tests/spectrum/test_response.py +0 -124
- pvlib/tests/spectrum/test_spectrl2.py +0 -72
- pvlib/tests/test__deprecation.py +0 -97
- pvlib/tests/test_albedo.py +0 -84
- pvlib/tests/test_atmosphere.py +0 -351
- pvlib/tests/test_clearsky.py +0 -884
- pvlib/tests/test_conftest.py +0 -37
- pvlib/tests/test_iam.py +0 -555
- pvlib/tests/test_inverter.py +0 -213
- pvlib/tests/test_irradiance.py +0 -1487
- pvlib/tests/test_location.py +0 -356
- pvlib/tests/test_modelchain.py +0 -2020
- pvlib/tests/test_numerical_precision.py +0 -124
- pvlib/tests/test_pvarray.py +0 -71
- pvlib/tests/test_pvsystem.py +0 -2511
- pvlib/tests/test_scaling.py +0 -207
- pvlib/tests/test_shading.py +0 -391
- pvlib/tests/test_singlediode.py +0 -608
- pvlib/tests/test_snow.py +0 -212
- pvlib/tests/test_soiling.py +0 -230
- pvlib/tests/test_solarposition.py +0 -966
- pvlib/tests/test_spa.py +0 -454
- pvlib/tests/test_temperature.py +0 -470
- pvlib/tests/test_tools.py +0 -146
- pvlib/tests/test_tracking.py +0 -474
- pvlib/tests/test_transformer.py +0 -60
- pvlib-0.11.2.dist-info/RECORD +0 -191
- {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info/licenses}/AUTHORS.md +0 -0
- {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info/licenses}/LICENSE +0 -0
- {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info}/top_level.txt +0 -0
pvlib/tests/test_scaling.py
DELETED
|
@@ -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)
|
pvlib/tests/test_shading.py
DELETED
|
@@ -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)
|