ubc-solar-physics 0.1.11__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.
- core.cpython-311-darwin.so +0 -0
- physics/__init__.py +14 -0
- physics/environment/__init__.py +33 -0
- physics/environment/base_environment.py +62 -0
- physics/environment/environment.rs +3 -0
- physics/environment/gis/__init__.py +7 -0
- physics/environment/gis/base_gis.py +24 -0
- physics/environment/gis/gis.py +374 -0
- physics/environment/gis/gis.rs +25 -0
- physics/environment/gis.rs +1 -0
- physics/environment/openweather_environment.py +18 -0
- physics/environment/race.py +89 -0
- physics/environment/solar_calculations/OpenweatherSolarCalculations.py +529 -0
- physics/environment/solar_calculations/SolcastSolarCalculations.py +41 -0
- physics/environment/solar_calculations/__init__.py +9 -0
- physics/environment/solar_calculations/base_solar_calculations.py +9 -0
- physics/environment/solar_calculations/solar_calculations.rs +24 -0
- physics/environment/solar_calculations.rs +1 -0
- physics/environment/solcast_environment.py +18 -0
- physics/environment/weather_forecasts/OpenWeatherForecast.py +307 -0
- physics/environment/weather_forecasts/SolcastForecasts.py +235 -0
- physics/environment/weather_forecasts/__init__.py +9 -0
- physics/environment/weather_forecasts/base_weather_forecasts.py +56 -0
- physics/environment/weather_forecasts/weather_forecasts.rs +116 -0
- physics/environment/weather_forecasts.rs +1 -0
- physics/environment.rs +3 -0
- physics/lib.rs +76 -0
- physics/models/__init__.py +13 -0
- physics/models/arrays/__init__.py +7 -0
- physics/models/arrays/arrays.rs +0 -0
- physics/models/arrays/base_array.py +6 -0
- physics/models/arrays/basic_array.py +39 -0
- physics/models/arrays.rs +1 -0
- physics/models/battery/__init__.py +7 -0
- physics/models/battery/base_battery.py +29 -0
- physics/models/battery/basic_battery.py +141 -0
- physics/models/battery/battery.rs +0 -0
- physics/models/battery.rs +1 -0
- physics/models/constants.py +23 -0
- physics/models/lvs/__init__.py +7 -0
- physics/models/lvs/base_lvs.py +6 -0
- physics/models/lvs/basic_lvs.py +18 -0
- physics/models/lvs/lvs.rs +0 -0
- physics/models/lvs.rs +1 -0
- physics/models/motor/__init__.py +7 -0
- physics/models/motor/base_motor.py +6 -0
- physics/models/motor/basic_motor.py +179 -0
- physics/models/motor/motor.rs +0 -0
- physics/models/motor.rs +1 -0
- physics/models/regen/__init__.py +7 -0
- physics/models/regen/base_regen.py +6 -0
- physics/models/regen/basic_regen.py +39 -0
- physics/models/regen/regen.rs +0 -0
- physics/models/regen.rs +1 -0
- physics/models.rs +5 -0
- ubc_solar_physics-0.1.11.dist-info/LICENSE +21 -0
- ubc_solar_physics-0.1.11.dist-info/METADATA +75 -0
- ubc_solar_physics-0.1.11.dist-info/RECORD +60 -0
- ubc_solar_physics-0.1.11.dist-info/WHEEL +5 -0
- ubc_solar_physics-0.1.11.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,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
|