ubc-solar-physics 1.6.0__cp310-cp310-win_amd64.whl → 1.7.0__cp310-cp310-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.
@@ -1,132 +1,188 @@
1
1
  import numpy as np
2
- from scipy import optimize
3
- from filterpy.kalman import ExtendedKalmanFilter as EKF
4
- from physics.models.battery.battery_config import BatteryModelConfig
2
+ from filterpy.kalman import ExtendedKalmanFilter
3
+ from typing import Protocol, runtime_checkable, cast, Callable
4
+ from physics.models.battery import EquivalentCircuitModelConfig
5
+ from numpy.typing import NDArray
5
6
 
6
7
 
7
- class EKF_SOC:
8
- def __init__(self, battery_config: BatteryModelConfig, initial_SOC=1, initial_Uc=0):
8
+ @runtime_checkable
9
+ class FilteredBatteryModelConfig(Protocol):
10
+ """
11
+ A specification for a configuration object which contains the requisite data to specify
12
+ a `FilteredBatteryModel`.
13
+ """
14
+ @property
15
+ def battery_model_config(self) -> EquivalentCircuitModelConfig:
9
16
  """
10
- EKF_SOC represents the Kalman filter used for predicting state of charge.
17
+ Configuration of the underlying `EquivalentCircuitModel`.
18
+ """
19
+ ...
20
+
21
+ @property
22
+ def process_noise_matrix(self) -> NDArray[float]:
23
+ """
24
+ A 2x2 matrix containing the process noise covariance matrix where [0, 0] is the SOC evolution
25
+ noise and [1, 1] is the polarization potential evolution noise.
26
+ """
27
+ ...
28
+
29
+ @property
30
+ def state_covariance_matrix(self) -> NDArray[float]:
31
+ """
32
+ A 2x2 matrix containing the state covariance matrix where [0, 0] is the SOC covariance
33
+ noise and [1, 1] is the polarization potential covariance.
34
+ """
35
+ ...
36
+
37
+ @property
38
+ def measurement_noise_vector(self) -> NDArray[float]:
39
+ """
40
+ A 1x1 vector containing the noise expected in the terminal voltage measurement.
41
+ """
42
+ ...
43
+
11
44
 
12
- :param BatteryModelConfig battery_config: Contains the HPPC parameters of the battery model.
13
- :param float initial_SOC: Initial state of charge of the battery (ranges from 0 to 1 inclusive, default is 1).
14
- :param float initial_Uc: Initial polarization voltage of the battery in volts (default is 0).
45
+ class FilteredBatteryModel:
46
+ """
47
+ `FilteredBatteryModel` is a first-order Thevenin equivalent model of a lithium-ion battery packed, wrapped
48
+ in a Kalman filter which uses voltage measurements with model predictions.
49
+ """
50
+ def __init__(
51
+ self,
52
+ battery_config: FilteredBatteryModelConfig,
53
+ initial_SOC: float = 1.0,
54
+ initial_Uc: float = 0.0,
55
+ alpha: float = 0.9
56
+ ):
57
+ """
58
+ :param FilteredBatteryModelConfig battery_config: Contains Kalman filter state estimation configuration and
59
+ underlying equivalent circuit model configuration.
60
+ :param float initial_SOC: Initial SOC of the battery, in the range (0, 1].
61
+ :param float initial_Uc: Initial polarization voltage of the battery in Volts.
15
62
  """
16
63
  # Initial state
17
- self.SOC = initial_SOC
18
- self.Uc = initial_Uc # Polarization Voltage
64
+ assert 0.0 <= initial_SOC <= 1.1, "`initial_SOC` must be in (0, 1.1]!"
19
65
 
20
- # Covariance Matrices
21
- self.Q_covariance = np.eye(2) * 0.0001
22
- self.R_covariance = np.eye(1) * 0.5 # currently not really trusting the predicted state
66
+ self._SOC = initial_SOC # State of Charge
67
+ self._Uc = initial_Uc # Polarization Voltage
23
68
 
24
69
  # Load Config data
25
- self.R_P = battery_config.R_P
26
- self.C_P = battery_config.C_P
27
- self.Q_total = battery_config.Q_total
28
- SOC_data = battery_config.SOC_data
29
- Uoc_data = battery_config.Uoc_data
30
- R_0_data = battery_config.R_0_data
31
-
32
- def quintic_polynomial(x, x0, x1, x2, x3, x4):
33
- return np.polyval([x0, x1, x2, x3, x4], x)
34
-
35
- U_oc_coefficients, _ = optimize.curve_fit(quintic_polynomial, SOC_data, Uoc_data)
36
- R_0_coefficients, _ = optimize.curve_fit(quintic_polynomial, SOC_data, R_0_data)
37
- self.U_oc = lambda soc: np.polyval(U_oc_coefficients, soc) # Open-circuit voltage as a function of SOC
38
- self.R_0 = lambda soc: np.polyval(R_0_coefficients, soc) # Resistance as a function of SOC
39
- self.Uoc_derivative = lambda soc: np.polyval(np.polyder(U_oc_coefficients), soc) # Derivative of Uoc wrt SOC
40
-
41
- self.tau = self.R_P / self.C_P
42
-
43
- # initializing the ekf object
44
- self.ekf = EKF(dim_x=2, dim_z=1)
45
- self.ekf.x = np.array([self.SOC, self.Uc])
46
- self.ekf.P = np.diag([1e-6, 1e-6]) # I'm keeping low uncertainty in initial SOC and Uc
47
- self.ekf.Q = self.Q_covariance
48
- self.ekf.R = self.R_covariance
49
-
50
- # For logs
51
- self.predicted_measurement = 0
52
-
53
- def get_SOC(self):
70
+ self._Q_total = battery_config.battery_model_config.Q_total
71
+
72
+ # These `cast` calls just promise to the type-checker that these will map floats to floats
73
+ self._U_oc = cast(Callable[[float], float], battery_config.battery_model_config.get_Uoc)
74
+ self._R_0 = cast(Callable[[float], float], battery_config.battery_model_config.get_R_0)
75
+ self._R_P = cast(Callable[[float], float], battery_config.battery_model_config.get_R_P)
76
+ self._C_P = cast(Callable[[float], float], battery_config.battery_model_config.get_C_P)
77
+
78
+ self._tau: Callable[[float], float] = lambda soc: self._R_P(soc) * self._C_P(soc) # Characteristic Time in s
79
+
80
+ def central_difference_derivative(func, value, h=1e-6):
81
+ """
82
+ Compute the derivative of an arbitrary function `func` at `SOC` using central difference.
83
+
84
+ :param func: The function to differentiate.
85
+ :param value: The point at which to compute the derivative.
86
+ :param h: Step size for the finite difference.
87
+ :return: The derivative of the function at `SOC`.
88
+ """
89
+ return (func(value + h) - func(value - h)) / (2 * h)
90
+
91
+ self._dUoc_dSOC = lambda soc: central_difference_derivative(self._U_oc, np.minimum(1.0, soc)) # dUOC wrt to SOC
92
+ self._dR0_dSOC = lambda soc: central_difference_derivative(self._R_0, np.minimum(1.0, soc)) # dR0 wrt to SOC
93
+
94
+ # Initializing the EKF object
95
+ self._ekf = ExtendedKalmanFilter(dim_x=2, dim_z=1)
96
+
97
+ # State Vector
98
+ self._ekf.x = np.array([
99
+ self._SOC,
100
+ self._Uc]
101
+ )
102
+
103
+ self._ekf.P = battery_config.state_covariance_matrix
104
+ self._ekf.Q = battery_config.process_noise_matrix
105
+ self._ekf.R = battery_config.measurement_noise_vector
106
+
107
+ assert 0 <= alpha <= 1, "`alpha` should be between 0 and 1!"
108
+ self._alpha = alpha
109
+
110
+ self._filtered_I = 0
111
+ self._predicted_measurement = 0
112
+
113
+ @property
114
+ def SOC(self) -> float:
54
115
  """
55
- Return the current state of charge of the battery.
116
+ Return the current SOC of the battery.
56
117
 
57
118
  :return: The current state of charge.
58
- :rtype: float
59
119
  """
60
- return self.SOC
120
+ return self._SOC
61
121
 
62
- def get_Uc(self):
122
+ @property
123
+ def Uc(self) -> float:
63
124
  """
64
125
  Return the polarization voltage of the battery.
65
126
 
66
127
  :return: The current polarization voltage.
67
- :rtype: float
68
128
  """
69
- return self.Uc
129
+ return self._Uc
70
130
 
71
- def get_predicted_Ut(self):
131
+ @property
132
+ def Ut(self) -> float:
72
133
  """
73
134
  Return the predicted terminal voltage for the last prediction step.
74
135
 
75
136
  :return: The predicted terminal voltage.
76
- :rtype: float
77
137
  """
78
- return self.predicted_measurement
79
-
80
- def update_filter(self, measured_Ut, I):
138
+ return self._predicted_measurement
139
+
140
+ def update_filter(self, measured_Ut, current):
81
141
  """
82
142
  Update the filter based on a new measurement and the predicted state.
83
143
  This function should be called after `predict_state` in a typical predict-update workflow.
84
144
 
85
145
  :param float measured_Ut: The actual voltage across the terminals of the battery.
86
- :param float I: The current being sourced by the battery.
146
+ :param float current: The current being sourced by the battery.
87
147
  """
88
- check_Terminal_V(measured_Ut)
89
-
90
- h_jacobian = self._measurement_jacobian
91
- Hx = self._measurement_function
148
+ # Simple low-pass filter to current
149
+ self._filtered_I = self._alpha * self._filtered_I + (1 - self._alpha) * current
92
150
 
93
- self.ekf.update(z=measured_Ut, HJacobian=h_jacobian, Hx=Hx, hx_args=I)
151
+ self._ekf.update(z=measured_Ut, HJacobian=self._measurement_jacobian, Hx=self._measurement_function)
94
152
 
95
- self.SOC, self.Uc = self.ekf.x
153
+ self._SOC, self._Uc = self._ekf.x
154
+ self._SOC = np.clip(self._SOC, 0.0, 1.1)
96
155
 
97
- def predict_state(self, I, time_step):
156
+ def predict_state(self, current, time_step):
98
157
  """
99
158
  Predict the next evolution of the state vector (SOC, Uc).
100
159
  This function should be called before updating the filter in a typical predict-update workflow.
101
160
 
102
- :param float I: The current being sourced by the battery. Positive indicates current being drawn.
161
+ :param float current: The current being sourced by the battery.
162
+ Sign convention is that positive indicates current being drawn.
103
163
  :param float time_step: Time elapsed between this prediction and the last updated state of the filter (seconds).
104
- """
105
- check_current(I)
164
+ """
106
165
  # Control matrix B (for input current I_k)
107
- self.ekf.B = np.array([-time_step / self.Q_total, self.R_P * (1 - np.exp(-time_step / self.tau))])
108
- self.ekf.F = self._state_jacobian(time_step)
166
+ self._ekf.B = np.array([
167
+ -time_step / self._Q_total,
168
+ self._R_P(self._SOC) * (1 - np.exp(-time_step / (self._tau(self._SOC)))),
169
+ ])
170
+ self._ekf.F = self._state_jacobian(time_step)
109
171
 
110
- self.ekf.predict(u=I)
111
- print(f'ekf prediction: {self.ekf.x_prior}')
172
+ self._ekf.predict(u=current)
173
+ self._SOC, self._Uc = self._ekf.x
112
174
 
113
- def predict_then_update(self, measured_Ut, I, time_step):
175
+ def predict_then_update(self, measured_Ut: float, current: float, time_step: float):
114
176
  """
115
177
  Predict the next evolution of the state vector (SOC, Uc), then update the filter
116
178
  based on this prediction and a measurement. Abstracts the full predict-update workflow of the EKF.
117
179
 
118
180
  :param float measured_Ut: The actual voltage across the terminals of the battery.
119
- :param float I: The current being sourced by the battery. Positive indicates current being drawn.
181
+ :param float current: The current being sourced by the battery. Positive indicates current being drawn.
120
182
  :param float time_step: Time elapsed between this prediction and the last updated state of the filter (seconds).
121
183
  """
122
- check_current(I)
123
- check_Terminal_V(measured_Ut)
124
-
125
- self.predict_state(I, time_step)
126
- print(f'predicted: {self.ekf.x_prior}')
127
-
128
- self.update_filter(measured_Ut, I)
129
- print(f'SOC: {self.ekf.x[0]}, Uc: {self.ekf.x[1]}')
184
+ self.predict_state(current, time_step)
185
+ self.update_filter(measured_Ut, current)
130
186
 
131
187
  def _state_jacobian(self, time_step):
132
188
  """
@@ -136,7 +192,7 @@ class EKF_SOC:
136
192
  :return: The state Jacobian matrix.
137
193
  :rtype: np.ndarray
138
194
  """
139
- return np.array([[1, 0], [0, np.exp(-time_step / self.tau)]])
195
+ return np.array([[1, 0], [0, np.exp(-time_step / self._tau(self._SOC))]])
140
196
 
141
197
  def _measurement_jacobian(self, x):
142
198
  """
@@ -147,195 +203,21 @@ class EKF_SOC:
147
203
  :rtype: np.ndarray
148
204
  """
149
205
  SOC = x[0]
150
- derivative = self.Uoc_derivative(SOC)
151
- return np.array([[derivative, -1]])
206
+ dUoc_dSOC = self._dUoc_dSOC(SOC)
207
+ dR0_dSOC = self._dR0_dSOC(SOC)
208
+
209
+ return np.array([[dUoc_dSOC - dR0_dSOC * self._filtered_I, -1]])
152
210
 
153
- def _measurement_function(self, x, I):
211
+ def _measurement_function(self, x) -> float:
154
212
  """
155
213
  Return the measurement function relating terminal voltage to SOC and polarization voltage.
156
214
 
157
215
  :param list[float, float] x: The state vector [SOC, Uc].
158
- :param float I: The current being sourced by the battery.
159
216
  :return: The predicted terminal voltage.
160
- :rtype: float
161
217
  """
162
218
  SOC, Uc = x
163
- R_0 = self.R_0(SOC)
164
- Uoc = self.U_oc(SOC)
165
- self.predicted_measurement = Uoc - Uc - R_0 * I
166
- return self.predicted_measurement
167
-
168
-
169
- def check_current(I):
170
- if not isinstance(I, (float, int)):
171
- raise TypeError(f"Invalid type for current I: {type(I)}. Expected float or int.")
172
- if not (-45.0 <= I <= 45.0):
173
- raise ValueError(f"Invalid value for current (I): {I}. Must be between -45.0A and 45.0A.")
174
-
175
-
176
- def check_Terminal_V(Ut):
177
- if not isinstance(Ut, (float, int)):
178
- raise TypeError(f"Invalid type for measured_Ut: {type(Ut)}. Expected float or int.")
179
- if not (0.0 <= Ut <= 5.0):
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.
219
+ Uoc = self._U_oc(SOC)
220
+ R0 = self._R_0(SOC)
221
+ self._predicted_measurement = Uoc - Uc - R0 * self._filtered_I
246
222
 
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.")
223
+ return self._predicted_measurement
@@ -0,0 +1,111 @@
1
+ import numpy as np
2
+
3
+
4
+ def constrain_speeds(speed_limits: np.ndarray, speeds: np.ndarray, tick: int) -> np.ndarray:
5
+ """
6
+ Constrains the vehicle speeds based on the speed limits and computes new speeds.
7
+
8
+ :param speed_limits: Array of speed limits (km/h) for each point.
9
+ :param speeds: Array of vehicle speeds (km/h).
10
+ :param tick: The time step (in some unit, e.g., seconds or ticks).
11
+
12
+ :return: A NumPy array of constrained vehicle speeds (km/h).
13
+ """
14
+ ...
15
+
16
+
17
+ def calculate_array_ghi_times(python_local_times: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
18
+ """
19
+ Calculates the array of GHI times based on local times.
20
+
21
+ :param python_local_times: Array of local times (UNIX timestamps or other format).
22
+
23
+ :return: A tuple of two NumPy arrays:
24
+ - Day of year (f64)
25
+ - Local time (f64)
26
+ """
27
+ ...
28
+
29
+
30
+ def closest_gis_indices_loop(python_cumulative_distances: np.ndarray,
31
+ python_average_distances: np.ndarray) -> np.ndarray:
32
+ """
33
+ Finds the closest GIS indices based on cumulative and average distances.
34
+
35
+ :param python_cumulative_distances: Array of cumulative distances.
36
+ :param python_average_distances: Array of average distances.
37
+
38
+ :return: A NumPy array of indices (i64) corresponding to the closest GIS indices.
39
+ """
40
+ ...
41
+
42
+
43
+ def closest_weather_indices_loop(
44
+ python_cumulative_distances: np.ndarray,
45
+ python_average_distances: np.ndarray
46
+ ) -> np.ndarray:
47
+ """
48
+ Finds the closest weather indices based on cumulative and average distances.
49
+
50
+ :param python_cumulative_distances: Array of cumulative distances.
51
+ :param python_average_distances: Array of average distances.
52
+
53
+ :return: A NumPy array of indices (i64) corresponding to the closest weather indices.
54
+ """
55
+ ...
56
+
57
+
58
+ def weather_in_time(
59
+ python_unix_timestamps: np.ndarray,
60
+ python_indices: np.ndarray,
61
+ python_weather_forecast: np.ndarray,
62
+ index: int
63
+ ) -> np.ndarray:
64
+ """
65
+ Retrieves the weather forecast at specific times for given indices.
66
+
67
+ :param python_unix_timestamps: Array of UNIX timestamps.
68
+ :param python_indices: Array of indices to look up.
69
+ :param python_weather_forecast: Array of weather forecasts.
70
+ :param index: A specific index to look up in the weather forecast.
71
+
72
+ :return: A NumPy array of weather values (f64) at the specified times and indices.
73
+ """
74
+ ...
75
+
76
+
77
+ def update_battery_state(
78
+ python_energy_or_current_array: np.ndarray,
79
+ time_step: float,
80
+ initial_state_of_charge: float,
81
+ initial_polarization_potential: float,
82
+ python_internal_resistance_lookup: np.ndarray,
83
+ python_open_circuit_voltage_lookup: np.ndarray,
84
+ python_polarization_resistance_lookup: np.ndarray,
85
+ python_polarization_capacitance_lookup: np.ndarray,
86
+ nominal_charge_capacity: float,
87
+ is_power: bool,
88
+ quantization_step: float,
89
+ min_soc: float
90
+ ) -> tuple[np.ndarray, np.ndarray]:
91
+ """
92
+ Updates the battery state (SOC and terminal voltage) based on energy/current input.
93
+
94
+ :param python_energy_or_current_array: Array of energy or current input values (f64).
95
+ :param time_step: Time step (f64).
96
+ :param initial_state_of_charge: Initial state of charge (f64).
97
+ :param initial_polarization_potential: Initial polarization potential (f64).
98
+ :param python_internal_resistance_lookup: Array of internal resistance values (f64).
99
+ :param python_open_circuit_voltage_lookup: Array of open-circuit voltage values (f64).
100
+ :param python_polarization_resistance_lookup: Array of polarization resistance values (f64).
101
+ :param python_polarization_capacitance_lookup: Array of polarization capacitance values (f64).
102
+ :param nominal_charge_capacity: Nominal charge capacity (f64).
103
+ :param is_power: Boolean flag to indicate if the input is power (`True`) or current (`False`).
104
+ :param quantization_step: The step size used to quantize the SOC (f64).
105
+ :param min_soc: The minimum SOC used when computing the lookup tables
106
+
107
+ :return: A tuple containing:
108
+ - An array of updated SOC values (f64).
109
+ - An array of updated terminal voltage values (f64).
110
+ """
111
+ ...
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ubc-solar-physics
3
- Version: 1.6.0
3
+ Version: 1.7.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>
@@ -73,6 +73,7 @@ Requires-Dist: pandas
73
73
  Requires-Dist: pydantic ==2.9.2
74
74
  Requires-Dist: scipy
75
75
  Requires-Dist: tomli
76
+ Requires-Dist: scipy-stubs
76
77
 
77
78
  # UBC Solar Physics
78
79