ubc-solar-physics 1.2.0__cp313-cp313-win_amd64.whl → 1.4.0__cp313-cp313-win_amd64.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.cp313-win_amd64.pyd +0 -0
- physics/_version.py +9 -4
- physics/models/battery/battery_config.toml +8 -0
- physics/models/battery/kalman_filter.py +161 -0
- physics/models/regen/basic_regen.py +14 -1
- {ubc_solar_physics-1.2.0.dist-info → ubc_solar_physics-1.4.0.dist-info}/METADATA +2 -2
- {ubc_solar_physics-1.2.0.dist-info → ubc_solar_physics-1.4.0.dist-info}/RECORD +10 -9
- {ubc_solar_physics-1.2.0.dist-info → ubc_solar_physics-1.4.0.dist-info}/WHEEL +1 -1
- {ubc_solar_physics-1.2.0.dist-info → ubc_solar_physics-1.4.0.dist-info}/LICENSE +0 -0
- {ubc_solar_physics-1.2.0.dist-info → ubc_solar_physics-1.4.0.dist-info}/top_level.txt +0 -0
core.cp313-win_amd64.pyd
CHANGED
Binary file
|
physics/_version.py
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
# file generated by
|
1
|
+
# file generated by setuptools-scm
|
2
2
|
# don't change, don't track in version control
|
3
|
+
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
5
|
+
|
3
6
|
TYPE_CHECKING = False
|
4
7
|
if TYPE_CHECKING:
|
5
|
-
from typing import Tuple
|
8
|
+
from typing import Tuple
|
9
|
+
from typing import Union
|
10
|
+
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
7
12
|
else:
|
8
13
|
VERSION_TUPLE = object
|
@@ -12,5 +17,5 @@ __version__: str
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
13
18
|
version_tuple: VERSION_TUPLE
|
14
19
|
|
15
|
-
__version__ = version = '1.
|
16
|
-
__version_tuple__ = version_tuple = (1,
|
20
|
+
__version__ = version = '1.4.0'
|
21
|
+
__version_tuple__ = version_tuple = (1, 4, 0)
|
@@ -0,0 +1,8 @@
|
|
1
|
+
R_0_data = [0.002564, 0.002541, 0.002541, 0.002558, 0.002549, 0.002574, 0.002596, 0.002626, 0.002676, 0.002789]
|
2
|
+
R_P = 0.000530
|
3
|
+
C_P = 14646
|
4
|
+
Q_total = 259200
|
5
|
+
SOC_data = [0.0752, 0.1705, 0.2677, 0.366, 0.4654, 0.5666, 0.6701, 0.7767, 0.8865, 1.0]
|
6
|
+
Uoc_data = [3.481, 3.557, 3.597, 3.623, 3.660, 3.750, 3.846, 3.946, 4.056, 4.183]
|
7
|
+
max_current_capacity = 40
|
8
|
+
max_energy_capacity = 500
|
@@ -178,3 +178,164 @@ def check_Terminal_V(Ut):
|
|
178
178
|
raise TypeError(f"Invalid type for measured_Ut: {type(Ut)}. Expected float or int.")
|
179
179
|
if not (0.0 <= Ut <= 5.0):
|
180
180
|
raise ValueError(f"Invalid value for terminal voltage (measured_Ut): {Ut}. Must be between 0.0 and 5.0 volts.")
|
181
|
+
|
182
|
+
import numpy as np
|
183
|
+
from filterpy.kalman import ExtendedKalmanFilter as EKF
|
184
|
+
from physics.models.battery.battery_config import BatteryModelConfig
|
185
|
+
import numpy as np
|
186
|
+
|
187
|
+
class EKF_SOC():
|
188
|
+
def __init__(self, battery_config: BatteryModelConfig, initial_SOC = 1, initial_Uc = 0):
|
189
|
+
"""
|
190
|
+
EKF_SOC represents the kalman filter used for predicting state of charge.
|
191
|
+
|
192
|
+
Attributes:
|
193
|
+
battery_model_config (BatteryModelConfig): This contains the HPPC parameters of the battery model
|
194
|
+
initial_SOC (float/int): Ranges from 0 to 1 inclusive. The initial state of charge of the battery.
|
195
|
+
initial_Uc (float/int): The initial polarization volatge of the battery. (V)
|
196
|
+
"""
|
197
|
+
# Inital state
|
198
|
+
self.SOC = initial_SOC
|
199
|
+
self.Uc = initial_Uc # Polarization Volatge
|
200
|
+
|
201
|
+
# Covariance Matrices
|
202
|
+
self.Q_covariance = np.eye(2) * 0.0001
|
203
|
+
self.R_covariance = np.eye(1) * 0.5 # currently not really trusting the predicted state
|
204
|
+
|
205
|
+
# Load Config data
|
206
|
+
self.R_P = battery_config.R_P
|
207
|
+
self.C_P = battery_config.C_P
|
208
|
+
self.Q_total = battery_config.Q_total
|
209
|
+
SOC_data = np.array(battery_config.SOC_data)
|
210
|
+
Uoc_data = np.array(battery_config.Uoc_data)
|
211
|
+
R_0_data = np.array(battery_config.R_0_data)
|
212
|
+
|
213
|
+
# polynomial interpolation
|
214
|
+
self.Uoc_coefficients = np.polyfit(SOC_data, Uoc_data, 7)
|
215
|
+
self.R_0_coefficients = np.polyfit(SOC_data, R_0_data, 7)
|
216
|
+
self.Uoc_derivative_coefficients = np.polyder(self.Uoc_coefficients)
|
217
|
+
|
218
|
+
self.tau = self.R_P / self.C_P
|
219
|
+
|
220
|
+
# initializing the ekf object
|
221
|
+
self.ekf = EKF(dim_x=2, dim_z=1)
|
222
|
+
self.ekf.x = np.array([self.SOC, self.Uc])
|
223
|
+
self.ekf.P = np.diag([1e-6, 1e-6]) # I'm keeping low uncertainty in initial SOC and Uc
|
224
|
+
self.ekf.Q = self.Q_covariance
|
225
|
+
self.ekf.R = self.R_covariance
|
226
|
+
|
227
|
+
# For logs
|
228
|
+
self.predicted_measurment = 0
|
229
|
+
|
230
|
+
def get_SOC(self):
|
231
|
+
""" Return the state of charge of the battery """
|
232
|
+
return self.SOC
|
233
|
+
|
234
|
+
def get_Uc(self):
|
235
|
+
""" Return the polarization voltage of the battery """
|
236
|
+
return self.Uc
|
237
|
+
|
238
|
+
def get_predicted_Ut(self):
|
239
|
+
""" Return the predicted terminal voltage for the last prediction step """
|
240
|
+
return self.predicted_measurment
|
241
|
+
|
242
|
+
def update_filter(self, measured_Ut, I):
|
243
|
+
"""
|
244
|
+
Update the filter based on a new measurment, and the predicted state.
|
245
|
+
This function should be called after predict_state in a typical predict update workflow.
|
246
|
+
|
247
|
+
Attributes:
|
248
|
+
measured_Ut (float/integer): The actual voltage across the terminals of the battery.
|
249
|
+
I (float/integer): The current being sourced by the battery
|
250
|
+
"""
|
251
|
+
check_Terminal_V(measured_Ut)
|
252
|
+
|
253
|
+
h_jacobian = self._measurement_jacobian
|
254
|
+
Hx = self._measurement_function
|
255
|
+
|
256
|
+
self.ekf.update(z=measured_Ut, HJacobian=h_jacobian, Hx=Hx, hx_args=I)
|
257
|
+
|
258
|
+
self.SOC, self.Uc = self.ekf.x
|
259
|
+
|
260
|
+
def predict_state(self, I, time_step):
|
261
|
+
"""
|
262
|
+
Predicts the next evolution of the state vector (SOC, Uc).
|
263
|
+
This function should be called before updating the filter in a typical predict update workflow.
|
264
|
+
|
265
|
+
Attributes:
|
266
|
+
I (float/integer): The current being sourced by the battery. Positive indicated current being drawn.
|
267
|
+
time_step (float/integer): Time elapsed between this prediction and the last updated state of filter. (Seconds)
|
268
|
+
"""
|
269
|
+
check_current(I)
|
270
|
+
# Control matrix B (for input current I_k)
|
271
|
+
self.ekf.B = np.array([-time_step / self.Q_total, self.R_P * (1 - np.exp(-time_step / self.tau))])
|
272
|
+
self.ekf.F = self._state_jacobian(time_step)
|
273
|
+
|
274
|
+
self.ekf.predict(u=I)
|
275
|
+
print(f'ekf prediction: {self.ekf.x_prior}')
|
276
|
+
|
277
|
+
def predict_then_update(self, measured_Ut, I, time_step):
|
278
|
+
"""
|
279
|
+
Predicts the next evolution of the state vector (SOC, Uc), then updates the filter
|
280
|
+
based on this prediction and a measurement.
|
281
|
+
This function abstracts the full predict update workflow of the EKF.
|
282
|
+
|
283
|
+
Attributes:
|
284
|
+
measured_Ut (float/integer): The actual voltage across the terminals of the battery.
|
285
|
+
I (float/integer): The current being sourced by the battery. Positive indicated current being drawn.
|
286
|
+
time_step (float/integer): Time elapsed between this prediction and the last updated state of filter. (Seconds)
|
287
|
+
"""
|
288
|
+
check_current(I)
|
289
|
+
check_Terminal_V(measured_Ut)
|
290
|
+
|
291
|
+
self.predict_state(I, time_step)
|
292
|
+
print(f'predicted: {self.ekf.x_prior}')
|
293
|
+
|
294
|
+
self.update_filter(measured_Ut, I)
|
295
|
+
print(f'SOC: {self.ekf.x[0]}, Uc: {self.ekf.x[1]}')
|
296
|
+
|
297
|
+
def _state_jacobian(self, time_step):
|
298
|
+
"""
|
299
|
+
Returns the state jacobian for the current time
|
300
|
+
|
301
|
+
Attributes:
|
302
|
+
time_step (float/integer): Time elapsed between this prediction and the last updated state of filter. (Seconds)
|
303
|
+
"""
|
304
|
+
return np.array([[1, 0], [0, np.exp(-time_step / self.tau)]])
|
305
|
+
|
306
|
+
def _measurement_jacobian(self, x):
|
307
|
+
"""
|
308
|
+
Returns the measurement jacobian for the current time
|
309
|
+
|
310
|
+
Attributes:
|
311
|
+
x [float, float]: The state vector [SOC, Uc], where both values are floats or integers.
|
312
|
+
"""
|
313
|
+
SOC = x[0]
|
314
|
+
derivative = np.polyval(self.Uoc_derivative_coefficients, SOC)
|
315
|
+
return np.array([[derivative, -1]])
|
316
|
+
|
317
|
+
def _measurement_function(self, x, I):
|
318
|
+
"""
|
319
|
+
The customized measurement equation relating Ut to SOC and Uc
|
320
|
+
|
321
|
+
Attributes:
|
322
|
+
x [float, float]: The state vector [SOC, Uc], where both values are floats or integers.
|
323
|
+
I (float/integer): The current being sourced by the battery. Positive indicated current being drawn.
|
324
|
+
"""
|
325
|
+
SOC, Uc = x
|
326
|
+
R_0 = np.polyval(self.R_0_coefficients, SOC)
|
327
|
+
Uoc = np.polyval(self.Uoc_coefficients, SOC)
|
328
|
+
self.predicted_measurment = Uoc - Uc - R_0*I
|
329
|
+
return self.predicted_measurment
|
330
|
+
|
331
|
+
def check_current(I):
|
332
|
+
if not isinstance(I, (float, int)):
|
333
|
+
raise TypeError(f"Invalid type for current I: {type(I)}. Expected float or int.")
|
334
|
+
if not (-45.0 <= I <= 45.0):
|
335
|
+
raise ValueError(f"Invalid value for current (I): {I}. Must be between -45.0A and 45.0A.")
|
336
|
+
|
337
|
+
def check_Terminal_V(Ut):
|
338
|
+
if not isinstance(Ut, (float, int)):
|
339
|
+
raise TypeError(f"Invalid type for measured_Ut: {type(Ut)}. Expected float or int.")
|
340
|
+
if not (0.0 <= Ut <= 5.0):
|
341
|
+
raise ValueError(f"Invalid value for terminal voltage (measured_Ut): {Ut}. Must be between 0.0 and 5.0 volts.")
|
@@ -12,6 +12,18 @@ class BasicRegen(BaseRegen):
|
|
12
12
|
self.vehicle_mass = vehicle_mass
|
13
13
|
self.kmh_to_mps = 0.278
|
14
14
|
|
15
|
+
def get_regen_efficiency(self, speed_array):
|
16
|
+
"""
|
17
|
+
Returns a numpy array of regen efficiency percentage based on the vehicle speed in m/s.
|
18
|
+
|
19
|
+
:param speed_array: a numpy array of speeds in m/s
|
20
|
+
:returns: numpy array of regen efficiency percentage
|
21
|
+
"""
|
22
|
+
# Efficiency polynomial, more details can be found in regen_analysis folder located in data_analysis
|
23
|
+
efficiency_poly = [0.022288416685942, 0.026545396753597]
|
24
|
+
|
25
|
+
return np.polyval(efficiency_poly, speed_array)
|
26
|
+
|
15
27
|
def calculate_produced_energy(self, speed_kmh, gis_route_elevations, min_regen_speed, max_power):
|
16
28
|
"""
|
17
29
|
Returns a numpy array containing the energy produced by regen
|
@@ -29,7 +41,8 @@ class BasicRegen(BaseRegen):
|
|
29
41
|
|
30
42
|
# create regen energy produced array
|
31
43
|
# if delta_energy is negative, we regen that energy back at the set efficiency rate; else 0 energy regen
|
32
|
-
|
44
|
+
efficiencies = self.get_regen_efficiency(speed_ms)
|
45
|
+
produced_energy = np.where(delta_energy < 0, abs(delta_energy) * efficiencies, 0)
|
33
46
|
|
34
47
|
# Regen does not occur below a certain speed
|
35
48
|
produced_energy = np.where(speed_ms >= min_regen_speed, produced_energy, 0)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: ubc-solar-physics
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: UBC Solar's Simulation Environment
|
5
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
6
|
Author-email: UBC Solar <strategy@ubcsolar.com>
|
@@ -42,7 +42,7 @@ License-File: LICENSE
|
|
42
42
|
Requires-Dist: backports.tarfile==1.2.0
|
43
43
|
Requires-Dist: certifi==2024.7.4
|
44
44
|
Requires-Dist: charset-normalizer==3.3.2
|
45
|
-
Requires-Dist: dill
|
45
|
+
Requires-Dist: dill>=0.3.8
|
46
46
|
Requires-Dist: haversine==2.8.1
|
47
47
|
Requires-Dist: idna==3.7
|
48
48
|
Requires-Dist: importlib_metadata==8.2.0
|
@@ -1,6 +1,6 @@
|
|
1
|
-
core.cp313-win_amd64.pyd,sha256=
|
1
|
+
core.cp313-win_amd64.pyd,sha256=HYmmy1U151O6esJac8W147vXTQN7gV7YGoWfM0ZYUok,382464
|
2
2
|
physics/__init__.py,sha256=jRV9J_eGh0vNXEfFrILqcM6xxVjyqm3XwKAg1B1IPBs,183
|
3
|
-
physics/_version.py,sha256=
|
3
|
+
physics/_version.py,sha256=mQpvfIn8roT-O8LS07P1pK-7O17jWuq_vL-TTh8VVvs,532
|
4
4
|
physics/environment.rs,sha256=OghmBkvHLZvzzuVsXUmV2lR3X_tEwuB9sT2TGZLQC6E,36
|
5
5
|
physics/lib.rs,sha256=FqnhKkotYKJCu8v1vbov2QW9s0apay7-BnEcUgxOakU,5798
|
6
6
|
physics/models.rs,sha256=747ABP-D1XKxA6X_MNh1PbmST0zsxpxhP_pEWjbR46c,63
|
@@ -33,8 +33,9 @@ physics/models/battery/base_battery.py,sha256=yU-QopEEQ83kw4CUvJ2MEhYyj3AM3LYY_h
|
|
33
33
|
physics/models/battery/basic_battery.py,sha256=5o-7g5xflhNLKuJyeqOY-1rLIOSIy_CJ0U4GEqeQO1E,5894
|
34
34
|
physics/models/battery/battery.rs,sha256=0wIQVli7UOWgKXT96cQLWisLQKo5UE08X4B9dl09USI,3649
|
35
35
|
physics/models/battery/battery_config.py,sha256=Dsi7cXR8SL0v7aSTuihhB6il9-8h1a2P8qrGbdvlf8Q,617
|
36
|
+
physics/models/battery/battery_config.toml,sha256=J8jzmhg1mdfFqiHreiRfbbb_5byJklgVBEXKsn3sSUI,393
|
36
37
|
physics/models/battery/battery_model.py,sha256=kHn-xOyFBzWycpd-9Wn75fzHYZ512wXOi1dUrlt3a5M,6313
|
37
|
-
physics/models/battery/kalman_filter.py,sha256=
|
38
|
+
physics/models/battery/kalman_filter.py,sha256=axDzXTpAmJVE_AcOv3nk8nhJJSztyaAwXQ6qC2Y0hj4,14231
|
38
39
|
physics/models/lvs/__init__.py,sha256=ZBips6zW4Lot7SkQZMZt_OGRNUqgOfUlDtBA5lfUkM4,114
|
39
40
|
physics/models/lvs/base_lvs.py,sha256=kVLfGd9Qwql4-6u86uwHbJoFCgYpG07r0cAR2Ngsq38,116
|
40
41
|
physics/models/lvs/basic_lvs.py,sha256=xNXeN6RGSZkJLhtcW0n2xZU6YIOT4bKUIbOFdmh4zc0,621
|
@@ -45,10 +46,10 @@ physics/models/motor/basic_motor.py,sha256=hHGngG5lvZNzw7qgU3-c_3KFMKQ9siZCm471R
|
|
45
46
|
physics/models/motor/motor.rs,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
47
|
physics/models/regen/__init__.py,sha256=JzyRYKwT89FQ6_p9ofCqusl2fnWGHulyiK4P4f8Lakc,126
|
47
48
|
physics/models/regen/base_regen.py,sha256=lY44jrTSHEo8Xv7hKCjo4C3Jx0PUgilyITHwQchT2bM,101
|
48
|
-
physics/models/regen/basic_regen.py,sha256=
|
49
|
+
physics/models/regen/basic_regen.py,sha256=SZl7nj75w-3uczfNfaZAMtcC4WO8M2mTmwbnB0HmrtU,2402
|
49
50
|
physics/models/regen/regen.rs,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
50
|
-
ubc_solar_physics-1.
|
51
|
-
ubc_solar_physics-1.
|
52
|
-
ubc_solar_physics-1.
|
53
|
-
ubc_solar_physics-1.
|
54
|
-
ubc_solar_physics-1.
|
51
|
+
ubc_solar_physics-1.4.0.dist-info/LICENSE,sha256=1Vq7OikLHh7N0xsmTPHCmPkOxk1AXrMK9k1a1icQFlk,1087
|
52
|
+
ubc_solar_physics-1.4.0.dist-info/METADATA,sha256=d_AzqplNDIwtWaZWavMufkFT6QdwjlxwvFCPherV6MA,5107
|
53
|
+
ubc_solar_physics-1.4.0.dist-info/WHEEL,sha256=4oNF-ClUxXRX55MBZicDC2_sTLjRbJdXNr8x18QuDzw,101
|
54
|
+
ubc_solar_physics-1.4.0.dist-info/top_level.txt,sha256=aws060Zz-1h0Kx76JzcE1gLA_AfS1lrRtTCsyUYwDvM,8
|
55
|
+
ubc_solar_physics-1.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|