|
| 1 | +import pandas as pd |
| 2 | +import numpy as np |
| 3 | +import pytest |
| 4 | +from solposx.solarposition import iqbal |
| 5 | +# from solposx.solarposition import michalsky |
| 6 | +# from solposx.solarposition import noaa |
| 7 | +# from solposx.solarposition import psa |
| 8 | +# from solposx.solarposition import usno |
| 9 | +# from solposx.solarposition import sg2 |
| 10 | +# from solposx.solarposition import walraven |
| 11 | + |
| 12 | + |
| 13 | +def expected_iqbal(): |
| 14 | + columns = ['elevation', 'zenith', 'azimuth'] |
| 15 | + values = [ |
| 16 | + [32.3933559, 57.6066441, 205.057264], |
| 17 | + [32.3808669, 57.6191331, 205.103653], |
| 18 | + [35.10239415, 54.89760585, 169.4252099], |
| 19 | + [18.79386827, 71.20613173, 234.3799454], |
| 20 | + [35.58304355, 54.41695645, 197.47158811], |
| 21 | + [-9.32664685, 99.32664685, 201.24829056], |
| 22 | + [66.88194467, 23.11805533, 245.62133739], |
| 23 | + [9.32664685, 80.67335315, 338.75170944], |
| 24 | + [49.89989703, 40.10010297, 326.27538784], |
| 25 | + [35.56795719, 54.43204281, 175.44720266], |
| 26 | + [-53.03010805, 143.03010805, 18.66654047], |
| 27 | + [-53.03010805, 143.03010805, 18.66654047], |
| 28 | + [32.7577876, 57.2422124, 205.13681576], |
| 29 | + [32.7577876, 57.2422124, 205.13681576], |
| 30 | + [-23.30557846, 113.30557846, 79.558556], |
| 31 | + [1.23252489, 88.76747511, 104.52245037], |
| 32 | + [32.3933559, 57.6066441, 205.057264], |
| 33 | + [32.3933559, 57.6066441, 205.057264], |
| 34 | + [32.3933559, 57.6066441, 205.057264], |
| 35 | + ] |
| 36 | + data = pd.DataFrame(data=values, columns=columns) |
| 37 | + return data |
| 38 | + |
| 39 | + |
| 40 | +def expected_michalsky_original_julian(): |
| 41 | + columns = ['elevation', 'apparent_elevation', 'zenith', 'apparent_zenith', |
| 42 | + 'azimuth'] |
| 43 | + values = [ |
| 44 | + [32.21780263, 32.24498279, 57.78219737, 57.75501721, 204.91751387], |
| 45 | + [32.20533693, 32.23252757, 57.79466307, 57.76747243, 204.96379895], |
| 46 | + [34.92279261, 34.9478766, 55.07720739, 55.0521234, 169.37618059], |
| 47 | + [18.63629794, 18.68330498, 71.36370206, 71.31669502, 234.18898285], |
| 48 | + [35.75816111, 35.78266275, 54.24183889, 54.21733725, 197.6747502], |
| 49 | + [-9.52972383, -9.55300057, 99.52972383, 99.55300057, 201.18805829], |
| 50 | + [66.85774447, 66.87103396, 23.14225553, 23.12896604, 245.08622522], |
| 51 | + [9.52972383, 9.62043934, 80.47027617, 80.37956066, 338.81194171], |
| 52 | + [50.10984044, 50.1274089, 39.89015956, 39.8725911, 326.23438218], |
| 53 | + [35.36181097, 35.38658543, 54.63818903, 54.61341457, 175.38864631], |
| 54 | + [-53.24139895, -53.25501504, 143.24139895, 143.25501504, 18.64775639], |
| 55 | + [-53.24139895, -53.25501504, 143.24139895, 143.25501504, 18.64775639], |
| 56 | + [33.19773834, 33.2241191, 56.80226166, 56.7758809, 205.09146059], |
| 57 | + [32.08343676, 32.11073039, 57.91656324, 57.88926961, 204.90619448], |
| 58 | + [-23.40757803, -23.43615939, 113.40757803, 113.43615939, 79.54939414], |
| 59 | + [1.10847034, 1.49128689, 88.89152966, 88.50871311, 104.54053775], |
| 60 | + [32.21780263, 32.24498279, 57.78219737, 57.75501721, 204.91751387], |
| 61 | + [32.21780263, 32.24498279, 57.78219737, 57.75501721, 204.91751387], |
| 62 | + [32.21780263, 32.24498279, 57.78219737, 57.75501721, 204.91751387], |
| 63 | + ] |
| 64 | + data = pd.DataFrame(data=values, columns=columns) |
| 65 | + return data |
| 66 | + |
| 67 | + |
| 68 | +def expected_michalsky_correct_julian(): |
| 69 | + columns = ['elevation', 'apparent_elevation', 'zenith', 'apparent_zenith', |
| 70 | + 'azimuth'] |
| 71 | + values = [ |
| 72 | + [32.21780263, 32.24498279, 57.78219737, 57.75501721, 204.91751387], |
| 73 | + [32.20533693, 32.23252757, 57.79466307, 57.76747243, 204.96379895], |
| 74 | + [34.92279261, 34.9478766, 55.07720739, 55.0521234, 169.37618059], |
| 75 | + [18.63629794, 18.68330498, 71.36370206, 71.31669502, 234.18898285], |
| 76 | + [35.75816111, 35.78266275, 54.24183889, 54.21733725, 197.6747502], |
| 77 | + [-9.52972383, -9.55300057, 99.52972383, 99.55300057, 201.18805829], |
| 78 | + [66.85774447, 66.87103396, 23.14225553, 23.12896604, 294.91377478], |
| 79 | + [9.52972383, 9.62043934, 80.47027617, 80.37956066, 201.18805829], |
| 80 | + [50.10984044, 50.1274089, 39.89015956, 39.8725911, 213.76561781], |
| 81 | + [35.36181097, 35.38658543, 54.63818903, 54.61341457, 175.38864631], |
| 82 | + [-53.24139895, -53.25501504, 143.24139895, 143.25501504, 18.64775638], |
| 83 | + [-53.24139895, -53.25501504, 143.24139895, 143.25501504, 18.64775638], |
| 84 | + [32.46357112, 32.49054619, 57.53642888, 57.50945381, 204.94138737], |
| 85 | + [32.44484719, 32.47183777, 57.55515281, 57.52816223, 204.97918825], |
| 86 | + [-23.40757803, -23.43615939, 113.40757803, 113.43615939, 79.54939414], |
| 87 | + [1.10847034, 1.49128689, 88.89152966, 88.50871311, 104.54053775], |
| 88 | + [32.21780263, 32.24498279, 57.78219737, 57.75501721, 204.91751387], |
| 89 | + [32.21780263, 32.24498279, 57.78219737, 57.75501721, 204.91751387], |
| 90 | + [32.21780263, 32.24498279, 57.78219737, 57.75501721, 204.91751387], |
| 91 | + ] |
| 92 | + data = pd.DataFrame(data=values, columns=columns) |
| 93 | + return data |
| 94 | + |
| 95 | + |
| 96 | +def expected_noaa(): |
| 97 | + columns = ['elevation', 'apparent_elevation', 'zenith', 'apparent_zenith', |
| 98 | + 'azimuth'] |
| 99 | + values = [ |
| 100 | + [32.21715174, 32.24268539, 57.78284826, 57.75731461, 204.92232395], |
| 101 | + [32.20468378, 32.23022967, 57.79531622, 57.76977033, 204.96860803], |
| 102 | + [34.92392895, 34.94698595, 55.07607105, 55.05301405, 169.38094302], |
| 103 | + [18.63438988, 18.68174893, 71.36561012, 71.31825107, 234.19290241], |
| 104 | + [35.75618186, 35.77854313, 54.24381814, 54.22145687, 197.67357003], |
| 105 | + [-9.52911488, -9.49473872, 99.52911488, 99.49473872, 336.8166928], |
| 106 | + [66.85423515, 66.8611327, 23.14576485, 23.1388673, 245.09172279], |
| 107 | + [9.52911488, 9.62132546, 80.47088512, 80.37867454, np.nan], |
| 108 | + [50.10765752, 50.12113671, 39.89234248, 39.87886329, 326.22893168], |
| 109 | + [35.36265374, 35.38534047, 54.63734626, 54.61465953, 175.39359304], |
| 110 | + [-53.23987161, -53.23556094, 143.23987161, 143.23556094, 18.65415239], |
| 111 | + [-53.23987161, -53.23556094, 143.23987161, 143.23556094, 18.65415239], |
| 112 | + [32.46248831, 32.48778263, 57.53751169, 57.51221737, 204.95376627], |
| 113 | + [32.44117331, 32.4664883, 57.55882669, 57.5335117, 204.97518601], |
| 114 | + [-23.40444445, -23.39111232, 113.40444445, 113.39111232, 79.55187937], |
| 115 | + [1.11161226, 1.46445929, 88.88838774, 88.53554071, 104.54291476], |
| 116 | + [32.21715174, 32.24268539, 57.78284826, 57.75731461, 204.92232395], |
| 117 | + [32.21715174, 32.24268539, 57.78284826, 57.75731461, 204.92232395], |
| 118 | + [32.21715174, 32.24268539, 57.78284826, 57.75731461, 204.92232395], |
| 119 | + ] |
| 120 | + data = pd.DataFrame(data=values, columns=columns) |
| 121 | + return data |
| 122 | + |
| 123 | + |
| 124 | +def expected_psa_2001(): |
| 125 | + columns = ['elevation', 'zenith', 'azimuth'] |
| 126 | + values = [ |
| 127 | + [32.21434385, 57.78565615, 204.91957868], |
| 128 | + [32.20187689, 57.79812311, 204.96586258], |
| 129 | + [34.92027644, 55.07972356, 169.3788305], |
| 130 | + [18.63211707, 71.36788293, 234.19028111], |
| 131 | + [35.75446859, 54.24553141, 197.67713395], |
| 132 | + [-9.53293173, 99.53293173, 201.19017401], |
| 133 | + [66.85455175, 23.14544825, 245.08643499], |
| 134 | + [9.52811892, 80.47188108, 338.80982599], |
| 135 | + [50.10817913, 39.89182087, 326.23090017], |
| 136 | + [35.35914111, 54.64085889, 175.39125721], |
| 137 | + [-53.24316093, 143.24316093, 18.65145716], |
| 138 | + [-53.24316093, 143.24316093, 18.65145716], |
| 139 | + [32.46428802, 57.53571198, 204.93415982], |
| 140 | + [32.43981197, 57.56018803, 204.98955089], |
| 141 | + [-23.40890746, 113.40890746, 79.55160745], |
| 142 | + [1.10690714, 88.89309286, 104.54258663], |
| 143 | + [32.21434385, 57.78565615, 204.91957868], |
| 144 | + [32.21434385, 57.78565615, 204.91957868], |
| 145 | + [32.21434385, 57.78565615, 204.91957868], |
| 146 | + ] |
| 147 | + data = pd.DataFrame(data=values, columns=columns) |
| 148 | + return data |
| 149 | + |
| 150 | + |
| 151 | +def expected_psa_2020(): |
| 152 | + columns = ['elevation', 'zenith', 'azimuth'] |
| 153 | + values = [ |
| 154 | + [32.21595946, 57.78404054, 204.9167547], |
| 155 | + [32.20349382, 57.79650618, 204.96304003], |
| 156 | + [34.92071906, 55.07928094, 169.37535422], |
| 157 | + [18.63439275, 71.36560725, 234.1884117], |
| 158 | + [35.7535716, 54.2464284, 197.67796274], |
| 159 | + [-9.5321134, 99.5321134, 201.18736969], |
| 160 | + [66.85741664, 23.14258336, 245.08558596], |
| 161 | + [9.52730057, 80.47269943, 338.81263031], |
| 162 | + [50.10853074, 39.89146926, 326.23536385], |
| 163 | + [35.35979872, 54.64020128, 175.38781378], |
| 164 | + [-53.24299848, 143.24299848, 18.64664538], |
| 165 | + [-53.24299848, 143.24299848, 18.64664538], |
| 166 | + [32.47215358, 57.52784642, 204.9305477], |
| 167 | + [32.43510874, 57.56489126, 204.98638231], |
| 168 | + [-23.41028925, 113.41028925, 79.54884709], |
| 169 | + [1.10556787, 88.89443213, 104.54003062], |
| 170 | + [32.21595946, 57.78404054, 204.9167547], |
| 171 | + [32.21595946, 57.78404054, 204.9167547], |
| 172 | + [32.21595946, 57.78404054, 204.9167547], |
| 173 | + ] |
| 174 | + data = pd.DataFrame(data=values, columns=columns) |
| 175 | + return data |
| 176 | + |
| 177 | + |
| 178 | +def expected_usno(): |
| 179 | + columns = ['elevation', 'zenith', 'azimuth'] |
| 180 | + values = [ |
| 181 | + [32.21913058, 57.78086942, 204.91396212], |
| 182 | + [32.20666654, 57.79333346, 204.9602485], |
| 183 | + [34.92271632, 55.07728368, 169.37217155], |
| 184 | + [18.63848631, 71.36151369, 234.18640074], |
| 185 | + [35.75823472, 54.24176528, 197.67107057], |
| 186 | + [-9.52936557, 99.52936557, 201.18474698], |
| 187 | + [66.86088833, 23.13911167, 245.08379957], |
| 188 | + [9.52936557, 80.47063443, 338.81525302], |
| 189 | + [50.11081313, 39.88918687, 326.23927513], |
| 190 | + [35.36198031, 54.63801969, 175.38462327], |
| 191 | + [-53.24179879, 143.24179879, 18.6423076], |
| 192 | + [-53.24179879, 143.24179879, 18.6423076], |
| 193 | + [32.46463884, 57.53536116, 204.9389006], |
| 194 | + [32.44304455, 57.55695545, 204.98724112], |
| 195 | + [-23.40963192, 113.40963192, 79.54658304], |
| 196 | + [1.10645791, 88.89354209, 104.53792899], |
| 197 | + [32.21913058, 57.78086942, 204.91396212], |
| 198 | + [32.21913058, 57.78086942, 204.91396212], |
| 199 | + [32.21913058, 57.78086942, 204.91396212], |
| 200 | + ] |
| 201 | + data = pd.DataFrame(data=values, columns=columns) |
| 202 | + return data |
| 203 | + |
| 204 | + |
| 205 | +def expected_sg2(): |
| 206 | + # The expected values were generated using the official SG2 Python wrapper |
| 207 | + # and the SG2 SAE refraction correction |
| 208 | + columns = ['elevation', 'apparent_elevation', 'zenith', 'apparent_zenith', |
| 209 | + 'azimuth'] |
| 210 | + values = [ |
| 211 | + [32.21696737, 32.24355729, 57.78303263, 57.75644271, 204.91857639], |
| 212 | + [32.2045009, 32.2311035, 57.7954991, 57.7688965, 204.96486201], |
| 213 | + [34.92230526, 34.94633034, 55.07769474, 55.05366966, 169.37656689], |
| 214 | + [18.63488143, 18.6838735, 71.36511857, 71.3161265, 234.19025564], |
| 215 | + [35.75666309, 35.77996471, 54.24333691, 54.22003529, 197.67009822], |
| 216 | + [-9.53068757, -9.49650419, 99.53068757, 99.49650419, 201.18854894], |
| 217 | + [66.85690391, 66.86409239, 23.14309609, 23.13590761, 245.09007204], |
| 218 | + [9.52588549, 9.61972777, 80.47411451, 80.38027223, 338.81145106], |
| 219 | + [50.10677054, 50.1208336, 39.89322946, 39.8791664, 326.23457619], |
| 220 | + [35.36128972, 35.38493054, 54.63871028, 54.61506946, 175.38913627], |
| 221 | + [-53.24134227, -53.23705528, 143.24134227, 143.23705528, 18.64798841], |
| 222 | + [-53.24134227, -53.23705528, 143.24134227, 143.23705528, 18.64798841], |
| 223 | + [np.nan, np.nan, np.nan, np.nan, np.nan], |
| 224 | + [np.nan, np.nan, np.nan, np.nan, np.nan], |
| 225 | + [-23.40828474, -23.39502759, 113.40828474, 113.39502759, 79.54872263], |
| 226 | + [1.10752265, 1.45828141, 88.89247735, 88.54171859, 104.53992596], |
| 227 | + [32.21696737, 32.24355729, 57.78303263, 57.75644271, 204.91857639], |
| 228 | + [32.21696737, 32.24355729, 57.78303263, 57.75644271, 204.91857639], |
| 229 | + [32.21696737, 32.24355729, 57.78303263, 57.75644271, 204.91857639], |
| 230 | + ] |
| 231 | + data = pd.DataFrame(data=values, columns=columns) |
| 232 | + return data |
| 233 | + |
| 234 | + |
| 235 | +def expected_walraven(): |
| 236 | + columns = ['elevation', 'zenith', 'azimuth'] |
| 237 | + values = [ |
| 238 | + [32.21577184, 57.78422816, 204.9145408], |
| 239 | + [32.20330752, 57.79669248, 204.96082497], |
| 240 | + [34.91988403, 55.08011597, 169.37445826], |
| 241 | + [18.63514306, 71.36485694, 234.18579686], |
| 242 | + [35.75942569, 54.24057431, 197.67543014], |
| 243 | + [-9.53241956, 99.53241956, 201.18624892], |
| 244 | + [66.85832643, 23.14167357, 245.07813388], |
| 245 | + [9.53241956, 80.46758044, 338.81375108], |
| 246 | + [50.11302395, 39.88697605, 326.23525905], |
| 247 | + [35.35901685, 54.64098315, 175.38665251], |
| 248 | + [-53.24443195, 143.24443195, 18.64588668], |
| 249 | + [-53.24443195, 143.24443195, 18.64588668], |
| 250 | + [33.19945919, 56.80054081, 205.08690939], |
| 251 | + [32.078221, 57.921779, 204.90430273], |
| 252 | + [-23.41074972, 113.41074972, 79.55008786], |
| 253 | + [1.10528968, 88.89471032, 104.541125], |
| 254 | + [32.21577184, 57.78422816, 204.9145408], |
| 255 | + [32.21577184, 57.78422816, 204.9145408], |
| 256 | + [32.21577184, 57.78422816, 204.9145408], |
| 257 | + ] |
| 258 | + data = pd.DataFrame(data=values, columns=columns) |
| 259 | + return data |
| 260 | + |
| 261 | + |
| 262 | +@pytest.fixture() |
| 263 | +def test_conditions(): |
| 264 | + inputs = pd.DataFrame( |
| 265 | + columns=['time', 'latitude', 'longitude', 'altitude'], |
| 266 | + data=( |
| 267 | + # Units: time, degrees, degrees, m |
| 268 | + ['2020-10-17T12:30+00:00', 45, 10, None], # reference |
| 269 | + ['2020-10-17T12:30:10+00:00', 45, 10, None], # non-zero seconds |
| 270 | + ['2020-10-17T12:30+02:00', 45, 10, None], # positive timezone |
| 271 | + ['2020-10-17T12:30-02:00', 45, 10, None], # negative timezone |
| 272 | + ['2020-02-29T12:30+00:00', 45, 10, None], # leap day |
| 273 | + ['2020-10-17T12:30+00:00', 90, 10, None], # 90 degree latitude |
| 274 | + ['2020-10-17T12:30+00:00', 0, 10, None], # zero latitude |
| 275 | + ['2020-10-17T12:30+00:00', -90, 10, None], # -90 degree latitude |
| 276 | + ['2020-10-17T12:30+00:00', -45, 10, None], # negative mid-latitude |
| 277 | + ['2020-10-17T12:30+00:00', 45, -15, None], # negative longitude |
| 278 | + ['2020-10-17T12:30+00:00', 45, -180, None], # 180-degree longitude |
| 279 | + ['2020-10-17T12:30+00:00', 45, 180, None], # 180-degree longitude |
| 280 | + ['1800-10-17T12:30+00:00', 45, 10, None], # 200 years in the past |
| 281 | + ['2200-10-17T12:30+00:00', 45, 10, None], # 200 years ahead |
| 282 | + ['2020-10-17T03:30+00:00', 45, 10, None], # negative sun elevation |
| 283 | + ['2020-10-17T05:50+00:00', 45, 10, None], # ~1-deg sun elevation |
| 284 | + ['2020-10-17T12:30+00:00', 45, 10, 0], # zero altitude |
| 285 | + ['2020-10-17T12:30+00:00', 45, 10, -100], # negative altitude |
| 286 | + ['2020-10-17T12:30+00:00', 45, 10, 4000], # positive altitude |
| 287 | + ), |
| 288 | + ) |
| 289 | + inputs = inputs.set_index('time') |
| 290 | + inputs.index = [pd.Timestamp(ii) for ii in inputs.index] |
| 291 | + return inputs |
| 292 | + |
| 293 | + |
| 294 | +def _generate_solarposition_dataframe(inputs, algorithm, **kwargs): |
| 295 | + """ |
| 296 | + Generate DataFrame with solar positions. |
| 297 | +
|
| 298 | + Each row is calculated separately due pd.DatetimeIndex being unable to |
| 299 | + handle different timezones. |
| 300 | + """ |
| 301 | + dfs = [] |
| 302 | + for index, row in inputs.iterrows(): |
| 303 | + try: |
| 304 | + dfi = algorithm( |
| 305 | + pd.DatetimeIndex([index]), |
| 306 | + row['latitude'], |
| 307 | + row['longitude'], |
| 308 | + **kwargs, |
| 309 | + ) |
| 310 | + except ValueError: |
| 311 | + # Add empty row (nans) |
| 312 | + dfi = pd.DataFrame(index=[index]) |
| 313 | + dfs.append(dfi) |
| 314 | + return pd.concat(dfs, axis='rows') |
| 315 | + |
| 316 | + |
| 317 | +@pytest.mark.parametrize('algorithm,expected,kwargs', [ |
| 318 | + (iqbal, expected_iqbal, {}), |
| 319 | + # (michalsky, expected_michalsky_original_julian, |
| 320 | + # {'spencer_correction': True, 'julian_date': 'original'}), |
| 321 | + # (michalsky, expected_michalsky_correct_julian, |
| 322 | + # {'spencer_correction': False, 'julian_date': 'pandas'}), |
| 323 | + # (noaa, expected_noaa, {}), |
| 324 | + # (psa, expected_psa_2001, {'coefficients': 2001}), |
| 325 | + # (psa, expected_psa_2020, {'coefficients': 2020}), |
| 326 | + # (usno, expected_usno, {}), |
| 327 | + # (sg2, expected_sg2, {}), |
| 328 | + # (sg2_c.sg2, expected_sg2, {}), |
| 329 | + # (walraven, expected_walraven, {}), |
| 330 | +]) |
| 331 | +def test_algorithm(algorithm, expected, kwargs, test_conditions): |
| 332 | + expected = expected().set_index(test_conditions.index) |
| 333 | + result = _generate_solarposition_dataframe( |
| 334 | + test_conditions, algorithm, **kwargs) |
| 335 | + result.name = algorithm.__module__ |
| 336 | + |
| 337 | + pd.testing.assert_index_equal(expected.index, result.index) |
| 338 | + rtol = 1e-3 if algorithm.__name__ == 'sg2' else 1e-6 |
| 339 | + print(algorithm.__name__) |
| 340 | + pd.testing.assert_frame_equal( |
| 341 | + expected, result, check_like=False, rtol=rtol) |
| 342 | + for c in expected.columns: |
| 343 | + pd.testing.assert_series_equal(expected[c], result[c], rtol=rtol) |
0 commit comments