ubc-solar-physics 1.0.5__cp39-cp39-win_amd64.whl → 1.2.0__cp39-cp39-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.cp39-win_amd64.pyd +0 -0
- physics/_version.py +2 -2
- physics/environment/__init__.py +0 -7
- physics/environment/meteorology/clouded_meteorology.py +4 -5
- physics/environment/meteorology/irradiant_meteorology.py +5 -4
- physics/lib.rs +34 -0
- physics/models/battery/__init__.py +8 -1
- physics/models/battery/basic_battery.py +0 -1
- physics/models/battery/battery.rs +78 -0
- physics/models/battery/battery_config.py +22 -0
- physics/models/battery/battery_model.py +135 -0
- physics/models/battery/kalman_filter.py +180 -0
- physics/models/battery.rs +1 -1
- physics/models.rs +1 -1
- {ubc_solar_physics-1.0.5.dist-info → ubc_solar_physics-1.2.0.dist-info}/METADATA +36 -30
- {ubc_solar_physics-1.0.5.dist-info → ubc_solar_physics-1.2.0.dist-info}/RECORD +19 -17
- {ubc_solar_physics-1.0.5.dist-info → ubc_solar_physics-1.2.0.dist-info}/WHEEL +1 -1
- physics/environment/race.py +0 -89
- {ubc_solar_physics-1.0.5.dist-info → ubc_solar_physics-1.2.0.dist-info}/LICENSE +0 -0
- {ubc_solar_physics-1.0.5.dist-info → ubc_solar_physics-1.2.0.dist-info}/top_level.txt +0 -0
core.cp39-win_amd64.pyd
CHANGED
Binary file
|
physics/_version.py
CHANGED
physics/environment/__init__.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from physics.environment.meteorology.base_meteorology import BaseMeteorology
|
2
2
|
from physics.environment.gis.gis import calculate_path_distances
|
3
3
|
import numpy as np
|
4
|
-
from physics.environment.race import Race
|
5
4
|
from numba import jit
|
6
5
|
import core
|
7
6
|
from typing import Optional
|
@@ -28,7 +27,7 @@ class CloudedMeteorology(BaseMeteorology):
|
|
28
27
|
|
29
28
|
self.last_updated_time = self._weather_forecast[0, 0, 2]
|
30
29
|
|
31
|
-
def spatially_localize(self, cumulative_distances: np.ndarray) -> None:
|
30
|
+
def spatially_localize(self, cumulative_distances: np.ndarray, simplify_weather: bool = False) -> None:
|
32
31
|
"""
|
33
32
|
|
34
33
|
IMPORTANT: we only have weather coordinates for a discrete set of coordinates. However, the car could be at any
|
@@ -46,14 +45,14 @@ class CloudedMeteorology(BaseMeteorology):
|
|
46
45
|
`get_weather_forecast_in_time()` method.
|
47
46
|
|
48
47
|
:param np.ndarray cumulative_distances: NumPy Array representing cumulative distances theoretically achievable for a given input speed array
|
49
|
-
|
48
|
+
:param bool simplify_weather: enable to only use a single weather coordinate (for track races without varying weather)
|
50
49
|
"""
|
51
50
|
|
52
|
-
#
|
51
|
+
# If racing a track race, there is no need for distance calculations. We will return only the origin coordinate
|
53
52
|
# This characterizes the weather at every point along the FSGP tracks
|
54
53
|
# with the weather at a single coordinate on the track, which is great for reducing the API calls and is a
|
55
54
|
# reasonable assumption to make for FSGP only.
|
56
|
-
if
|
55
|
+
if simplify_weather:
|
57
56
|
self._weather_indices = np.zeros_like(cumulative_distances, dtype=int)
|
58
57
|
return
|
59
58
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from physics.environment.meteorology.base_meteorology import BaseMeteorology
|
2
2
|
from physics.environment.gis.gis import calculate_path_distances
|
3
3
|
import numpy as np
|
4
|
-
from physics.environment.race import Race
|
5
4
|
import core
|
6
5
|
from typing import Optional
|
7
6
|
|
@@ -25,16 +24,18 @@ class IrradiantMeteorology(BaseMeteorology):
|
|
25
24
|
|
26
25
|
super().__init__()
|
27
26
|
|
28
|
-
def spatially_localize(self, cumulative_distances: np.ndarray) -> None:
|
27
|
+
def spatially_localize(self, cumulative_distances: np.ndarray, simplify_weather: bool = False) -> None:
|
29
28
|
"""
|
30
29
|
|
31
30
|
:param np.ndarray cumulative_distances: NumPy Array representing cumulative distances theoretically achievable for a given input speed array
|
31
|
+
:param bool simplify_weather: enable to only use a single weather coordinate (for track races without varying weather)
|
32
|
+
|
32
33
|
"""
|
33
|
-
#
|
34
|
+
# If racing a track race, there is no need for distance calculations. We will return only the origin coordinate
|
34
35
|
# This characterizes the weather at every point along the FSGP tracks
|
35
36
|
# with the weather at a single coordinate on the track, which is great for reducing the API calls and is a
|
36
37
|
# reasonable assumption to make for FSGP only.
|
37
|
-
if
|
38
|
+
if simplify_weather:
|
38
39
|
self._weather_indices = np.zeros_like(cumulative_distances, dtype=int)
|
39
40
|
return
|
40
41
|
|
physics/lib.rs
CHANGED
@@ -8,6 +8,7 @@ pub mod environment;
|
|
8
8
|
pub mod models;
|
9
9
|
use crate::environment::gis::gis::rust_closest_gis_indices_loop;
|
10
10
|
use crate::environment::meteorology::meteorology::{rust_calculate_array_ghi_times, rust_closest_weather_indices_loop, rust_weather_in_time, rust_closest_timestamp_indices};
|
11
|
+
use crate::models::battery::battery::update_battery_array;
|
11
12
|
|
12
13
|
fn constrain_speeds(speed_limits: ArrayViewD<f64>, speeds: ArrayViewD<f64>, tick: i32) -> Vec<f64> {
|
13
14
|
let mut distance: f64 = 0.0;
|
@@ -94,5 +95,38 @@ fn rust_simulation(_py: Python, m: &PyModule) -> PyResult<()> {
|
|
94
95
|
py_result
|
95
96
|
}
|
96
97
|
|
98
|
+
#[pyfn(m)]
|
99
|
+
#[pyo3(name = "update_battery_array")]
|
100
|
+
fn update_battery_array_py<'py>(
|
101
|
+
py: Python<'py>,
|
102
|
+
python_delta_energy_array: PyReadwriteArrayDyn<'py, f64>,
|
103
|
+
time_step: f64,
|
104
|
+
initial_state_of_charge: f64,
|
105
|
+
initial_polarization_potential: f64,
|
106
|
+
polarization_resistance: f64,
|
107
|
+
python_internal_resistance_coeffs: PyReadwriteArrayDyn<'py, f64>,
|
108
|
+
python_open_circuit_voltage_coeffs: PyReadwriteArrayDyn<'py, f64>,
|
109
|
+
time_constant: f64,
|
110
|
+
nominal_charge_capacity: f64,
|
111
|
+
) -> (&'py PyArrayDyn<f64>, &'py PyArrayDyn<f64>) {
|
112
|
+
let delta_energy_array = python_delta_energy_array.as_array();
|
113
|
+
let internal_resistance_coeffs = python_internal_resistance_coeffs.as_array();
|
114
|
+
let open_circuit_voltage_coeffs = python_open_circuit_voltage_coeffs.as_array();
|
115
|
+
let (soc_array, voltage_array): (Vec<f64>, Vec<f64>) = update_battery_array(
|
116
|
+
delta_energy_array,
|
117
|
+
time_step,
|
118
|
+
initial_state_of_charge,
|
119
|
+
initial_polarization_potential,
|
120
|
+
polarization_resistance,
|
121
|
+
internal_resistance_coeffs,
|
122
|
+
open_circuit_voltage_coeffs,
|
123
|
+
time_constant,
|
124
|
+
nominal_charge_capacity,
|
125
|
+
);
|
126
|
+
let py_soc_array = PyArray::from_vec(py, soc_array).to_dyn();
|
127
|
+
let py_voltage_array = PyArray::from_vec(py, voltage_array).to_dyn();
|
128
|
+
(py_soc_array, py_voltage_array)
|
129
|
+
}
|
130
|
+
|
97
131
|
Ok(())
|
98
132
|
}
|
@@ -1,7 +1,14 @@
|
|
1
1
|
from .base_battery import BaseBattery
|
2
2
|
from .basic_battery import BasicBattery
|
3
|
+
from .battery_model import BatteryModel
|
4
|
+
from .kalman_filter import EKF_SOC
|
5
|
+
from .battery_config import BatteryModelConfig, load_battery_config
|
3
6
|
|
4
7
|
__all__ = [
|
5
8
|
"BaseBattery",
|
6
|
-
"BasicBattery"
|
9
|
+
"BasicBattery",
|
10
|
+
"BatteryModel",
|
11
|
+
"EKF_SOC",
|
12
|
+
"BatteryModelConfig",
|
13
|
+
"load_battery_config"
|
7
14
|
]
|
@@ -0,0 +1,78 @@
|
|
1
|
+
use std::f64;
|
2
|
+
use numpy::ndarray::ArrayViewD;
|
3
|
+
|
4
|
+
/// Evaluate a polynomial given coefficients and an input value (x)
|
5
|
+
fn evaluate_polynomial(coefficients: &[f64], x: f64) -> f64 {
|
6
|
+
coefficients.iter().fold(0.0, |acc, &coeff| acc * x + coeff)
|
7
|
+
}
|
8
|
+
|
9
|
+
/// Evolve the battery state for a single step
|
10
|
+
fn battery_evolve(
|
11
|
+
power: f64, // Watts
|
12
|
+
tick: f64, // Seconds
|
13
|
+
state_of_charge: f64, // Dimensionless, 0 < SOC < 1
|
14
|
+
polarization_potential: f64, // Volts
|
15
|
+
polarization_resistance: f64, // Ohms
|
16
|
+
internal_resistance: f64, // Ohms
|
17
|
+
open_circuit_voltage: f64, // Volts
|
18
|
+
time_constant: f64, // Seconds
|
19
|
+
nominal_charge_capacity: f64, // Nominal charge capacity (Coulombs)
|
20
|
+
) -> (f64, f64, f64) {
|
21
|
+
// Compute current (I) based on power input/output
|
22
|
+
let current: f64 = power / (open_circuit_voltage + polarization_potential + internal_resistance);
|
23
|
+
|
24
|
+
// Update state of charge and polarization potential
|
25
|
+
let new_state_of_charge: f64 = state_of_charge + (current * tick / nominal_charge_capacity);
|
26
|
+
let new_polarization_potential: f64 = f64::exp(-tick / time_constant) * polarization_potential
|
27
|
+
+ current * polarization_resistance * (1.0 - f64::exp(-tick / time_constant));
|
28
|
+
let terminal_voltage: f64 = open_circuit_voltage + new_polarization_potential
|
29
|
+
+ (current * internal_resistance); // Terminal voltage
|
30
|
+
|
31
|
+
(new_state_of_charge, new_polarization_potential, terminal_voltage)
|
32
|
+
}
|
33
|
+
|
34
|
+
pub fn update_battery_array(
|
35
|
+
delta_energy_array: ArrayViewD<'_, f64>, // W*s
|
36
|
+
tick: f64, // Seconds
|
37
|
+
initial_state_of_charge: f64, // dimensionless, 0 < SOC < 1
|
38
|
+
initial_polarization_potential: f64, // Volts
|
39
|
+
polarization_resistance: f64, // Ohms
|
40
|
+
internal_resistance_coeffs: ArrayViewD<'_, f64>, // Coefficients for internal resistance
|
41
|
+
open_circuit_voltage_coeffs: ArrayViewD<'_, f64>, // Coefficients for open-circuit voltage
|
42
|
+
time_constant: f64, // Seconds
|
43
|
+
nominal_charge_capacity: f64, // Coulombs
|
44
|
+
) -> (Vec<f64>, Vec<f64>) {
|
45
|
+
let mut state_of_charge: f64 = initial_state_of_charge;
|
46
|
+
let mut polarization_potential: f64 = initial_polarization_potential;
|
47
|
+
let mut soc_array: Vec<f64> = Vec::with_capacity(delta_energy_array.len());
|
48
|
+
let mut voltage_array: Vec<f64> = Vec::with_capacity(delta_energy_array.len());
|
49
|
+
|
50
|
+
for &power in delta_energy_array.iter() {
|
51
|
+
// Interpolate values from coefficient
|
52
|
+
let open_circuit_voltage: f64 = evaluate_polynomial(open_circuit_voltage_coeffs.as_slice().unwrap(), state_of_charge);
|
53
|
+
let internal_resistance: f64 = evaluate_polynomial(internal_resistance_coeffs.as_slice().unwrap(), state_of_charge);
|
54
|
+
|
55
|
+
let (new_state_of_charge, new_polarization_potential, terminal_voltage) = battery_evolve(
|
56
|
+
power,
|
57
|
+
tick,
|
58
|
+
state_of_charge,
|
59
|
+
polarization_potential,
|
60
|
+
polarization_resistance,
|
61
|
+
internal_resistance,
|
62
|
+
open_circuit_voltage,
|
63
|
+
time_constant,
|
64
|
+
nominal_charge_capacity,
|
65
|
+
);
|
66
|
+
|
67
|
+
// Update state for the next iteration
|
68
|
+
state_of_charge = new_state_of_charge;
|
69
|
+
polarization_potential = new_polarization_potential;
|
70
|
+
|
71
|
+
// Store results
|
72
|
+
soc_array.push(new_state_of_charge);
|
73
|
+
voltage_array.push(terminal_voltage);
|
74
|
+
}
|
75
|
+
|
76
|
+
(soc_array, voltage_array)
|
77
|
+
}
|
78
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import tomli
|
2
|
+
from pydantic import BaseModel
|
3
|
+
from typing import List
|
4
|
+
import os
|
5
|
+
import pathlib
|
6
|
+
|
7
|
+
class BatteryModelConfig(BaseModel):
|
8
|
+
R_0_data: List[float]
|
9
|
+
R_P: float
|
10
|
+
C_P: float
|
11
|
+
Q_total: float
|
12
|
+
SOC_data: List[float]
|
13
|
+
Uoc_data: List[float]
|
14
|
+
max_current_capacity: float
|
15
|
+
max_energy_capacity: float
|
16
|
+
|
17
|
+
def load_battery_config(absolute_path: str) -> BatteryModelConfig:
|
18
|
+
# Build the full path to the config file
|
19
|
+
full_path = pathlib.Path(absolute_path)
|
20
|
+
with open(full_path, 'rb') as f:
|
21
|
+
data = tomli.load(f)
|
22
|
+
return BatteryModelConfig.model_validate(data)
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import core
|
3
|
+
from scipy import optimize
|
4
|
+
from physics.models.battery.battery_config import BatteryModelConfig
|
5
|
+
|
6
|
+
|
7
|
+
class BatteryModel:
|
8
|
+
"""
|
9
|
+
Class representing the Thevenin equivalent battery model with modular parameters.
|
10
|
+
|
11
|
+
Attributes:
|
12
|
+
R_P (float): Polarization resistance of the battery (Ohms).
|
13
|
+
C_P (float): Polarization capacitance of the battery (Farads).
|
14
|
+
max_current_capacity (float): Nominal capacity of the battery (Ah).
|
15
|
+
max_energy_capacity (float): Maximum energy capacity of the battery (Wh).
|
16
|
+
nominal_charge_capacity (float): Total charge capacity of the battery (Coulombs).
|
17
|
+
state_of_charge (float): Current state of charge (dimensionless, 0.0 to 1.0).
|
18
|
+
U_oc_coefficients (np.ndarray): Coefficients for the open-circuit voltage polynomial.
|
19
|
+
R_0_coefficients (np.ndarray): Coefficients for the ohmic resistance polynomial.
|
20
|
+
U_oc (callable): Function for open-circuit voltage as a function of state of charge (V).
|
21
|
+
R_0 (callable): Function for ohmic resistance as a function of state of charge (Ohms).
|
22
|
+
U_P (float): Current polarization potential (V).
|
23
|
+
U_L (float): Current terminal voltage (V).
|
24
|
+
tau (float): Time constant of the battery model (seconds).
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self, battery_config: BatteryModelConfig, state_of_charge=1):
|
28
|
+
"""
|
29
|
+
Constructor for the BatteryModel class.
|
30
|
+
|
31
|
+
:param BatteryModelConfig battery_config: Configuration object containing the battery's parameters and data.
|
32
|
+
:param float state_of_charge: Initial state of charge of the battery (default is 1.0, fully charged).
|
33
|
+
"""
|
34
|
+
|
35
|
+
# ----- Load Config -----
|
36
|
+
|
37
|
+
self.R_P = battery_config.R_P
|
38
|
+
self.C_P = battery_config.C_P
|
39
|
+
self.max_current_capacity = battery_config.max_current_capacity
|
40
|
+
self.max_energy_capacity = battery_config.max_energy_capacity
|
41
|
+
self.nominal_charge_capacity = battery_config.Q_total
|
42
|
+
Soc_data = battery_config.SOC_data
|
43
|
+
Uoc_data = battery_config.Uoc_data
|
44
|
+
R_0_data = battery_config.R_0_data
|
45
|
+
|
46
|
+
# ----- Initialize Parameters -----
|
47
|
+
def quintic_polynomial(x, x0, x1, x2, x3, x4):
|
48
|
+
return np.polyval(np.array([x0, x1, x2, x3, x4]), x)
|
49
|
+
|
50
|
+
self.U_oc_coefficients, _ = optimize.curve_fit(quintic_polynomial, Soc_data, Uoc_data)
|
51
|
+
self.R_0_coefficients, _ = optimize.curve_fit(quintic_polynomial, Soc_data, R_0_data)
|
52
|
+
self.U_oc = lambda soc: np.polyval(self.U_oc_coefficients, soc) # V
|
53
|
+
self.R_0 = lambda soc: np.polyval(self.R_0_coefficients, soc) # Ohms
|
54
|
+
|
55
|
+
self.U_P = 0.0 # V
|
56
|
+
self.U_L = 0.0 # V
|
57
|
+
self.state_of_charge = state_of_charge
|
58
|
+
|
59
|
+
self.tau = self.R_P * self.C_P # Characteristic Time (seconds)
|
60
|
+
|
61
|
+
# calculated the charging and discharging currents
|
62
|
+
self.discharge_current = lambda P, U_oc, U_P, R_0: ((U_oc - U_P) - np.sqrt(
|
63
|
+
np.power((U_oc - U_P), 2) - 4 * R_0 * P)) / (2 * R_0)
|
64
|
+
self.charge_current = lambda P, U_oc, U_P, R_0: (-(U_oc + U_P) + np.sqrt(
|
65
|
+
np.power((U_oc + U_P), 2) + 4 * R_0 * P)) / (2 * R_0)
|
66
|
+
|
67
|
+
def _evolve(self, power: float, tick: float) -> None:
|
68
|
+
"""
|
69
|
+
Update the battery state given the power and time elapsed.
|
70
|
+
|
71
|
+
:param float power: Power applied to the battery (W). Positive for charging, negative for discharging.
|
72
|
+
:param float T: Time interval over which the power is applied (seconds).
|
73
|
+
"""
|
74
|
+
soc = self.state_of_charge # State of Charge (dimensionless, 0 < soc < 1)
|
75
|
+
U_P = self.U_P # Polarization Potential (V)
|
76
|
+
R_P = self.R_P # Polarization Resistance (Ohms)
|
77
|
+
U_oc = self.U_oc(soc) # Open-Circuit Potential (V)
|
78
|
+
R_0 = self.R_0(soc) # Ohmic Resistance (Ohms)
|
79
|
+
Q = self.nominal_charge_capacity # Nominal Charge Capacity (C)
|
80
|
+
|
81
|
+
current = self.discharge_current(power, U_oc, U_P, R_0) if power <= 0 else self.charge_current(power, U_oc, U_P, R_0) # Current (A)
|
82
|
+
|
83
|
+
new_soc = soc + (current * tick / Q)
|
84
|
+
new_U_P = np.exp(-tick / self.tau) * U_P + current * R_P * (1 - np.exp(-tick / self.tau))
|
85
|
+
|
86
|
+
self.state_of_charge = new_soc
|
87
|
+
self.U_P = new_U_P
|
88
|
+
self.U_L = U_oc + U_P + (current * R_0)
|
89
|
+
|
90
|
+
def update_array(self, delta_energy_array, tick, rust=True):
|
91
|
+
"""
|
92
|
+
Compute the battery's state of charge, voltage, and stored energy over time.
|
93
|
+
This function is a wrapper for the Rust-based and Python-based implementations.
|
94
|
+
|
95
|
+
:param np.ndarray delta_energy_array: Array of energy changes (J) at each time step.
|
96
|
+
:param float tick: Time interval for each step (seconds).
|
97
|
+
:param bool rust: If True, use Rust-based calculations (default is True).
|
98
|
+
|
99
|
+
:return: A tuple containing arrays for state-of-charge, voltage, and stored energy.
|
100
|
+
:rtype: tuple[np.ndarray, np.ndarray, np.ndarray]
|
101
|
+
"""
|
102
|
+
|
103
|
+
if rust:
|
104
|
+
return core.update_battery_array(
|
105
|
+
delta_energy_array,
|
106
|
+
tick,
|
107
|
+
self.state_of_charge,
|
108
|
+
self.U_P,
|
109
|
+
self.R_P,
|
110
|
+
self.R_0_coefficients,
|
111
|
+
self.U_oc_coefficients,
|
112
|
+
self.tau,
|
113
|
+
self.nominal_charge_capacity
|
114
|
+
)
|
115
|
+
else:
|
116
|
+
return self._update_array_py(delta_energy_array, tick)
|
117
|
+
|
118
|
+
def _update_array_py(self, delta_energy_array, tick):
|
119
|
+
"""
|
120
|
+
Perform energy calculations using Python (fallback method if Rust is disabled).
|
121
|
+
|
122
|
+
:param np.ndarray delta_energy_array: Array of energy changes (J) at each time step.
|
123
|
+
:param float tick: Time interval for each step (seconds).
|
124
|
+
|
125
|
+
:return: A tuple containing arrays for state-of-charge and voltage.
|
126
|
+
:rtype: tuple[np.ndarray, np.ndarray]
|
127
|
+
"""
|
128
|
+
soc = np.empty_like(delta_energy_array, dtype=float)
|
129
|
+
voltage = np.empty_like(delta_energy_array, dtype=float)
|
130
|
+
for i, energy in enumerate(delta_energy_array):
|
131
|
+
self._evolve(energy, tick)
|
132
|
+
soc[i] = self.state_of_charge
|
133
|
+
voltage[i] = self.U_L
|
134
|
+
|
135
|
+
return soc, voltage
|
@@ -0,0 +1,180 @@
|
|
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
|
5
|
+
|
6
|
+
|
7
|
+
class EKF_SOC:
|
8
|
+
def __init__(self, battery_config: BatteryModelConfig, initial_SOC=1, initial_Uc=0):
|
9
|
+
"""
|
10
|
+
EKF_SOC represents the Kalman filter used for predicting state of charge.
|
11
|
+
|
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).
|
15
|
+
"""
|
16
|
+
# Initial state
|
17
|
+
self.SOC = initial_SOC
|
18
|
+
self.Uc = initial_Uc # Polarization Voltage
|
19
|
+
|
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
|
23
|
+
|
24
|
+
# 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):
|
54
|
+
"""
|
55
|
+
Return the current state of charge of the battery.
|
56
|
+
|
57
|
+
:return: The current state of charge.
|
58
|
+
:rtype: float
|
59
|
+
"""
|
60
|
+
return self.SOC
|
61
|
+
|
62
|
+
def get_Uc(self):
|
63
|
+
"""
|
64
|
+
Return the polarization voltage of the battery.
|
65
|
+
|
66
|
+
:return: The current polarization voltage.
|
67
|
+
:rtype: float
|
68
|
+
"""
|
69
|
+
return self.Uc
|
70
|
+
|
71
|
+
def get_predicted_Ut(self):
|
72
|
+
"""
|
73
|
+
Return the predicted terminal voltage for the last prediction step.
|
74
|
+
|
75
|
+
:return: The predicted terminal voltage.
|
76
|
+
:rtype: float
|
77
|
+
"""
|
78
|
+
return self.predicted_measurement
|
79
|
+
|
80
|
+
def update_filter(self, measured_Ut, I):
|
81
|
+
"""
|
82
|
+
Update the filter based on a new measurement and the predicted state.
|
83
|
+
This function should be called after `predict_state` in a typical predict-update workflow.
|
84
|
+
|
85
|
+
:param float measured_Ut: The actual voltage across the terminals of the battery.
|
86
|
+
:param float I: The current being sourced by the battery.
|
87
|
+
"""
|
88
|
+
check_Terminal_V(measured_Ut)
|
89
|
+
|
90
|
+
h_jacobian = self._measurement_jacobian
|
91
|
+
Hx = self._measurement_function
|
92
|
+
|
93
|
+
self.ekf.update(z=measured_Ut, HJacobian=h_jacobian, Hx=Hx, hx_args=I)
|
94
|
+
|
95
|
+
self.SOC, self.Uc = self.ekf.x
|
96
|
+
|
97
|
+
def predict_state(self, I, time_step):
|
98
|
+
"""
|
99
|
+
Predict the next evolution of the state vector (SOC, Uc).
|
100
|
+
This function should be called before updating the filter in a typical predict-update workflow.
|
101
|
+
|
102
|
+
:param float I: The current being sourced by the battery. Positive indicates current being drawn.
|
103
|
+
:param float time_step: Time elapsed between this prediction and the last updated state of the filter (seconds).
|
104
|
+
"""
|
105
|
+
check_current(I)
|
106
|
+
# 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)
|
109
|
+
|
110
|
+
self.ekf.predict(u=I)
|
111
|
+
print(f'ekf prediction: {self.ekf.x_prior}')
|
112
|
+
|
113
|
+
def predict_then_update(self, measured_Ut, I, time_step):
|
114
|
+
"""
|
115
|
+
Predict the next evolution of the state vector (SOC, Uc), then update the filter
|
116
|
+
based on this prediction and a measurement. Abstracts the full predict-update workflow of the EKF.
|
117
|
+
|
118
|
+
: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.
|
120
|
+
:param float time_step: Time elapsed between this prediction and the last updated state of the filter (seconds).
|
121
|
+
"""
|
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]}')
|
130
|
+
|
131
|
+
def _state_jacobian(self, time_step):
|
132
|
+
"""
|
133
|
+
Return the state Jacobian matrix for the current time step.
|
134
|
+
|
135
|
+
:param float time_step: Time elapsed between this prediction and the last updated state of the filter (seconds).
|
136
|
+
:return: The state Jacobian matrix.
|
137
|
+
:rtype: np.ndarray
|
138
|
+
"""
|
139
|
+
return np.array([[1, 0], [0, np.exp(-time_step / self.tau)]])
|
140
|
+
|
141
|
+
def _measurement_jacobian(self, x):
|
142
|
+
"""
|
143
|
+
Return the measurement Jacobian matrix for the current state vector.
|
144
|
+
|
145
|
+
:param list[float, float] x: The state vector [SOC, Uc].
|
146
|
+
:return: The measurement Jacobian matrix.
|
147
|
+
:rtype: np.ndarray
|
148
|
+
"""
|
149
|
+
SOC = x[0]
|
150
|
+
derivative = self.Uoc_derivative(SOC)
|
151
|
+
return np.array([[derivative, -1]])
|
152
|
+
|
153
|
+
def _measurement_function(self, x, I):
|
154
|
+
"""
|
155
|
+
Return the measurement function relating terminal voltage to SOC and polarization voltage.
|
156
|
+
|
157
|
+
:param list[float, float] x: The state vector [SOC, Uc].
|
158
|
+
:param float I: The current being sourced by the battery.
|
159
|
+
:return: The predicted terminal voltage.
|
160
|
+
:rtype: float
|
161
|
+
"""
|
162
|
+
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.")
|
physics/models/battery.rs
CHANGED
@@ -1 +1 @@
|
|
1
|
-
mod battery;
|
1
|
+
pub mod battery;
|
physics/models.rs
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: ubc-solar-physics
|
3
|
-
Version: 1.0
|
3
|
+
Version: 1.2.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>
|
@@ -39,34 +39,40 @@ Classifier: Topic :: Scientific/Engineering :: Physics
|
|
39
39
|
Requires-Python: >=3.9
|
40
40
|
Description-Content-Type: text/markdown
|
41
41
|
License-File: LICENSE
|
42
|
-
Requires-Dist: backports.tarfile
|
43
|
-
Requires-Dist: certifi
|
44
|
-
Requires-Dist: charset-normalizer
|
45
|
-
Requires-Dist: dill
|
46
|
-
Requires-Dist: haversine
|
47
|
-
Requires-Dist: idna
|
48
|
-
Requires-Dist:
|
49
|
-
Requires-Dist: jaraco.classes
|
50
|
-
Requires-Dist: jaraco.context
|
51
|
-
Requires-Dist: jaraco.functools
|
52
|
-
Requires-Dist: keyring
|
53
|
-
Requires-Dist: llvmlite
|
54
|
-
Requires-Dist: markdown-it-py
|
55
|
-
Requires-Dist: mdurl
|
56
|
-
Requires-Dist: more-itertools
|
57
|
-
Requires-Dist: nh3
|
58
|
-
Requires-Dist: numba
|
59
|
-
Requires-Dist: numpy
|
60
|
-
Requires-Dist: pkginfo
|
61
|
-
Requires-Dist: Pygments
|
62
|
-
Requires-Dist:
|
63
|
-
Requires-Dist: requests
|
64
|
-
Requires-Dist: requests-toolbelt
|
65
|
-
Requires-Dist: rfc3986
|
66
|
-
Requires-Dist: rich
|
67
|
-
Requires-Dist: tqdm
|
68
|
-
Requires-Dist: urllib3
|
69
|
-
Requires-Dist: zipp
|
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: mdurl==0.1.2
|
56
|
+
Requires-Dist: more-itertools==10.4.0
|
57
|
+
Requires-Dist: nh3==0.2.18
|
58
|
+
Requires-Dist: numba==0.60.0
|
59
|
+
Requires-Dist: numpy==2.0.1
|
60
|
+
Requires-Dist: pkginfo==1.10.0
|
61
|
+
Requires-Dist: Pygments==2.18.0
|
62
|
+
Requires-Dist: readme_renderer==44.0
|
63
|
+
Requires-Dist: requests==2.32.3
|
64
|
+
Requires-Dist: requests-toolbelt==1.0.0
|
65
|
+
Requires-Dist: rfc3986==2.0.0
|
66
|
+
Requires-Dist: rich==13.7.1
|
67
|
+
Requires-Dist: tqdm==4.66.5
|
68
|
+
Requires-Dist: urllib3==2.2.2
|
69
|
+
Requires-Dist: zipp==3.20.0
|
70
|
+
Requires-Dist: filterpy==1.4.5
|
71
|
+
Requires-Dist: toml==0.10.2
|
72
|
+
Requires-Dist: pandas
|
73
|
+
Requires-Dist: pydantic==2.9.2
|
74
|
+
Requires-Dist: scipy
|
75
|
+
Requires-Dist: tomli
|
70
76
|
|
71
77
|
# UBC Solar Physics
|
72
78
|
|
@@ -1,26 +1,25 @@
|
|
1
|
-
core.cp39-win_amd64.pyd,sha256=
|
1
|
+
core.cp39-win_amd64.pyd,sha256=kdWR551aw-H-qoXz6vJoE104KevwzuO_WJpKzy8Ta3c,382976
|
2
2
|
physics/__init__.py,sha256=jRV9J_eGh0vNXEfFrILqcM6xxVjyqm3XwKAg1B1IPBs,183
|
3
|
-
physics/_version.py,sha256=
|
3
|
+
physics/_version.py,sha256=DnUqy-xWN4z1XWOsowoRN4Vz2GPuDm_18GEJc3VGkWg,427
|
4
4
|
physics/environment.rs,sha256=OghmBkvHLZvzzuVsXUmV2lR3X_tEwuB9sT2TGZLQC6E,36
|
5
|
-
physics/lib.rs,sha256=
|
6
|
-
physics/models.rs,sha256=
|
7
|
-
physics/environment/__init__.py,sha256=
|
5
|
+
physics/lib.rs,sha256=FqnhKkotYKJCu8v1vbov2QW9s0apay7-BnEcUgxOakU,5798
|
6
|
+
physics/models.rs,sha256=747ABP-D1XKxA6X_MNh1PbmST0zsxpxhP_pEWjbR46c,63
|
7
|
+
physics/environment/__init__.py,sha256=se_LVo4aWZKcZgbbK1KwwhHG8SH2zS1g6TEPw0GOZSs,225
|
8
8
|
physics/environment/environment.rs,sha256=-VztdV2_GSlRbyIV_Pt6gKPVxpuNXpjLgAmoervonLg,34
|
9
9
|
physics/environment/gis.rs,sha256=9R7G0cjf5PxQAz-CSryA6-KGfrh1eSwRhJ6qF8KfjDE,12
|
10
10
|
physics/environment/meteorology.rs,sha256=naWb7qYrtMkCE_tLAkM474fmxaufhCkyhy3TTUQ4Yw4,20
|
11
|
-
physics/environment/race.py,sha256=XuZaNli-68pH0Fy2h2yVKcf4ICsFNE651VtY_aDE754,3017
|
12
11
|
physics/environment/gis/__init__.py,sha256=SjqhVjuDbZln636zOFROq1tWPfadghkuYz8aheflyxA,96
|
13
12
|
physics/environment/gis/base_gis.py,sha256=WJMwpuxjmHuV-dS5HwWvLxARNd7JRQyd3IBptuxNAI0,656
|
14
13
|
physics/environment/gis/gis.py,sha256=I04ABXsNOmeikCajBtl9a5oW6NzMBPc8nG53oiIicqw,11737
|
15
14
|
physics/environment/gis/gis.rs,sha256=jMkVmlUNl5cz7GF1QVkMNoRb58YUOe4D95EdhBJ4anM,876
|
16
15
|
physics/environment/meteorology/__init__.py,sha256=mvjJw_0nNLIdh80F_yTaRC3Sw3oI-z1L0J5cOK_ei0k,157
|
17
16
|
physics/environment/meteorology/base_meteorology.py,sha256=n0JsEXQLuciEatQp_S0QBXd_HQzCY5LeIQeVKgl5O-8,2487
|
18
|
-
physics/environment/meteorology/clouded_meteorology.py,sha256=
|
19
|
-
physics/environment/meteorology/irradiant_meteorology.py,sha256=
|
17
|
+
physics/environment/meteorology/clouded_meteorology.py,sha256=H4jqQmaf1UiARQ4T5HmWE0ChS6IJhlvumj0lfoe52cw,28241
|
18
|
+
physics/environment/meteorology/irradiant_meteorology.py,sha256=BNuINbPfNQO5dB9AE9bMMdN4IUWK5HpRmEXONSykpVc,5646
|
20
19
|
physics/environment/meteorology/meteorology.rs,sha256=a5XlYhb34xvPKuGp1etTQZlSqm9qTd7UXuN6H0-jXfY,5142
|
21
20
|
physics/models/__init__.py,sha256=YgSvt4iBbcoH55XskiK9uE3VXxqCh-ZoIbAWogNUK7U,268
|
22
21
|
physics/models/arrays.rs,sha256=rtthXq7PDjL30lIt8y9L2xFAPJE5o_ltmCbOGzzOxrc,11
|
23
|
-
physics/models/battery.rs,sha256=
|
22
|
+
physics/models/battery.rs,sha256=fTL9O20fQarT_CFsmMSqVEZNe_sTejWMaAR8Fc-z_ak,16
|
24
23
|
physics/models/constants.py,sha256=GMD4hYO1FKoni3MNPvcyYg2EKGrgKxvOnxVKlEapUEc,839
|
25
24
|
physics/models/lvs.rs,sha256=uyJ1ZZ1Phq8cWCzr2aevCWzt8MlhCw9DO5ObUvEs8ds,8
|
26
25
|
physics/models/motor.rs,sha256=Iya1C_YF09KMy-9N-Mt-rBf1EIAs2Bf3Q4eDvyFuAoc,10
|
@@ -29,10 +28,13 @@ physics/models/arrays/__init__.py,sha256=Ds36SXwtCBnoq1xDOVlqA4kMBcOqS1wa9gLTn7T
|
|
29
28
|
physics/models/arrays/arrays.rs,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
29
|
physics/models/arrays/base_array.py,sha256=KjTX_0MCvWSEf4irXd3P1ITsgjhFUqK1Cf0MbSQwLuk,101
|
31
30
|
physics/models/arrays/basic_array.py,sha256=-6yj85XkuySrjnLVmLnU35A4EOatiw4QpkLcC7n1WXU,1371
|
32
|
-
physics/models/battery/__init__.py,sha256=
|
31
|
+
physics/models/battery/__init__.py,sha256=huRYMnfw035kqHyWeApAvdCaSf9yAQZMcQMnPutIXLE,376
|
33
32
|
physics/models/battery/base_battery.py,sha256=yU-QopEEQ83kw4CUvJ2MEhYyj3AM3LYY_hvdZ2wwW7c,1232
|
34
|
-
physics/models/battery/basic_battery.py,sha256=
|
35
|
-
physics/models/battery/battery.rs,sha256=
|
33
|
+
physics/models/battery/basic_battery.py,sha256=5o-7g5xflhNLKuJyeqOY-1rLIOSIy_CJ0U4GEqeQO1E,5894
|
34
|
+
physics/models/battery/battery.rs,sha256=0wIQVli7UOWgKXT96cQLWisLQKo5UE08X4B9dl09USI,3649
|
35
|
+
physics/models/battery/battery_config.py,sha256=Dsi7cXR8SL0v7aSTuihhB6il9-8h1a2P8qrGbdvlf8Q,617
|
36
|
+
physics/models/battery/battery_model.py,sha256=kHn-xOyFBzWycpd-9Wn75fzHYZ512wXOi1dUrlt3a5M,6313
|
37
|
+
physics/models/battery/kalman_filter.py,sha256=Lr7J2vwO1HwN6miNFAYCfhZiZ2j72bwmUt-vubpo0Ks,7371
|
36
38
|
physics/models/lvs/__init__.py,sha256=ZBips6zW4Lot7SkQZMZt_OGRNUqgOfUlDtBA5lfUkM4,114
|
37
39
|
physics/models/lvs/base_lvs.py,sha256=kVLfGd9Qwql4-6u86uwHbJoFCgYpG07r0cAR2Ngsq38,116
|
38
40
|
physics/models/lvs/basic_lvs.py,sha256=xNXeN6RGSZkJLhtcW0n2xZU6YIOT4bKUIbOFdmh4zc0,621
|
@@ -45,8 +47,8 @@ physics/models/regen/__init__.py,sha256=JzyRYKwT89FQ6_p9ofCqusl2fnWGHulyiK4P4f8L
|
|
45
47
|
physics/models/regen/base_regen.py,sha256=lY44jrTSHEo8Xv7hKCjo4C3Jx0PUgilyITHwQchT2bM,101
|
46
48
|
physics/models/regen/basic_regen.py,sha256=RY730lQLJ_gKkm2wJ68t1OPTmcz9xxGmu0yBLwHCGoQ,1811
|
47
49
|
physics/models/regen/regen.rs,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
48
|
-
ubc_solar_physics-1.0.
|
49
|
-
ubc_solar_physics-1.0.
|
50
|
-
ubc_solar_physics-1.0.
|
51
|
-
ubc_solar_physics-1.0.
|
52
|
-
ubc_solar_physics-1.0.
|
50
|
+
ubc_solar_physics-1.2.0.dist-info/LICENSE,sha256=1Vq7OikLHh7N0xsmTPHCmPkOxk1AXrMK9k1a1icQFlk,1087
|
51
|
+
ubc_solar_physics-1.2.0.dist-info/METADATA,sha256=nPuG2LtB0tkAdyZqiH_xYFiVCR4DmG0aFDQwPj-zBEU,5107
|
52
|
+
ubc_solar_physics-1.2.0.dist-info/WHEEL,sha256=agy-BJge3afXwWznUXANATmKFW4eqelqRR0uf608A_0,99
|
53
|
+
ubc_solar_physics-1.2.0.dist-info/top_level.txt,sha256=aws060Zz-1h0Kx76JzcE1gLA_AfS1lrRtTCsyUYwDvM,8
|
54
|
+
ubc_solar_physics-1.2.0.dist-info/RECORD,,
|
physics/environment/race.py
DELETED
@@ -1,89 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This class collects the constants that are related to a specific competition.
|
3
|
-
"""
|
4
|
-
import pathlib
|
5
|
-
|
6
|
-
import numpy as np
|
7
|
-
import pickle
|
8
|
-
import enum
|
9
|
-
import json
|
10
|
-
import os
|
11
|
-
|
12
|
-
|
13
|
-
class Race:
|
14
|
-
class RaceType(enum.Enum):
|
15
|
-
ASC = "ASC"
|
16
|
-
FSGP = "FSGP"
|
17
|
-
|
18
|
-
def __str__(self):
|
19
|
-
match self.value:
|
20
|
-
case "ASC":
|
21
|
-
return "ASC"
|
22
|
-
case "FSGP":
|
23
|
-
return "FSGP"
|
24
|
-
|
25
|
-
def __reduce__(self):
|
26
|
-
return self.__class__, (self.name,)
|
27
|
-
|
28
|
-
def __contains__(self, item):
|
29
|
-
return item == "ASC" or item == "FSGP"
|
30
|
-
|
31
|
-
def __repr__(self):
|
32
|
-
return str(self)
|
33
|
-
|
34
|
-
ASC = RaceType.ASC
|
35
|
-
FSGP = RaceType.FSGP
|
36
|
-
|
37
|
-
def __init__(self, race_type: RaceType, race_constants: dict):
|
38
|
-
self.race_type = race_type
|
39
|
-
|
40
|
-
self.days = race_constants["days"]
|
41
|
-
self.tiling = race_constants["tiling"]
|
42
|
-
self.date = (race_constants["start_year"], race_constants["start_month"], race_constants["start_day"])
|
43
|
-
|
44
|
-
self.race_duration = len(self.days) * 24 * 60 * 60 # Duration (s)
|
45
|
-
self.driving_boolean = self.make_time_boolean("driving")
|
46
|
-
self.charging_boolean = self.make_time_boolean("charging")
|
47
|
-
|
48
|
-
def __str__(self):
|
49
|
-
return str(self.race_type)
|
50
|
-
|
51
|
-
def write(self, race_directory: pathlib.Path):
|
52
|
-
with open(race_directory / f"{str(self.race_type)}.pkl", 'wb') as outfile:
|
53
|
-
pickle.dump(self, outfile, protocol=pickle.HIGHEST_PROTOCOL)
|
54
|
-
|
55
|
-
def make_time_boolean(self, boolean_type: str):
|
56
|
-
boolean: np.ndarray = np.empty(self.race_duration, dtype=np.int8)
|
57
|
-
DAY_LENGTH: int = 24 * 60 * 60 # Length of a day in seconds
|
58
|
-
|
59
|
-
for tick in range(len(boolean)):
|
60
|
-
day: int = tick // DAY_LENGTH # Integer division to determine how many days have passed
|
61
|
-
time_of_day = tick % DAY_LENGTH # Time of day in seconds where 0 is midnight and 43200 is noon
|
62
|
-
begin, end = self.days[str(day)][boolean_type]
|
63
|
-
|
64
|
-
# If the time of day is between the beginning and end, then the boolean is True, else False
|
65
|
-
boolean[tick] = begin <= time_of_day < end
|
66
|
-
|
67
|
-
return boolean
|
68
|
-
|
69
|
-
|
70
|
-
def load_race(race_type: Race.RaceType, race_directory: pathlib.Path) -> Race:
|
71
|
-
with open(race_directory / f"{str(race_type)}.pkl", 'rb') as infile:
|
72
|
-
return pickle.load(infile)
|
73
|
-
|
74
|
-
|
75
|
-
def compile_races(config_directory: pathlib.Path, race_directory: pathlib.Path):
|
76
|
-
fsgp_config_path = os.path.join(config_directory, f"settings_FSGP.json")
|
77
|
-
asc_config_path = os.path.join(config_directory, f"settings_ASC.json")
|
78
|
-
|
79
|
-
with open(fsgp_config_path) as f:
|
80
|
-
fsgp_race_constants = json.load(f)
|
81
|
-
|
82
|
-
with open(asc_config_path) as f:
|
83
|
-
asc_race_constants = json.load(f)
|
84
|
-
|
85
|
-
fsgp = Race(Race.FSGP, fsgp_race_constants)
|
86
|
-
fsgp.write(race_directory)
|
87
|
-
|
88
|
-
asc = Race(Race.ASC, asc_race_constants)
|
89
|
-
asc.write(race_directory)
|
File without changes
|
File without changes
|