ubc-solar-physics 0.1.0__cp311-cp311-macosx_13_0_universal2.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 (60) hide show
  1. core.cpython-311-darwin.so +0 -0
  2. physics/__init__.py +14 -0
  3. physics/environment/__init__.py +33 -0
  4. physics/environment/base_environment.py +62 -0
  5. physics/environment/environment.rs +3 -0
  6. physics/environment/gis/__init__.py +7 -0
  7. physics/environment/gis/base_gis.py +24 -0
  8. physics/environment/gis/gis.py +374 -0
  9. physics/environment/gis/gis.rs +25 -0
  10. physics/environment/gis.rs +1 -0
  11. physics/environment/openweather_environment.py +18 -0
  12. physics/environment/race.py +89 -0
  13. physics/environment/solar_calculations/OpenweatherSolarCalculations.py +529 -0
  14. physics/environment/solar_calculations/SolcastSolarCalculations.py +41 -0
  15. physics/environment/solar_calculations/__init__.py +9 -0
  16. physics/environment/solar_calculations/base_solar_calculations.py +9 -0
  17. physics/environment/solar_calculations/solar_calculations.rs +24 -0
  18. physics/environment/solar_calculations.rs +1 -0
  19. physics/environment/solcast_environment.py +18 -0
  20. physics/environment/weather_forecasts/OpenWeatherForecast.py +308 -0
  21. physics/environment/weather_forecasts/SolcastForecasts.py +216 -0
  22. physics/environment/weather_forecasts/__init__.py +9 -0
  23. physics/environment/weather_forecasts/base_weather_forecasts.py +57 -0
  24. physics/environment/weather_forecasts/weather_forecasts.rs +116 -0
  25. physics/environment/weather_forecasts.rs +1 -0
  26. physics/environment.rs +3 -0
  27. physics/lib.rs +76 -0
  28. physics/models/__init__.py +13 -0
  29. physics/models/arrays/__init__.py +7 -0
  30. physics/models/arrays/arrays.rs +0 -0
  31. physics/models/arrays/base_array.py +6 -0
  32. physics/models/arrays/basic_array.py +39 -0
  33. physics/models/arrays.rs +1 -0
  34. physics/models/battery/__init__.py +7 -0
  35. physics/models/battery/base_battery.py +29 -0
  36. physics/models/battery/basic_battery.py +141 -0
  37. physics/models/battery/battery.rs +0 -0
  38. physics/models/battery.rs +1 -0
  39. physics/models/constants.py +23 -0
  40. physics/models/lvs/__init__.py +7 -0
  41. physics/models/lvs/base_lvs.py +6 -0
  42. physics/models/lvs/basic_lvs.py +18 -0
  43. physics/models/lvs/lvs.rs +0 -0
  44. physics/models/lvs.rs +1 -0
  45. physics/models/motor/__init__.py +7 -0
  46. physics/models/motor/base_motor.py +6 -0
  47. physics/models/motor/basic_motor.py +179 -0
  48. physics/models/motor/motor.rs +0 -0
  49. physics/models/motor.rs +1 -0
  50. physics/models/regen/__init__.py +7 -0
  51. physics/models/regen/base_regen.py +6 -0
  52. physics/models/regen/basic_regen.py +39 -0
  53. physics/models/regen/regen.rs +0 -0
  54. physics/models/regen.rs +1 -0
  55. physics/models.rs +5 -0
  56. ubc_solar_physics-0.1.0.dist-info/LICENSE +21 -0
  57. ubc_solar_physics-0.1.0.dist-info/METADATA +44 -0
  58. ubc_solar_physics-0.1.0.dist-info/RECORD +60 -0
  59. ubc_solar_physics-0.1.0.dist-info/WHEEL +5 -0
  60. ubc_solar_physics-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,529 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ A class to perform calculation and approximations for obtaining quantities
5
+ such as solar time, solar position, and the various types of solar irradiance.
6
+ """
7
+
8
+ import datetime
9
+ import numpy as np
10
+
11
+ from physics.environment.race import Race
12
+ from physics.environment.solar_calculations.base_solar_calculations import BaseSolarCalculations
13
+ from physics.environment.openweather_environment import OpenweatherEnvironment
14
+ import core
15
+ from numba import jit
16
+
17
+
18
+ class OpenweatherSolarCalculations(BaseSolarCalculations):
19
+
20
+ def __init__(self, race: Race):
21
+ """
22
+
23
+ Initializes the instance of a SolarCalculations class
24
+
25
+ """
26
+ self.S_0 = 1367 # Solar Constant: W/m^2
27
+ self.race = race
28
+
29
+ # ----- Calculation of solar position in the sky -----
30
+
31
+ @staticmethod
32
+ def _calculate_hour_angle(time_zone_utc, day_of_year, local_time, longitude):
33
+ """
34
+
35
+ Calculates and returns the Hour Angle of the Sun in the sky.
36
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/solar-time
37
+ Note: If local time and time_zone_utc are both unadjusted for Daylight Savings, the
38
+ calculation will end up just the same
39
+ :param np.ndarray time_zone_utc: The UTC time zone of your area in hours of UTC offset.
40
+ :param np.ndarray day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
41
+ :param np.ndarray local_time: The local time in hours from midnight. (Adjust for Daylight Savings)
42
+ :param np.ndarray longitude: The longitude of a location on Earth
43
+ :returns: The Hour Angle in degrees.
44
+ :rtype: np.ndarray
45
+
46
+ """
47
+
48
+ lst = local_time_to_apparent_solar_time(time_zone_utc / 3600, day_of_year,
49
+ local_time, longitude)
50
+
51
+ hour_angle = 15 * (lst - 12)
52
+
53
+ return hour_angle
54
+
55
+ def _calculate_elevation_angle(self, latitude, longitude, time_zone_utc, day_of_year,
56
+ local_time):
57
+ """
58
+
59
+ Calculates the Elevation Angle of the Sun relative to a location on the Earth
60
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/elevation-angle
61
+ Note: If local time and time_zone_utc are both unadjusted for Daylight Savings, the
62
+ calculation will end up just the same
63
+
64
+ :param np.ndarray latitude: The latitude of a location on Earth
65
+ :param np.ndarray longitude: The longitude of a location on Earth
66
+ :param np.ndarray time_zone_utc: The UTC time zone of your area in hours of UTC offset. For example, Vancouver has time_zone_utc = -7
67
+ :param np.ndarray day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
68
+ :param np.ndarray local_time: The local time in hours from midnight. (Adjust for Daylight Savings)
69
+ :returns: The elevation angle in degrees
70
+ :rtype: np.ndarray
71
+
72
+ """
73
+
74
+ # Negative declination angles: Northern Hemisphere winter
75
+ # 0 declination angle : Equinoxes (March 22, Sept 22)
76
+ # Positive declination angle: Northern Hemisphere summer
77
+ declination_angle = calculate_declination_angle(day_of_year)
78
+
79
+ # Negative hour angles: Morning
80
+ # 0 hour angle : Solar noon
81
+ # Positive hour angle: Afternoon
82
+ hour_angle = self._calculate_hour_angle(time_zone_utc, day_of_year,
83
+ local_time, longitude)
84
+ # From: https://en.wikipedia.org/wiki/Hour_angle#:~:text=At%20solar%20noon%20the%20hour,times%201.5%20hours%20before%20noon).
85
+ # "For example, at 10:30 AM local apparent time
86
+ # the hour angle is −22.5° (15° per hour times 1.5 hours before noon)."
87
+
88
+ # mathy part is delegated to a helper function to optimize for numba compilation
89
+ return compute_elevation_angle_math(declination_angle, hour_angle, latitude)
90
+
91
+ def _calculate_zenith_angle(self, latitude, longitude, time_zone_utc, day_of_year,
92
+ local_time):
93
+ """
94
+
95
+ Calculates the Zenith Angle of the Sun relative to a location on the Earth
96
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/azimuth-angle
97
+ Note: If local time and time_zone_utc are both unadjusted for Daylight Savings, the
98
+ calculation will end up just the same
99
+
100
+ :param latitude: The latitude of a location on Earth
101
+ :param longitude: The longitude of a location on Earth
102
+ :param time_zone_utc: The UTC time zone of your area in hours of UTC offset.
103
+ :param day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
104
+ :param local_time: The local time in hours from midnight. (Adjust for Daylight Savings)
105
+ :return: The zenith angle in degrees
106
+ :rtype: float
107
+
108
+ """
109
+
110
+ elevation_angle = self._calculate_elevation_angle(latitude, longitude,
111
+ time_zone_utc, day_of_year, local_time)
112
+
113
+ return 90 - elevation_angle
114
+
115
+ def _calculate_azimuth_angle(self, latitude, longitude, time_zone_utc, day_of_year,
116
+ local_time):
117
+ """
118
+
119
+ Calculates the Azimuth Angle of the Sun relative to a location on the Earth.
120
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/azimuth-angle
121
+ Note: If local time and time_zone_utc are both unadjusted for Daylight Savings, the
122
+ calculation will end up just the same
123
+
124
+ :param latitude: The latitude of a location on Earth
125
+ :param longitude: The longitude of a location on Earth
126
+ :param time_zone_utc: The UTC time zone of your area in hours of UTC offset. For example, Vancouver has time_zone_utc = -7
127
+ :param day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
128
+ :param local_time: The local time in hours from midnight. (Adjust for Daylight Savings)
129
+ :returns: The azimuth angle in degrees
130
+ :rtype: np.ndarray
131
+
132
+ """
133
+
134
+ declination_angle = calculate_declination_angle(day_of_year)
135
+ hour_angle = self._calculate_hour_angle(time_zone_utc, day_of_year,
136
+ local_time, longitude)
137
+
138
+ term_1 = np.sin(np.radians(declination_angle)) * \
139
+ np.sin(np.radians(latitude))
140
+
141
+ term_2 = np.cos(np.radians(declination_angle)) * \
142
+ np.sin(np.radians(latitude)) * \
143
+ np.cos(np.radians(hour_angle))
144
+
145
+ elevation_angle = self._calculate_elevation_angle(latitude, longitude,
146
+ time_zone_utc, day_of_year, local_time)
147
+
148
+ term_3 = np.float_(term_1 - term_2) / \
149
+ np.cos(np.radians(elevation_angle))
150
+
151
+ if term_3 < -1:
152
+ term_3 = -1
153
+ elif term_3 > 1:
154
+ term_3 = 1
155
+
156
+ azimuth_angle = np.arcsin(term_3)
157
+
158
+ return np.degrees(azimuth_angle)
159
+
160
+ # ----- Calculation of sunrise and sunset times -----
161
+
162
+ # ----- Calculation of modes of solar irradiance -----
163
+
164
+ def _calculate_DNI(self, latitude, longitude, time_zone_utc, day_of_year,
165
+ local_time, elevation):
166
+ """
167
+
168
+ Calculates the Direct Normal Irradiance from the Sun, relative to a location
169
+ on the Earth (clearsky)
170
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/calculation-of-solar-insolation
171
+ Note: If local time and time_zone_utc are both unadjusted for Daylight Savings, the
172
+ calculation will end up just the same
173
+
174
+ :param np.ndarray latitude: The latitude of a location on Earth
175
+ :param np.ndarray longitude: The longitude of a location on Earth
176
+ :param np.ndarray time_zone_utc: The UTC time zone of your area in hours of UTC offset.
177
+ :param np.ndarray day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
178
+ :param np.ndarray local_time: The local time in hours from midnight. (Adjust for Daylight Savings)
179
+ :param np.ndarray elevation: The local elevation of a location in metres
180
+ :returns: The Direct Normal Irradiance in W/m2
181
+ :rtype: np.ndarray
182
+
183
+ """
184
+
185
+ zenith_angle = self._calculate_zenith_angle(latitude, longitude,
186
+ time_zone_utc, day_of_year, local_time)
187
+ a = 0.14
188
+
189
+ # https://www.pveducation.org/pvcdrom/properties-of-sunlight/air-mass
190
+ # air_mass = 1 / (math.cos(math.radians(zenith_angle)) + \
191
+ # 0.50572*pow((96.07995 - zenith_angle), -1.6364))
192
+
193
+ with np.errstate(invalid="ignore"):
194
+ air_mass = np.float_(1) / (np.float_(np.cos(np.radians(zenith_angle)))
195
+ + 0.50572*np.power((96.07995 - zenith_angle), -1.6364))
196
+
197
+ with np.errstate(over="ignore"):
198
+ DNI = self.S_0 * ((1 - a * elevation * 0.001) * np.power(0.7, np.power(air_mass, 0.678))
199
+ + a * elevation * 0.001)
200
+
201
+ return np.where(zenith_angle > 90, 0, DNI)
202
+
203
+ def _calculate_DHI(self, latitude, longitude, time_zone_utc, day_of_year,
204
+ local_time, elevation):
205
+ """
206
+
207
+ Calculates the Diffuse Horizontal Irradiance from the Sun, relative to a location
208
+ on the Earth (clearsky)
209
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/calculation-of-solar-insolation
210
+ Note: If local time and time_zone_utc are both unadjusted for Daylight Savings, the
211
+ calculation will end up just the same
212
+
213
+ :param np.ndarray latitude: The latitude of a location on Earth
214
+ :param np.ndarray longitude: The longitude of a location on Earth
215
+ :param np.ndarray time_zone_utc: The UTC time zone of your area in hours of UTC offset.
216
+ :param np.ndarray np.ndarray day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
217
+ :param np.ndarray local_time: The local time in hours from midnight
218
+ :param np.ndarray elevation: The local elevation of a location in metres
219
+ :returns: The Diffuse Horizontal Irradiance in W/m2
220
+ :rtype: np.ndarray
221
+
222
+ """
223
+
224
+ DNI = self._calculate_DNI(latitude, longitude, time_zone_utc, day_of_year,
225
+ local_time, elevation)
226
+
227
+ DHI = 0.1 * DNI
228
+
229
+ return DHI
230
+
231
+ def _calculate_GHI(self, latitude, longitude, time_zone_utc, day_of_year,
232
+ local_time, elevation, cloud_cover):
233
+ """
234
+
235
+ Calculates the Global Horizontal Irradiance from the Sun, relative to a location
236
+ on the Earth
237
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/calculation-of-solar-insolation
238
+ Note: If local time and time_zone_utc are both unadjusted for Daylight Savings, the
239
+ calculation will end up just the same
240
+
241
+ :param np.ndarray latitude: The latitude of a location on Earth
242
+ :param np.ndarray longitude: The longitude of a location on Earth
243
+ :param np.ndarray time_zone_utc: The UTC time zone of your area in hours of UTC offset, without including the effects of Daylight Savings Time. For example, Vancouver has time_zone_utc = -8 year-round.
244
+ :param np.ndarray day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
245
+ :param np.ndarray local_time: The local time in hours from midnight.
246
+ :param np.ndarray elevation: The local elevation of a location in metres
247
+ :param np.ndarray cloud_cover: A NumPy array representing cloud cover as a percentage from 0 to 100
248
+ :returns: The Global Horizontal Irradiance in W/m^2
249
+ :rtype: np.ndarray
250
+
251
+ """
252
+
253
+ DHI = self._calculate_DHI(latitude, longitude, time_zone_utc, day_of_year,
254
+ local_time, elevation)
255
+
256
+ DNI = self._calculate_DNI(latitude, longitude, time_zone_utc, day_of_year,
257
+ local_time, elevation)
258
+
259
+ zenith_angle = self._calculate_zenith_angle(latitude, longitude,
260
+ time_zone_utc, day_of_year, local_time)
261
+
262
+ GHI = DNI * np.cos(np.radians(zenith_angle)) + DHI
263
+
264
+ return self._apply_cloud_cover(GHI=GHI, cloud_cover=cloud_cover)
265
+
266
+ @staticmethod
267
+ def _apply_cloud_cover(GHI, cloud_cover):
268
+ """
269
+
270
+ Applies a cloud cover model to the GHI data.
271
+
272
+ Cloud cover adjustment follows the equation laid out here:
273
+ http://www.shodor.org/os411/courses/_master/tools/calculators/solarrad/
274
+
275
+ :param np.ndarray GHI: Global Horizontal Index in W/m^2
276
+ :param np.ndarray cloud_cover: A NumPy array representing cloud cover as a percentage from 0 to 100
277
+
278
+ :returns: GHI after considering cloud cover data
279
+ :rtype: np.ndarray
280
+
281
+ """
282
+
283
+ assert np.logical_and(cloud_cover >= 0, cloud_cover <= 100).all()
284
+
285
+ scaled_cloud_cover = cloud_cover / 100
286
+
287
+ assert np.logical_and(scaled_cloud_cover >= 0,
288
+ scaled_cloud_cover <= 1).all()
289
+
290
+ return GHI * (1 - (0.75 * np.power(scaled_cloud_cover, 3.4)))
291
+
292
+ # ----- Calculation of modes of solar irradiance, but returning numpy arrays -----
293
+ @staticmethod
294
+ def _python_calculate_array_GHI_times(local_times):
295
+ date = list(map(datetime.datetime.utcfromtimestamp, local_times))
296
+ day_of_year = np.array(list(map(get_day_of_year_map, date)), dtype=np.float64)
297
+ local_time = np.array(list(map(OpenweatherSolarCalculations._date_convert, date)))
298
+ return day_of_year, local_time
299
+
300
+ @staticmethod
301
+ def _date_convert(date):
302
+ """
303
+
304
+ Convert a date into local time.
305
+
306
+ :param datetime.datetime date: date to be converted
307
+ :return: a date converted into local time.
308
+ :rtype: int
309
+
310
+ """
311
+
312
+ return date.hour + (float(date.minute * 60 + date.second) / 3600)
313
+
314
+ def calculate_array_GHI(self, coords, time_zones, local_times,
315
+ elevations, environment: OpenweatherEnvironment):
316
+ """
317
+
318
+ Calculates the Global Horizontal Irradiance from the Sun, relative to a location
319
+ on the Earth, for arrays of coordinates, times, elevations and weathers
320
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/calculation-of-solar-insolation
321
+ Note: If local_times and time_zones are both unadjusted for Daylight Savings, the
322
+ calculation will end up just the same
323
+
324
+ :param np.ndarray coords: (float[N][lat, lng]) array of latitudes and longitudes
325
+ :param np.ndarray time_zones: (int[N]) time zones at different locations in seconds relative to UTC
326
+ :param np.ndarray local_times: (int[N]) unix time that the vehicle will be at each location. (Adjusted for Daylight Savings)
327
+ :param np.ndarray elevations: (float[N]) elevation from sea level in m
328
+ :param OpenweatherEnvironment environment: environment data object
329
+ :returns: (float[N]) Global Horizontal Irradiance in W/m2
330
+ :rtype: np.ndarray
331
+
332
+ """
333
+ day_of_year, local_time = core.calculate_array_ghi_times(local_times)
334
+
335
+ ghi = self._calculate_GHI(coords[:, 0], coords[:, 1], time_zones,
336
+ day_of_year, local_time, elevations, environment.cloud_cover)
337
+
338
+ stationary_irradiance = self._calculate_angled_irradiance(coords[:, 0], coords[:, 1], time_zones, day_of_year,
339
+ local_time, elevations, environment.cloud_cover)
340
+
341
+ # Use stationary irradiance when the car is not driving
342
+ effective_irradiance = np.where(
343
+ np.logical_not(self.race.driving_boolean),
344
+ stationary_irradiance,
345
+ ghi)
346
+
347
+ return effective_irradiance
348
+
349
+ def _calculate_angled_irradiance(self, latitude, longitude, time_zone_utc, day_of_year,
350
+ local_time, elevation, cloud_cover, array_angles=np.array([0, 15, 30, 45])):
351
+ """
352
+
353
+ Determine the direct and diffuse irradiance on an array which can be mounted at different angles.
354
+ During stationary charging, the car can mount the array at different angles, resulting in a higher
355
+ component of direct irradiance captured.
356
+
357
+ Uses the GHI formula, GHI = DNI*cos(zenith)+DHI but with an 'effective zenith',
358
+ the angle between the mounted panel's normal and the sun.
359
+
360
+ :param np.ndarray latitude: The latitude of a location on Earth
361
+ :param np.ndarray longitude: The longitude of a location on Earth
362
+ :param np.ndarray time_zone_utc: The UTC time zone of your area in hours of UTC offset, without including the effects of Daylight Savings Time. For example, Vancouver has time_zone_utc = -8 year-round.
363
+ :param np.ndarray day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
364
+ :param np.ndarray local_time: The local time in hours from midnight.
365
+ :param np.ndarray elevation: The local elevation of a location in metres
366
+ :param np.ndarray cloud_cover: A NumPy array representing cloud cover as a percentage from 0 to 100
367
+ :param np.ndarray array_angles: An array containing the discrete angles on which the array can be mounted
368
+ :returns: The "effective Global Horizontal Irradiance" in W/m^2
369
+ :rtype: np.ndarray
370
+
371
+ """
372
+
373
+ DHI = self._calculate_DHI(latitude, longitude, time_zone_utc, day_of_year,
374
+ local_time, elevation)
375
+
376
+ DNI = self._calculate_DNI(latitude, longitude, time_zone_utc, day_of_year,
377
+ local_time, elevation)
378
+
379
+ zenith_angle = self._calculate_zenith_angle(latitude, longitude,
380
+ time_zone_utc, day_of_year, local_time)
381
+
382
+ # Calculate the absolute differences
383
+ differences = np.abs(zenith_angle[:, np.newaxis] - array_angles)
384
+
385
+ # Find the minimum difference for each element in zenith_angle
386
+ effective_zenith = np.min(differences, axis=1)
387
+
388
+ # Now effective_zenith contains the minimum absolute difference for each element in zenith_angle
389
+
390
+ GHI = DNI * np.cos(np.radians(effective_zenith)) + DHI
391
+
392
+ return self._apply_cloud_cover(GHI=GHI, cloud_cover=cloud_cover)
393
+
394
+
395
+ def local_time_to_apparent_solar_time(time_zone_utc, day_of_year, local_time,
396
+ longitude):
397
+ """
398
+
399
+ Converts between the local time to the apparent solar time and returns the apparent
400
+ solar time.
401
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/solar-time
402
+
403
+ Note: If local time and time_zone_utc are both unadjusted for Daylight Savings, the
404
+ calculation will end up just the same
405
+
406
+ :param np.ndarray time_zone_utc: The UTC time zone of your area in hours of UTC offset.
407
+ :param np.ndarray day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
408
+ :param np.ndarray local_time: The local time in hours from midnight (Adjust for Daylight Savings)
409
+ :param np.ndarray longitude: The longitude of a location on Earth
410
+ :returns: The Apparent Solar Time of a location, in hours from midnight
411
+ :rtype: np.ndarray
412
+
413
+ """
414
+
415
+ lstm = calculate_LSTM(time_zone_utc)
416
+ eot = calculate_eot_correction(day_of_year)
417
+
418
+ # local solar time
419
+ lst = local_time + np.float_(longitude - lstm) / 15 + np.float_(eot) / 60
420
+
421
+ return lst
422
+
423
+
424
+ @jit(nopython=True)
425
+ def calculate_LSTM(time_zone_utc):
426
+ """
427
+
428
+ Calculates and returns the LSTM, or Local Solar Time Meridian.
429
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/solar-time
430
+
431
+ :param np.ndarray time_zone_utc: The UTC time zone of your area in hours of UTC offset.
432
+ :returns: The Local Solar Time Meridian in degrees
433
+ :rtype: np.ndarray
434
+
435
+ """
436
+
437
+ return 15 * time_zone_utc
438
+
439
+
440
+ @jit(nopython=True)
441
+ def calculate_eot_correction(day_of_year):
442
+ """
443
+
444
+ Approximates and returns the correction factor between the apparent
445
+ solar time and the mean solar time
446
+
447
+ :param np.ndarray day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
448
+ :returns: The Equation of Time correction EoT in minutes, where apparent Solar Time = Mean Solar Time + EoT
449
+ :rtype: np.ndarray
450
+
451
+ """
452
+
453
+ b = np.radians((np.float_(360) / 364) * (day_of_year - 81))
454
+
455
+ eot = 9.87 * np.sin(2 * b) - 7.83 * np.cos(b) - 1.5 * np.sin(b)
456
+
457
+ return eot
458
+
459
+
460
+ def get_day_of_year_map(date):
461
+ """
462
+
463
+ Extracts day, month, year, from datetime object
464
+
465
+ :param datetime.date date: date to be decomposed
466
+
467
+ """
468
+ return get_day_of_year(date.day, date.month, date.year)
469
+
470
+
471
+ def get_day_of_year(day, month, year):
472
+ """
473
+
474
+ Calculates the day of the year, given the day, month and year.
475
+ Day refers to a number representing the nth day of the year. So, Jan 1st will be the 1st day of the year
476
+
477
+ :param int day: nth day of the year
478
+ :param int month: month
479
+ :param int year: year
480
+ :returns: day of year
481
+ :rtype: int
482
+
483
+ """
484
+
485
+ return (datetime.date(year, month, day) - datetime.date(year, 1, 1)).days + 1
486
+
487
+
488
+ @jit(nopython=True)
489
+ def calculate_declination_angle(day_of_year):
490
+ """
491
+
492
+ Calculates the Declination Angle of the Earth at a given day
493
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/declination-angle
494
+
495
+ :param np.ndarray day_of_year: The number of the day of the current year, with January 1 being the first day of the year.
496
+ :returns: The declination angle of the Earth relative to the Sun, in degrees
497
+ :rtype: np.ndarray
498
+
499
+ """
500
+
501
+ declination_angle = -23.45 * np.cos(np.radians((np.float_(360) / 365) *
502
+ (day_of_year + 10)))
503
+
504
+ return declination_angle
505
+
506
+
507
+ @jit(nopython=True)
508
+ def compute_elevation_angle_math(declination_angle, hour_angle, latitude):
509
+ """
510
+
511
+ Gets the two terms to calculate and return elevation angle, given the
512
+ declination angle, hour angle, and latitude.
513
+
514
+ This method separates the math part of the calculation from its caller
515
+ method to optimize for numba compilation.
516
+
517
+ :param np.ndarray latitude: array of latitudes
518
+ :param np.ndarray declination_angle: The declination angle of the Earth relative to the Sun
519
+ :param np.ndarray hour_angle: The hour angle of the sun in the sky
520
+ :returns: The elevation angle in degrees
521
+ :rtype: np.ndarray
522
+
523
+ """
524
+
525
+ term_1 = np.sin(np.radians(declination_angle)) * np.sin(np.radians(latitude))
526
+ term_2 = np.cos(np.radians(declination_angle)) * np.cos(np.radians(latitude)) * np.cos(np.radians(hour_angle))
527
+ elevation_angle = np.arcsin(term_1 + term_2)
528
+
529
+ return np.degrees(elevation_angle)
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ A class to perform calculation and approximations for obtaining quantities
5
+ such as solar time, solar position, and the various types of solar irradiance.
6
+ """
7
+ from physics.environment.race import Race
8
+ from physics.environment.solar_calculations.base_solar_calculations import BaseSolarCalculations
9
+ from physics.environment import SolcastEnvironment
10
+
11
+
12
+ class SolcastSolarCalculations(BaseSolarCalculations):
13
+
14
+ def __init__(self, race: Race):
15
+ """
16
+
17
+ Initializes the instance of a SolarCalculations class
18
+
19
+ """
20
+ self.race = race
21
+
22
+ def calculate_array_GHI(self, coords, time_zones, local_times,
23
+ elevations, environment: SolcastEnvironment):
24
+ """
25
+
26
+ Calculates the Global Horizontal Irradiance from the Sun, relative to a location
27
+ on the Earth, for arrays of coordinates, times, elevations and weathers
28
+ https://www.pveducation.org/pvcdrom/properties-of-sunlight/calculation-of-solar-insolation
29
+ Note: If local_times and time_zones are both unadjusted for Daylight Savings, the
30
+ calculation will end up just the same
31
+
32
+ :param np.ndarray coords: (float[N][lat, lng]) array of latitudes and longitudes
33
+ :param np.ndarray time_zones: (int[N]) time zones at different locations in seconds relative to UTC
34
+ :param np.ndarray local_times: (int[N]) unix time that the vehicle will be at each location. (Adjusted for Daylight Savings)
35
+ :param np.ndarray elevations: (float[N]) elevation from sea level in m
36
+ :param SolcastEnvironment environment: environment data object
37
+ :returns: (float[N]) Global Horizontal Irradiance in W/m2
38
+ :rtype: np.ndarray
39
+
40
+ """
41
+ return environment.ghi
@@ -0,0 +1,9 @@
1
+ from .base_solar_calculations import BaseSolarCalculations
2
+ from .SolcastSolarCalculations import SolcastSolarCalculations
3
+ from .OpenweatherSolarCalculations import OpenweatherSolarCalculations
4
+
5
+ __all__ = [
6
+ "BaseSolarCalculations",
7
+ "SolcastSolarCalculations",
8
+ "OpenweatherSolarCalculations"
9
+ ]
@@ -0,0 +1,9 @@
1
+ from abc import ABC, abstractmethod
2
+ import numpy as np
3
+
4
+
5
+ class BaseSolarCalculations(ABC):
6
+ @abstractmethod
7
+ def calculate_array_GHI(self, coords, time_zones, local_times,
8
+ elevations, environment) -> np.ndarray:
9
+ raise NotImplementedError
@@ -0,0 +1,24 @@
1
+ use chrono::{Datelike, NaiveDateTime, Timelike};
2
+ use numpy::ndarray::{s, Array, Array2, ArrayViewD, ArrayViewMut2, ArrayViewMut3, Axis};
3
+
4
+ pub fn rust_calculate_array_ghi_times<'a>(
5
+ local_times: ArrayViewD<'_, u64>,
6
+ ) -> (Vec<f64>, Vec<f64>) {
7
+ let mut datetimes: Vec<_> = Vec::with_capacity(local_times.len());
8
+
9
+ for &unix_time_stamp in local_times {
10
+ let datetime = NaiveDateTime::from_timestamp_opt(unix_time_stamp as i64, 0).unwrap();
11
+ datetimes.push(datetime);
12
+ }
13
+
14
+ let day_of_year_out: Vec<f64> = datetimes
15
+ .iter()
16
+ .map(|&date| date.date().ordinal() as f64)
17
+ .collect();
18
+ let local_time_out: Vec<f64> = datetimes
19
+ .iter()
20
+ .map(|&date| date.time().num_seconds_from_midnight() as f64 / 3600.0)
21
+ .collect();
22
+
23
+ (day_of_year_out, local_time_out)
24
+ }
@@ -0,0 +1 @@
1
+ pub mod solar_calculations;
@@ -0,0 +1,18 @@
1
+ from physics.environment.base_environment import BaseEnvironment
2
+
3
+
4
+ class SolcastEnvironment(BaseEnvironment):
5
+ def __init__(self):
6
+ super().__init__()
7
+ self._ghi = None
8
+
9
+ @property
10
+ def ghi(self):
11
+ if (value := self._ghi) is not None:
12
+ return value
13
+ else:
14
+ raise ValueError("ghi is None!")
15
+
16
+ @ghi.setter
17
+ def ghi(self, value):
18
+ self._ghi = value