ubc-solar-physics 1.0.3__cp39-cp39-win32.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.cp39-win32.pyd +0 -0
- physics/__init__.py +12 -0
- physics/__version__.py +16 -0
- physics/environment/__init__.py +22 -0
- physics/environment/environment.rs +2 -0
- physics/environment/gis/__init__.py +7 -0
- physics/environment/gis/base_gis.py +24 -0
- physics/environment/gis/gis.py +337 -0
- physics/environment/gis/gis.rs +25 -0
- physics/environment/gis.rs +1 -0
- physics/environment/meteorology/__init__.py +3 -0
- physics/environment/meteorology/base_meteorology.py +69 -0
- physics/environment/meteorology/clouded_meteorology.py +601 -0
- physics/environment/meteorology/irradiant_meteorology.py +106 -0
- physics/environment/meteorology/meteorology.rs +138 -0
- physics/environment/meteorology.rs +1 -0
- physics/environment/race.py +89 -0
- physics/environment.rs +2 -0
- physics/lib.rs +98 -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 +174 -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-1.0.3.dist-info/LICENSE +21 -0
- ubc_solar_physics-1.0.3.dist-info/METADATA +136 -0
- ubc_solar_physics-1.0.3.dist-info/RECORD +52 -0
- ubc_solar_physics-1.0.3.dist-info/WHEEL +5 -0
- ubc_solar_physics-1.0.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from numpy.polynomial import Polynomial
|
3
|
+
|
4
|
+
from physics.models.battery.base_battery import BaseBattery
|
5
|
+
|
6
|
+
|
7
|
+
class BasicBattery(BaseBattery):
|
8
|
+
"""
|
9
|
+
Class representing the DayBreak battery pack.
|
10
|
+
|
11
|
+
Attributes:
|
12
|
+
max_voltage (float): maximum voltage of the DayBreak battery pack (V)
|
13
|
+
min_voltage (float): minimum voltage of the DayBreak battery pack (V)
|
14
|
+
max_current_capacity (float): nominal capacity of the DayBreak battery pack (Ah)
|
15
|
+
max_energy_capacity (float): nominal energy capacity of the DayBreak battery pack (Wh)
|
16
|
+
|
17
|
+
state_of_charge (float): instantaneous battery state-of-charge (0.00 - 1.00)
|
18
|
+
discharge_capacity (float): instantaneous amount of charge extracted from battery (Ah)
|
19
|
+
voltage (float): instantaneous voltage of the battery (V)
|
20
|
+
stored_energy (float): instantaneous energy stored in the battery (Wh)
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, state_of_charge, max_voltage, min_voltage, max_current_capacity, max_energy_capacity):
|
24
|
+
"""
|
25
|
+
|
26
|
+
Constructor for BasicBattery class.
|
27
|
+
|
28
|
+
:param float state_of_charge: initial battery state of charge
|
29
|
+
|
30
|
+
"""
|
31
|
+
|
32
|
+
# ----- DayBreak battery constants -----
|
33
|
+
|
34
|
+
self.max_voltage = max_voltage
|
35
|
+
self.min_voltage = min_voltage
|
36
|
+
self.max_current_capacity = max_current_capacity
|
37
|
+
self.max_energy_capacity = max_energy_capacity
|
38
|
+
|
39
|
+
# ----- DayBreak battery equations -----
|
40
|
+
|
41
|
+
self.calculate_voltage_from_discharge_capacity = calculate_voltage_from_discharge_capacity()
|
42
|
+
|
43
|
+
self.calculate_energy_from_discharge_capacity = calculate_energy_from_discharge_capacity()
|
44
|
+
|
45
|
+
self.calculate_soc_from_discharge_capacity = calculate_soc_from_discharge_capacity(self.max_current_capacity)
|
46
|
+
|
47
|
+
self.calculate_discharge_capacity_from_soc = calculate_discharge_capacity_from_soc(self.max_current_capacity)
|
48
|
+
|
49
|
+
self.calculate_discharge_capacity_from_energy = calculate_discharge_capacity_from_energy()
|
50
|
+
|
51
|
+
# ----- DayBreak battery variables -----
|
52
|
+
|
53
|
+
self.state_of_charge = state_of_charge
|
54
|
+
|
55
|
+
# SOC -> discharge_capacity
|
56
|
+
self.discharge_capacity = self.calculate_discharge_capacity_from_soc(self.state_of_charge)
|
57
|
+
|
58
|
+
# discharge_capacity -> voltage
|
59
|
+
self.voltage = self.calculate_voltage_from_discharge_capacity(self.discharge_capacity)
|
60
|
+
|
61
|
+
# discharge_capacity -> energy
|
62
|
+
self.stored_energy = self.max_energy_capacity - self.calculate_energy_from_discharge_capacity(
|
63
|
+
self.discharge_capacity)
|
64
|
+
|
65
|
+
# ----- DayBreak battery initialisation -----
|
66
|
+
|
67
|
+
super().__init__(self.stored_energy, self.max_current_capacity, self.max_energy_capacity,
|
68
|
+
self.max_voltage, self.min_voltage, self.voltage, self.state_of_charge)
|
69
|
+
|
70
|
+
def update_array(self, cumulative_energy_array):
|
71
|
+
"""
|
72
|
+
Performs energy calculations with NumPy arrays
|
73
|
+
|
74
|
+
:param cumulative_energy_array: a NumPy array containing the cumulative energy changes at each time step
|
75
|
+
experienced by the battery
|
76
|
+
|
77
|
+
:return: soc_array – a NumPy array containing the battery state of charge at each time step
|
78
|
+
|
79
|
+
:return: voltage_array – a NumPy array containing the voltage of the battery at each time step
|
80
|
+
|
81
|
+
:return: stored_energy_array– a NumPy array containing the energy stored in the battery at each time step
|
82
|
+
|
83
|
+
"""
|
84
|
+
|
85
|
+
stored_energy_array = np.full_like(cumulative_energy_array, fill_value=self.stored_energy)
|
86
|
+
stored_energy_array += cumulative_energy_array / 3600
|
87
|
+
stored_energy_array = np.clip(stored_energy_array, a_min=0, a_max=self.max_energy_capacity)
|
88
|
+
|
89
|
+
energy_discharged_array = np.full_like(cumulative_energy_array, fill_value=self.max_energy_capacity) - \
|
90
|
+
stored_energy_array
|
91
|
+
|
92
|
+
discharge_capacity_array = self.calculate_discharge_capacity_from_energy(energy_discharged_array)
|
93
|
+
|
94
|
+
soc_array = self.calculate_soc_from_discharge_capacity(discharge_capacity_array)
|
95
|
+
voltage_array = self.calculate_voltage_from_discharge_capacity(discharge_capacity_array)
|
96
|
+
|
97
|
+
return soc_array, voltage_array, stored_energy_array
|
98
|
+
|
99
|
+
def get_raw_soc(self, cumulative_energy_array):
|
100
|
+
"""
|
101
|
+
|
102
|
+
Return the not truncated (SOC is allowed to go above 100% and below 0%) state of charge.
|
103
|
+
|
104
|
+
:param np.ndarray cumulative_energy_array: a NumPy array containing the cumulative energy changes at each time step
|
105
|
+
experienced by the battery
|
106
|
+
|
107
|
+
:return: a NumPy array containing the battery state of charge at each time step
|
108
|
+
:rtype: np.ndarray
|
109
|
+
|
110
|
+
"""
|
111
|
+
|
112
|
+
stored_energy_array = np.full_like(cumulative_energy_array, fill_value=self.stored_energy)
|
113
|
+
stored_energy_array += cumulative_energy_array / 3600
|
114
|
+
|
115
|
+
energy_discharged_array = np.full_like(cumulative_energy_array, fill_value=self.max_energy_capacity) - stored_energy_array
|
116
|
+
|
117
|
+
discharge_capacity_array = self.calculate_discharge_capacity_from_energy(energy_discharged_array)
|
118
|
+
|
119
|
+
soc_array = self.calculate_soc_from_discharge_capacity(discharge_capacity_array)
|
120
|
+
|
121
|
+
return soc_array
|
122
|
+
|
123
|
+
|
124
|
+
def calculate_voltage_from_discharge_capacity():
|
125
|
+
return Polynomial([117.6, -0.858896]) # -0.8589x + 117.6
|
126
|
+
|
127
|
+
|
128
|
+
def calculate_energy_from_discharge_capacity():
|
129
|
+
return Polynomial([0, 117.6, -0.429448]) # -0.4294x^2 + 117.6x
|
130
|
+
|
131
|
+
|
132
|
+
def calculate_soc_from_discharge_capacity(max_current_capacity):
|
133
|
+
return Polynomial([1, -1 / max_current_capacity])
|
134
|
+
|
135
|
+
|
136
|
+
def calculate_discharge_capacity_from_soc(max_current_capacity):
|
137
|
+
return Polynomial([max_current_capacity, -max_current_capacity])
|
138
|
+
|
139
|
+
|
140
|
+
def calculate_discharge_capacity_from_energy():
|
141
|
+
return lambda x: 136.92 - np.sqrt(18747.06027 - 2.32857 * x)
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
mod battery;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Radius of the Earth (m)
|
2
|
+
EARTH_RADIUS = 6371009
|
3
|
+
|
4
|
+
# Acceleration caused by gravity (m/s^2)
|
5
|
+
ACCELERATION_G = 9.81
|
6
|
+
|
7
|
+
# Density of Air at 15C and 101kPa (kg/m^3)
|
8
|
+
AIR_DENSITY = 1.225
|
9
|
+
|
10
|
+
# Maximum number of waypoints that can be given to generate route data
|
11
|
+
MAX_WAYPOINTS = 10
|
12
|
+
|
13
|
+
# Solar Irradiance (W/m^2)
|
14
|
+
SOLAR_IRRADIANCE = 1353
|
15
|
+
|
16
|
+
# As we currently have a limited number of API calls(60) every minute with the
|
17
|
+
# current Weather API, we must shrink the dataset significantly. As the
|
18
|
+
# OpenWeatherAPI models have a resolution of between 2.5 - 70 km, we will
|
19
|
+
# go for a resolution of 25km. Assuming we travel at 100km/h for 12 hours,
|
20
|
+
# 1200 kilometres/25 = 48 API calls
|
21
|
+
# As the Google Maps API has a resolution of around 40m between points,
|
22
|
+
# for ASC, we must cull at 625:1 (because 25,000m / 40m = 625)
|
23
|
+
REDUCTION_FACTOR = 625
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from physics.models.lvs.base_lvs import BaseLVS
|
2
|
+
|
3
|
+
|
4
|
+
class BasicLVS(BaseLVS):
|
5
|
+
|
6
|
+
def __init__(self, consumed_energy, lvs_current, lvs_voltage):
|
7
|
+
super().__init__(consumed_energy)
|
8
|
+
self.lvs_current = lvs_current
|
9
|
+
self.lvs_voltage = lvs_voltage
|
10
|
+
|
11
|
+
def get_consumed_energy(self, tick):
|
12
|
+
"""
|
13
|
+
Get the energy consumption of the Low Voltage System (current * voltage * time)
|
14
|
+
|
15
|
+
:param tick - (int) tick time passed
|
16
|
+
:returns: consumed_energy - (number) value of energy consumed
|
17
|
+
"""
|
18
|
+
return self.lvs_current * self.lvs_voltage * tick
|
File without changes
|
physics/models/lvs.rs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
mod lvs;
|
@@ -0,0 +1,174 @@
|
|
1
|
+
import math
|
2
|
+
import numpy as np
|
3
|
+
|
4
|
+
from physics.models.motor.base_motor import BaseMotor
|
5
|
+
from physics.models.constants import ACCELERATION_G, AIR_DENSITY
|
6
|
+
|
7
|
+
|
8
|
+
class BasicMotor(BaseMotor):
|
9
|
+
def __init__(self, vehicle_mass, road_friction, tire_radius, vehicle_frontal_area, drag_coefficient):
|
10
|
+
super().__init__()
|
11
|
+
|
12
|
+
# Instantaneous voltage supplied by the battery to the motor controller
|
13
|
+
self.dc_v = 0
|
14
|
+
|
15
|
+
# Instantaneous current supplied by the battery to the motor controller
|
16
|
+
self.dc_i = 0
|
17
|
+
|
18
|
+
self.input_power = 0
|
19
|
+
self.vehicle_mass = vehicle_mass
|
20
|
+
self.acceleration_g = ACCELERATION_G
|
21
|
+
self.road_friction = road_friction
|
22
|
+
self.tire_radius = tire_radius
|
23
|
+
|
24
|
+
self.air_density = AIR_DENSITY
|
25
|
+
self.vehicle_frontal_area = vehicle_frontal_area
|
26
|
+
self.drag_coefficient = drag_coefficient
|
27
|
+
|
28
|
+
self.friction_force = (self.vehicle_mass * self.acceleration_g * self.road_friction)
|
29
|
+
|
30
|
+
self.e_mc = 0.98 # motor controller efficiency, subject to change
|
31
|
+
self.e_m = 0.9 # motor efficiency, subject to change
|
32
|
+
|
33
|
+
# print("torque experienced by motor: {} Nm".format(self.constant_torque))
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def calculate_motor_efficiency(motor_angular_speed, motor_output_energy, tick):
|
37
|
+
"""
|
38
|
+
|
39
|
+
Calculates a NumPy array of motor efficiency from NumPy array of operating angular speeds and NumPy array
|
40
|
+
of output power. Based on data obtained from NGM SC-M150 Datasheet and modelling done in MATLAB
|
41
|
+
|
42
|
+
r squared value: 0.873
|
43
|
+
|
44
|
+
:param np.ndarray motor_angular_speed: (float[N]) angular speed motor operates in rad/s
|
45
|
+
:param np.ndarray motor_output_energy: (float[N]) energy motor outputs to the wheel in J
|
46
|
+
:param float tick: length of 1 update cycle in seconds
|
47
|
+
:returns e_m: (float[N]) efficiency of the motor
|
48
|
+
:rtype: np.ndarray
|
49
|
+
|
50
|
+
"""
|
51
|
+
|
52
|
+
# Power = Energy / Time
|
53
|
+
motor_output_power = motor_output_energy * tick
|
54
|
+
rads_rpm_conversion_factor = 30 / math.pi
|
55
|
+
|
56
|
+
revolutions_per_minute = motor_angular_speed * rads_rpm_conversion_factor
|
57
|
+
|
58
|
+
e_m = calculate_motor_efficiency(motor_output_power, revolutions_per_minute)
|
59
|
+
|
60
|
+
e_m[e_m < 0.7382] = 0.7382
|
61
|
+
e_m[e_m > 1] = 1
|
62
|
+
|
63
|
+
return e_m
|
64
|
+
|
65
|
+
@staticmethod
|
66
|
+
def calculate_motor_controller_efficiency(motor_angular_speed, motor_output_energy, tick):
|
67
|
+
"""
|
68
|
+
|
69
|
+
Calculates a NumPy array of motor controller efficiency from NumPy array of operating angular speeds and
|
70
|
+
NumPy array of output power. Based on data obtained from the WaveSculptor Motor Controller Datasheet efficiency
|
71
|
+
curve for a 90 V DC Bus and modelling done in MATLAB.
|
72
|
+
|
73
|
+
r squared value: 0.7431
|
74
|
+
|
75
|
+
:param np.ndarray motor_angular_speed: (float[N]) angular speed motor operates in rad/s
|
76
|
+
:param np.ndarray motor_output_energy: (float[N]) energy motor outputs to the wheel in J
|
77
|
+
:param float tick: length of 1 update cycle in seconds
|
78
|
+
:returns e_mc (float[N]) efficiency of the motor controller
|
79
|
+
:rtype: np.ndarray
|
80
|
+
|
81
|
+
"""
|
82
|
+
|
83
|
+
# Ignore nan warning. Set nan value to 0
|
84
|
+
np.seterr(divide='ignore', invalid='ignore')
|
85
|
+
|
86
|
+
# Power = Energy / Time
|
87
|
+
motor_output_power = motor_output_energy / tick
|
88
|
+
|
89
|
+
# Torque = Power / Angular Speed
|
90
|
+
motor_torque_array = np.nan_to_num(motor_output_power / motor_angular_speed)
|
91
|
+
|
92
|
+
np.seterr(divide='warn', invalid='warn')
|
93
|
+
|
94
|
+
e_mc = calculate_motor_controller_efficiency(motor_angular_speed, motor_torque_array)
|
95
|
+
|
96
|
+
e_mc[e_mc < 0.9] = 0.9
|
97
|
+
e_mc[e_mc > 1] = 1
|
98
|
+
|
99
|
+
return e_mc
|
100
|
+
|
101
|
+
def calculate_energy_in(self, required_speed_kmh, gradients, wind_speeds, tick):
|
102
|
+
"""
|
103
|
+
|
104
|
+
Create a function which takes in array of elevation, array of wind speed, required
|
105
|
+
speed, returns the consumed energy.
|
106
|
+
|
107
|
+
:param np.ndarray required_speed_kmh: (float[N]) required speed array in km/h
|
108
|
+
:param np.ndarray gradients: (float[N]) gradient at parts of the road
|
109
|
+
:param np.ndarray wind_speeds: (float[N]) speeds of wind in m/s, where > 0 means against the direction of the vehicle
|
110
|
+
:param float tick: length of 1 update cycle in seconds
|
111
|
+
:returns: (float[N]) energy expended by the motor at every tick
|
112
|
+
:rtype: np.ndarray
|
113
|
+
|
114
|
+
"""
|
115
|
+
required_speed_ms = required_speed_kmh / 3.6
|
116
|
+
|
117
|
+
acceleration_ms2 = np.clip(np.gradient(required_speed_ms), a_min=0, a_max=None)
|
118
|
+
acceleration_force = acceleration_ms2 * self.vehicle_mass
|
119
|
+
|
120
|
+
required_angular_speed_rads = required_speed_ms / self.tire_radius
|
121
|
+
|
122
|
+
drag_forces = 0.5 * self.air_density * (
|
123
|
+
(required_speed_ms + wind_speeds) ** 2) * self.drag_coefficient * self.vehicle_frontal_area
|
124
|
+
|
125
|
+
angles = np.arctan(gradients)
|
126
|
+
g_forces = self.vehicle_mass * self.acceleration_g * np.sin(angles)
|
127
|
+
|
128
|
+
road_friction_array = self.road_friction * self.vehicle_mass * self.acceleration_g * np.cos(angles)
|
129
|
+
|
130
|
+
net_force = road_friction_array + drag_forces + g_forces + acceleration_force
|
131
|
+
|
132
|
+
motor_output_energies = required_angular_speed_rads * net_force * self.tire_radius * tick
|
133
|
+
motor_output_energies = np.clip(motor_output_energies, a_min=0, a_max=None)
|
134
|
+
|
135
|
+
e_m = self.calculate_motor_efficiency(required_angular_speed_rads, motor_output_energies, tick)
|
136
|
+
e_mc = self.calculate_motor_controller_efficiency(required_angular_speed_rads, motor_output_energies, tick)
|
137
|
+
|
138
|
+
motor_controller_input_energies = motor_output_energies / (e_m * e_mc)
|
139
|
+
|
140
|
+
# Filter out and replace negative energy consumption as 0
|
141
|
+
motor_controller_input_energies = np.where(motor_controller_input_energies > 0,
|
142
|
+
motor_controller_input_energies, 0)
|
143
|
+
|
144
|
+
return motor_controller_input_energies
|
145
|
+
|
146
|
+
def __str__(self):
|
147
|
+
return (f"Tire radius: {self.tire_radius}m\n"
|
148
|
+
f"Rolling resistance coefficient: {self.road_friction}\n"
|
149
|
+
f"Vehicle mass: {self.vehicle_mass}kg\n"
|
150
|
+
f"Acceleration of gravity: {self.acceleration_g}m/s^2\n"
|
151
|
+
f"Motor controller efficiency: {self.e_mc}%\n"
|
152
|
+
f"Motor efficiency: {self.e_m}%\n")
|
153
|
+
|
154
|
+
|
155
|
+
def calculate_motor_efficiency(motor_output_power, revolutions_per_minute):
|
156
|
+
return 0.7382 - (6.281e-5 * motor_output_power) + (6.708e-4 * revolutions_per_minute) \
|
157
|
+
- (2.89e-8 * motor_output_power ** 2) + (2.416e-7 * motor_output_power * revolutions_per_minute) \
|
158
|
+
- (8.672e-7 * revolutions_per_minute ** 2) + (5.653e-12 * motor_output_power ** 3) \
|
159
|
+
- (1.74e-11 * motor_output_power ** 2 * revolutions_per_minute) \
|
160
|
+
- (7.322e-11 * motor_output_power * revolutions_per_minute ** 2) \
|
161
|
+
+ (3.263e-10 * revolutions_per_minute ** 3)
|
162
|
+
|
163
|
+
|
164
|
+
def calculate_motor_controller_efficiency(motor_angular_speed, motor_torque_array):
|
165
|
+
return 0.7694 + (0.007818 * motor_angular_speed) + (0.007043 * motor_torque_array) \
|
166
|
+
- (1.658e-4 * motor_angular_speed ** 2) - (1.806e-5 * motor_torque_array * motor_angular_speed) \
|
167
|
+
- (1.909e-4 * motor_torque_array ** 2) + (1.602e-6 * motor_angular_speed ** 3) \
|
168
|
+
+ (4.236e-7 * motor_angular_speed ** 2 * motor_torque_array) \
|
169
|
+
- (2.306e-7 * motor_angular_speed * motor_torque_array ** 2) \
|
170
|
+
+ (2.122e-06 * motor_torque_array ** 3) - (5.701e-09 * motor_angular_speed ** 4) \
|
171
|
+
- (2.054e-9 * motor_angular_speed ** 3 * motor_torque_array) \
|
172
|
+
- (3.126e-10 * motor_angular_speed ** 2 * motor_torque_array ** 2) \
|
173
|
+
+ (1.708e-09 * motor_angular_speed * motor_torque_array ** 3) \
|
174
|
+
- (8.094e-09 * motor_torque_array ** 4)
|
File without changes
|
physics/models/motor.rs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
mod motor;
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from physics.models.regen import BaseRegen
|
2
|
+
import numpy as np
|
3
|
+
|
4
|
+
|
5
|
+
class BasicRegen(BaseRegen):
|
6
|
+
GRAVITY = 9.81
|
7
|
+
EFFICIENCY = 0.5 # currently set to 50% but best case scenario is 60-70%
|
8
|
+
|
9
|
+
def __init__(self, vehicle_mass):
|
10
|
+
super().__init__()
|
11
|
+
self.min_decel_mag = 0
|
12
|
+
self.vehicle_mass = vehicle_mass
|
13
|
+
self.kmh_to_mps = 0.278
|
14
|
+
|
15
|
+
def calculate_produced_energy(self, speed_kmh, gis_route_elevations, min_regen_speed, max_power):
|
16
|
+
"""
|
17
|
+
Returns a numpy array containing the energy produced by regen
|
18
|
+
during each tick of the race based on the change in energy in that tick
|
19
|
+
:param speed_kmh: an array containing the speeds at each tick
|
20
|
+
:param gis_route_elevations: an array containing elevations on the route at each tick
|
21
|
+
"""
|
22
|
+
# get the changes of energy from tick i to tick i + 1
|
23
|
+
speed_ms = speed_kmh / 3.6 # Convert to m/s from km/h
|
24
|
+
delta_kinetic_energy = np.diff((1 / 2) * self.vehicle_mass * pow(speed_ms, 2), append=[0])
|
25
|
+
delta_potential_energy = np.diff(self.vehicle_mass * self.GRAVITY * gis_route_elevations, append=[0])
|
26
|
+
|
27
|
+
# get the total change in energy at each tick
|
28
|
+
delta_energy = delta_kinetic_energy + delta_potential_energy
|
29
|
+
|
30
|
+
# create regen energy produced array
|
31
|
+
# if delta_energy is negative, we regen that energy back at the set efficiency rate; else 0 energy regen
|
32
|
+
produced_energy = np.where(delta_energy < 0, abs(delta_energy) * self.EFFICIENCY, 0)
|
33
|
+
|
34
|
+
# Regen does not occur below a certain speed
|
35
|
+
produced_energy = np.where(speed_ms >= min_regen_speed, produced_energy, 0)
|
36
|
+
|
37
|
+
# Regen power is capped by current limitations
|
38
|
+
|
39
|
+
return np.clip(produced_energy, a_min=0, a_max=max_power)
|
File without changes
|
physics/models/regen.rs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
mod regen;
|
physics/models.rs
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 UBC Solar
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,136 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: ubc-solar-physics
|
3
|
+
Version: 1.0.3
|
4
|
+
Summary: UBC Solar's Simulation Environment
|
5
|
+
Author: Fisher Xue, Mihir Nimgade, Chris Chang, David Widjaja, Justin Hua, Ilya Veksler, Renu Rajamagesh, Ritchie Xia, Erik Langille, Chris Aung, Nicolas Ric, Ishaan Trivedi, Jason Liang, Felix Toft, Mack Wilson, Jonah Lee, Tamzeed Quazi, Joshua Riefman
|
6
|
+
Author-email: UBC Solar <strategy@ubcsolar.com>
|
7
|
+
Maintainer: Renu Rajamagmesh, Felix Toft, Mack Wilson, Jonah Lee, Tamzeed Quazi
|
8
|
+
Maintainer-email: UBC Solar <strategy@ubcsolar.com>, Joshua Riefman <joshuariefman@gmail.com>
|
9
|
+
License: MIT License
|
10
|
+
|
11
|
+
Copyright (c) 2024 UBC Solar
|
12
|
+
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
15
|
+
in the Software without restriction, including without limitation the rights
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
18
|
+
furnished to do so, subject to the following conditions:
|
19
|
+
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
21
|
+
copies or substantial portions of the Software.
|
22
|
+
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
29
|
+
SOFTWARE.
|
30
|
+
|
31
|
+
Project-URL: Homepage, https://ubcsolar.com
|
32
|
+
Project-URL: Repository, https://github.com/UBC-Solar/physics
|
33
|
+
Project-URL: Documentation, https://ubc-solar-physics.readthedocs.io/en/latest/
|
34
|
+
Keywords: car,simulation,solar
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
36
|
+
Classifier: Programming Language :: Rust
|
37
|
+
Classifier: Natural Language :: English
|
38
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
39
|
+
Requires-Python: >=3.9
|
40
|
+
Description-Content-Type: text/markdown
|
41
|
+
License-File: LICENSE
|
42
|
+
Requires-Dist: backports.tarfile ==1.2.0
|
43
|
+
Requires-Dist: certifi ==2024.7.4
|
44
|
+
Requires-Dist: charset-normalizer ==3.3.2
|
45
|
+
Requires-Dist: dill ==0.3.8
|
46
|
+
Requires-Dist: haversine ==2.8.1
|
47
|
+
Requires-Dist: idna ==3.7
|
48
|
+
Requires-Dist: importlib-metadata ==8.2.0
|
49
|
+
Requires-Dist: jaraco.classes ==3.4.0
|
50
|
+
Requires-Dist: jaraco.context ==5.3.0
|
51
|
+
Requires-Dist: jaraco.functools ==4.0.2
|
52
|
+
Requires-Dist: keyring ==25.3.0
|
53
|
+
Requires-Dist: llvmlite ==0.43.0
|
54
|
+
Requires-Dist: markdown-it-py ==3.0.0
|
55
|
+
Requires-Dist: maturin ==1.7.0
|
56
|
+
Requires-Dist: mdurl ==0.1.2
|
57
|
+
Requires-Dist: more-itertools ==10.4.0
|
58
|
+
Requires-Dist: nh3 ==0.2.18
|
59
|
+
Requires-Dist: numba ==0.60.0
|
60
|
+
Requires-Dist: numpy ==2.0.1
|
61
|
+
Requires-Dist: pkginfo ==1.10.0
|
62
|
+
Requires-Dist: Pygments ==2.18.0
|
63
|
+
Requires-Dist: readme-renderer ==44.0
|
64
|
+
Requires-Dist: requests ==2.32.3
|
65
|
+
Requires-Dist: requests-toolbelt ==1.0.0
|
66
|
+
Requires-Dist: rfc3986 ==2.0.0
|
67
|
+
Requires-Dist: rich ==13.7.1
|
68
|
+
Requires-Dist: tqdm ==4.66.5
|
69
|
+
Requires-Dist: urllib3 ==2.2.2
|
70
|
+
Requires-Dist: zipp ==3.20.0
|
71
|
+
|
72
|
+
# UBC Solar Physics
|
73
|
+
|
74
|
+
<!-- marker-index-start -->
|
75
|
+
|
76
|
+
[](https://ubc-solar-physics.readthedocs.io/en/latest/?badge=latest)
|
77
|
+
|
78
|
+
UBC Solar's physics and environment models for simulating our groundbreaking solar cars.
|
79
|
+
|
80
|
+
The API is currently unstable, and backwards compatibility may not be maintained.
|
81
|
+
|
82
|
+
## Requirements
|
83
|
+
|
84
|
+
Versions indicated are recommended
|
85
|
+
|
86
|
+
* Git [^1]
|
87
|
+
* Python >=3.9 [^2]
|
88
|
+
* Rustc >=1.79.0 [^3]
|
89
|
+
* Cargo >=1.79.0 [^4]
|
90
|
+
|
91
|
+
## Installation
|
92
|
+
|
93
|
+
First, clone this repository.
|
94
|
+
|
95
|
+
```bash
|
96
|
+
git clone https://github.com/UBC-Solar/physics.git
|
97
|
+
```
|
98
|
+
Then, create and activate a virtual environment.
|
99
|
+
Next, install dependencies in editable mode.
|
100
|
+
|
101
|
+
```bash
|
102
|
+
pip3 install -e .
|
103
|
+
```
|
104
|
+
|
105
|
+
## Getting Started
|
106
|
+
|
107
|
+
Example of calculating solar arrays produced energy
|
108
|
+
|
109
|
+
```python
|
110
|
+
from physics.models.arrays import BasicArray
|
111
|
+
import numpy as np
|
112
|
+
|
113
|
+
efficiency = 0.25 # 25.0% efficient
|
114
|
+
panel_size = 4.0 # 4.0m^2 of panels
|
115
|
+
tick = 1.0 # 1.0s interval
|
116
|
+
|
117
|
+
arrays = BasicArray(panel_efficiency=efficiency, panel_size=panel_size)
|
118
|
+
|
119
|
+
irradiance = np.full([5], 400.0) # 10 seconds of 400.0W/m^2 irradiance
|
120
|
+
|
121
|
+
solar_power_produced = arrays.calculate_produced_energy(solar_irradiance=irradiance, tick=tick)
|
122
|
+
|
123
|
+
assert np.array_equal(solar_power_produced, np.array([400.0, 400.0, 400.0, 400.0, 400.0]))
|
124
|
+
```
|
125
|
+
|
126
|
+
## Appendix
|
127
|
+
|
128
|
+
[^1]: use `git --version` to verify version
|
129
|
+
|
130
|
+
[^2]: use `python3 --version` to verify version
|
131
|
+
|
132
|
+
[^3]: use `rustc --version` to verify version
|
133
|
+
|
134
|
+
[^4]: use `cargo --version` to verify version
|
135
|
+
|
136
|
+
<!-- marker-index-end -->
|