ubc-solar-physics 1.6.0__cp310-cp310-win_amd64.whl → 1.7.1__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.
physics/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.6.0'
16
- __version_tuple__ = version_tuple = (1, 6, 0)
15
+ __version__ = version = '1.7.1'
16
+ __version_tuple__ = version_tuple = (1, 7, 1)
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import math
3
- import core
3
+ import physics_rs
4
4
  import numpy as np
5
5
  import sys
6
6
 
@@ -81,7 +81,7 @@ class GIS(BaseGIS):
81
81
  :rtype: np.ndarray
82
82
 
83
83
  """
84
- return core.closest_gis_indices_loop(distances, self.path_distances)
84
+ return physics_rs.closest_gis_indices_loop(distances, self.path_distances)
85
85
 
86
86
  def calculate_driving_speeds(
87
87
  self,
@@ -108,7 +108,7 @@ class GIS(BaseGIS):
108
108
  :return: A simulation-time array of driving speeds in m/s, or an error if there weren't enough
109
109
  laps provided to fill the entire simulation time.
110
110
  """
111
- return core.get_driving_speeds(
111
+ return physics_rs.get_driving_speeds(
112
112
  np.array(average_lap_speeds).astype(np.float64),
113
113
  simulation_dt,
114
114
  np.array(driving_allowed).astype(bool),
@@ -120,7 +120,7 @@ class GIS(BaseGIS):
120
120
  def _python_calculate_closest_gis_indices(distances, path_distances):
121
121
  """
122
122
 
123
- Python implementation of rust core.closest_gis_indices_loop. See parent function for documentation details.
123
+ Python implementation of use_compiled core.closest_gis_indices_loop. See parent function for documentation details.
124
124
 
125
125
  """
126
126
 
@@ -2,7 +2,7 @@ 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
4
  from numba import jit
5
- import core
5
+ import physics_rs
6
6
  from typing import Optional
7
7
  import datetime
8
8
 
@@ -70,7 +70,7 @@ class CloudedMeteorology(BaseMeteorology):
70
70
  # contains the average distance between two consecutive elements in the cumulative_weather_path_distances array
71
71
  average_distances = np.abs(np.diff(cumulative_weather_path_distances) / 2)
72
72
 
73
- return core.closest_weather_indices_loop(cumulative_distances, average_distances)
73
+ return physics_rs.closest_weather_indices_loop(cumulative_distances, average_distances)
74
74
 
75
75
  def temporally_localize(self, unix_timestamps, start_time, tick) -> None:
76
76
  """
@@ -96,7 +96,7 @@ class CloudedMeteorology(BaseMeteorology):
96
96
  :rtype: np.ndarray
97
97
 
98
98
  """
99
- weather_data = core.weather_in_time(unix_timestamps.astype(np.int64), self._weather_indices.astype(np.int64), self._weather_forecast, 4)
99
+ weather_data = physics_rs.weather_in_time(unix_timestamps.astype(np.int64), self._weather_indices.astype(np.int64), self._weather_forecast, 4)
100
100
  # roll_by_tick = int(3600 / tick) * (24 + start_hour - hour_from_unix_timestamp(weather_data[0, 2]))
101
101
  # weather_data = np.roll(weather_data, -roll_by_tick, 0)
102
102
 
@@ -124,7 +124,7 @@ class CloudedMeteorology(BaseMeteorology):
124
124
  :rtype: np.ndarray
125
125
 
126
126
  """
127
- day_of_year, local_time = core.calculate_array_ghi_times(local_times)
127
+ day_of_year, local_time = physics_rs.calculate_array_ghi_times(local_times)
128
128
 
129
129
  ghi = self._calculate_GHI(coords[:, 0], coords[:, 1], time_zones,
130
130
  day_of_year, local_time, elevations, self._cloud_cover)
@@ -1,7 +1,7 @@
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
- import core
4
+ import physics_rs
5
5
  from typing import Optional
6
6
 
7
7
 
@@ -11,6 +11,7 @@ class IrradiantMeteorology(BaseMeteorology):
11
11
  solar irradiance data, but not cloud cover.
12
12
 
13
13
  """
14
+
14
15
  def __init__(self, race, weather_forecasts):
15
16
  self._race = race
16
17
  self._raw_weather_data = weather_forecasts
@@ -53,7 +54,7 @@ class IrradiantMeteorology(BaseMeteorology):
53
54
  # contains the average distance between two consecutive elements in the cumulative_weather_path_distances array
54
55
  average_distances = np.abs(np.diff(cumulative_weather_path_distances) / 2)
55
56
 
56
- self._weather_indices = core.closest_weather_indices_loop(cumulative_distances, average_distances)
57
+ self._weather_indices = physics_rs.closest_weather_indices_loop(cumulative_distances, average_distances)
57
58
 
58
59
  def temporally_localize(self, unix_timestamps, start_time, tick) -> None:
59
60
  """
@@ -76,8 +77,9 @@ class IrradiantMeteorology(BaseMeteorology):
76
77
  :returns: a SolcastEnvironment object with time_dt, latitude, longitude, wind_speed, wind_direction, and ghi.
77
78
  :rtype: SolcastEnvironment
78
79
  """
79
- forecasts_array = core.weather_in_time(unix_timestamps.astype(np.int64), self._weather_indices.astype(np.int64),
80
- self._raw_weather_data, 0)
80
+ forecasts_array = physics_rs.weather_in_time(unix_timestamps.astype(np.int64),
81
+ self._weather_indices.astype(np.int64),
82
+ self._raw_weather_data, 0)
81
83
 
82
84
  self._time_dt = forecasts_array[:, 0]
83
85
  self._latitude = forecasts_array[:, 1]
@@ -103,5 +105,3 @@ class IrradiantMeteorology(BaseMeteorology):
103
105
 
104
106
  """
105
107
  return self.solar_irradiance
106
-
107
-
physics/lib.rs CHANGED
@@ -1,5 +1,5 @@
1
1
  use numpy::ndarray::ArrayViewD;
2
- use numpy::{PyArray, PyArrayDyn, PyReadwriteArrayDyn, PyReadonlyArray1, PyArray1};
2
+ use numpy::{PyArray, PyArrayDyn, PyReadwriteArrayDyn, PyReadwriteArray1, PyReadonlyArray1, PyArray1};
3
3
  use pyo3::prelude::*;
4
4
  use pyo3::types::PyModule;
5
5
 
@@ -7,7 +7,7 @@ pub mod environment;
7
7
  pub mod models;
8
8
  use crate::environment::gis::gis::{rust_closest_gis_indices_loop, get_driving_speeds};
9
9
  use crate::environment::meteorology::meteorology::{rust_calculate_array_ghi_times, rust_closest_weather_indices_loop, rust_weather_in_time};
10
- use crate::models::battery::battery::update_battery_array;
10
+ use crate::models::battery::battery::update_battery_state;
11
11
 
12
12
  fn constrain_speeds(speed_limits: ArrayViewD<f64>, speeds: ArrayViewD<f64>, tick: i32) -> Vec<f64> {
13
13
  let mut distance: f64 = 0.0;
@@ -25,7 +25,7 @@ fn constrain_speeds(speed_limits: ArrayViewD<f64>, speeds: ArrayViewD<f64>, tic
25
25
 
26
26
  /// A Python module implemented in Rust. The name of this function is the Rust module name!
27
27
  #[pymodule]
28
- #[pyo3(name = "core")]
28
+ #[pyo3(name = "physics_rs")]
29
29
  fn rust_simulation(_py: Python, m: &PyModule) -> PyResult<()> {
30
30
  #[pyfn(m)]
31
31
  #[pyo3(name = "constrain_speeds")]
@@ -95,35 +95,43 @@ fn rust_simulation(_py: Python, m: &PyModule) -> PyResult<()> {
95
95
  }
96
96
 
97
97
  #[pyfn(m)]
98
- #[pyo3(name = "update_battery_array")]
99
- fn update_battery_array_py<'py>(
98
+ #[pyo3(name = "update_battery_state")]
99
+ fn update_battery_state_py<'py>(
100
100
  py: Python<'py>,
101
- python_delta_energy_array: PyReadwriteArrayDyn<'py, f64>,
101
+ python_energy_or_current_array: PyReadwriteArray1<'py, f64>,
102
102
  time_step: f64,
103
103
  initial_state_of_charge: f64,
104
104
  initial_polarization_potential: f64,
105
- polarization_resistance: f64,
106
- python_internal_resistance_coeffs: PyReadwriteArrayDyn<'py, f64>,
107
- python_open_circuit_voltage_coeffs: PyReadwriteArrayDyn<'py, f64>,
108
- time_constant: f64,
105
+ python_internal_resistance_lookup: PyReadwriteArray1<'py, f64>,
106
+ python_open_circuit_voltage_lookup: PyReadwriteArray1<'py, f64>,
107
+ python_polarization_resistance_lookup: PyReadwriteArray1<'py, f64>,
108
+ python_polarization_capacitance_lookup: PyReadwriteArray1<'py, f64>,
109
109
  nominal_charge_capacity: f64,
110
- ) -> (&'py PyArrayDyn<f64>, &'py PyArrayDyn<f64>) {
111
- let delta_energy_array = python_delta_energy_array.as_array();
112
- let internal_resistance_coeffs = python_internal_resistance_coeffs.as_array();
113
- let open_circuit_voltage_coeffs = python_open_circuit_voltage_coeffs.as_array();
114
- let (soc_array, voltage_array): (Vec<f64>, Vec<f64>) = update_battery_array(
115
- delta_energy_array,
110
+ is_power: bool,
111
+ quantization_step: f64,
112
+ min_soc: f64,
113
+ ) -> (&'py PyArray1<f64>, &'py PyArray1<f64>) {
114
+ let energy_or_current_array = python_energy_or_current_array.as_array();
115
+ let internal_resistance_lookup = python_internal_resistance_lookup.as_array();
116
+ let open_circuit_voltage_lookup = python_open_circuit_voltage_lookup.as_array();
117
+ let polarization_resistance_lookup = python_polarization_resistance_lookup.as_array();
118
+ let polarization_capacitance_lookup = python_polarization_capacitance_lookup.as_array();
119
+ let (soc_array, voltage_array): (Vec<f64>, Vec<f64>) = update_battery_state(
120
+ energy_or_current_array,
116
121
  time_step,
117
122
  initial_state_of_charge,
118
123
  initial_polarization_potential,
119
- polarization_resistance,
120
- internal_resistance_coeffs,
121
- open_circuit_voltage_coeffs,
122
- time_constant,
124
+ internal_resistance_lookup,
125
+ open_circuit_voltage_lookup,
126
+ polarization_resistance_lookup,
127
+ polarization_capacitance_lookup,
123
128
  nominal_charge_capacity,
129
+ is_power,
130
+ quantization_step,
131
+ min_soc
124
132
  );
125
- let py_soc_array = PyArray::from_vec(py, soc_array).to_dyn();
126
- let py_voltage_array = PyArray::from_vec(py, voltage_array).to_dyn();
133
+ let py_soc_array = PyArray::from_vec(py, soc_array);
134
+ let py_voltage_array = PyArray::from_vec(py, voltage_array);
127
135
  (py_soc_array, py_voltage_array)
128
136
  }
129
137
 
@@ -1,14 +1,18 @@
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
+ from .battery_model import EquivalentCircuitBatteryModel, EquivalentCircuitModelConfig, SOCDependent
4
+ from .kalman_filter import FilteredBatteryModel, FilteredBatteryModelConfig
5
+ from .battery_config import BatteryModelConfig, load_battery_config, KalmanFilterConfig
6
6
 
7
7
  __all__ = [
8
8
  "BaseBattery",
9
9
  "BasicBattery",
10
- "BatteryModel",
11
- "EKF_SOC",
10
+ "EquivalentCircuitBatteryModel",
11
+ "FilteredBatteryModel",
12
12
  "BatteryModelConfig",
13
- "load_battery_config"
13
+ "load_battery_config",
14
+ "EquivalentCircuitModelConfig",
15
+ "FilteredBatteryModelConfig",
16
+ "KalmanFilterConfig",
17
+ "SOCDependent"
14
18
  ]
@@ -1,14 +1,23 @@
1
1
  use std::f64;
2
- use numpy::ndarray::ArrayViewD;
2
+ use numpy::ndarray::{ArrayView1};
3
+
4
+ fn get_lookup_index(soc: f64, quantization_step: f64, num_indices: usize, min_soc: f64) -> usize {
5
+ // Apply the same formula as in Python
6
+ let index = ((soc - min_soc) / quantization_step).floor() as usize;
7
+
8
+ // Clamp the index to be between 0 and num_indices - 1
9
+ index.min(num_indices - 1) // equivalent to max(0, min(num_indices - 1, index))
10
+ }
3
11
 
4
12
  /// 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)
13
+ fn evaluate_lookup(lookup: &[f64], quantization_step: f64, value: f64, min_soc: f64) -> f64 {
14
+ let index = get_lookup_index(value, quantization_step, lookup.len(), min_soc);
15
+ lookup[index]
7
16
  }
8
17
 
9
18
  /// Evolve the battery state for a single step
10
19
  fn battery_evolve(
11
- power: f64, // Watts
20
+ current: f64, // Amperes
12
21
  tick: f64, // Seconds
13
22
  state_of_charge: f64, // Dimensionless, 0 < SOC < 1
14
23
  polarization_potential: f64, // Volts
@@ -18,9 +27,6 @@ fn battery_evolve(
18
27
  time_constant: f64, // Seconds
19
28
  nominal_charge_capacity: f64, // Nominal charge capacity (Coulombs)
20
29
  ) -> (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
30
  // Update state of charge and polarization potential
25
31
  let new_state_of_charge: f64 = state_of_charge + (current * tick / nominal_charge_capacity);
26
32
  let new_polarization_potential: f64 = f64::exp(-tick / time_constant) * polarization_potential
@@ -31,29 +37,48 @@ fn battery_evolve(
31
37
  (new_state_of_charge, new_polarization_potential, terminal_voltage)
32
38
  }
33
39
 
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
40
+ // Update battery state, using either energy or current draw
41
+ pub fn update_battery_state(
42
+ energy_or_current_array: ArrayView1<'_, f64>, // Power (W*s) or current (Amperes)
43
+ tick: f64, // Seconds
44
+ initial_state_of_charge: f64, // dimensionless, 0 < SOC < 1
45
+ initial_polarization_potential: f64, // Volts
46
+ internal_resistance_lookup: ArrayView1<'_, f64>,// Coefficients for internal resistance
47
+ open_circuit_voltage_lookup: ArrayView1<'_, f64>, // Coefficients for open-circuit voltage
48
+ polarization_resistance_lookup: ArrayView1<'_, f64>, // Coefficients for polarization resistance
49
+ capacitance_lookup: ArrayView1<'_, f64>, // Coefficients for polarization capacitance
50
+ nominal_charge_capacity: f64, // Coulombs
51
+ is_energy_input: bool, // Whether the input is power or current,
52
+ quantization_step: f64, // The quantization step size of SOC for lookup tables
53
+ min_soc: f64,
54
+
44
55
  ) -> (Vec<f64>, Vec<f64>) {
45
- let mut state_of_charge: f64 = initial_state_of_charge;
56
+ let mut state_of_charge: f64 = initial_state_of_charge;
46
57
  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());
58
+ let mut soc_array: Vec<f64> = Vec::with_capacity(energy_or_current_array.len());
59
+ let mut voltage_array: Vec<f64> = Vec::with_capacity(energy_or_current_array.len());
49
60
 
50
- for &power in delta_energy_array.iter() {
61
+ for &input in energy_or_current_array.iter() {
51
62
  // 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);
63
+ let open_circuit_voltage = evaluate_lookup(open_circuit_voltage_lookup.as_slice().unwrap(), quantization_step, state_of_charge, min_soc);
64
+ let internal_resistance = evaluate_lookup(internal_resistance_lookup.as_slice().unwrap(), quantization_step, state_of_charge, min_soc);
65
+ let polarization_resistance = evaluate_lookup(polarization_resistance_lookup.as_slice().unwrap(), quantization_step, state_of_charge, min_soc);
66
+ let capacitance = evaluate_lookup(capacitance_lookup.as_slice().unwrap(), quantization_step, state_of_charge, min_soc);
67
+ let time_constant = polarization_resistance * capacitance;
68
+
69
+ // Calculate current from power or use the current directly
70
+ let current: f64 = if is_energy_input {
71
+ // Use the last voltage to calculate current, or an absurdly large number if it is the
72
+ // first, because we don't know voltage yet, so we will have a very small initial
73
+ // current, no matter what. We shouldn't be starting to simulate when the battery is
74
+ // in an active state anyway, so this should be an alright compromise.
75
+ input / (tick * voltage_array.last().unwrap_or(&10000.0)) // I = (E / dt) / V
76
+ } else {
77
+ input // Current is directly given in the current input array
78
+ };
54
79
 
55
80
  let (new_state_of_charge, new_polarization_potential, terminal_voltage) = battery_evolve(
56
- power,
81
+ current,
57
82
  tick,
58
83
  state_of_charge,
59
84
  polarization_potential,
@@ -75,4 +100,3 @@ pub fn update_battery_array(
75
100
 
76
101
  (soc_array, voltage_array)
77
102
  }
78
-
@@ -1,22 +1,107 @@
1
- import tomli
2
- from pydantic import BaseModel
3
- from typing import List
4
- import os
1
+ import tomli as tomllib
5
2
  import pathlib
3
+ from scipy import optimize
4
+ import numpy as np
5
+ from physics.models.battery import SOCDependent
6
+ from typing import cast
7
+ from numpy.typing import NDArray
6
8
 
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:
9
+
10
+ class BatteryModelConfig:
11
+ """
12
+ A concrete implementation of the `EquivalentCircuitModelConfig` protocol.
13
+
14
+ This implementation fits values of U_oc, R_0, R_P, and C_P at various state-of-charge (SOC) values
15
+ to a seventh degree polynomial to generate a smooth function mapping SOC to each battery parameter.
16
+
17
+ For example, R_0 = R_0_data[i] when Soc = Soc_data[i].
18
+ """
19
+ def __init__(self, R_0_data, Soc_data, R_P_data, C_P_data, Uoc_data, Q_total):
20
+ # ----- Initialize Parameters -----
21
+ def quintic_polynomial(x, x0, x1, x2, x3, x4, x5, x6, x7):
22
+ return np.polyval(np.array([x0, x1, x2, x3, x4, x5, x6, x7]), x)
23
+
24
+ self._U_oc_coefficients, _ = optimize.curve_fit(quintic_polynomial, Soc_data, Uoc_data)
25
+ self._R_0_coefficients, _ = optimize.curve_fit(quintic_polynomial, Soc_data, R_0_data)
26
+ self._C_P_coefficients, _ = optimize.curve_fit(quintic_polynomial, Soc_data, C_P_data)
27
+ self._R_P_coefficients, _ = optimize.curve_fit(quintic_polynomial, Soc_data, R_P_data)
28
+
29
+ # Casts are just for the type-checker to know that np.polyval will work as SOCDependent
30
+ self._U_oc: SOCDependent = cast(SOCDependent, lambda soc: np.polyval(self._U_oc_coefficients, soc)) # V
31
+ self._R_0: SOCDependent = cast(SOCDependent, lambda soc: np.polyval(self._R_0_coefficients, soc)) # Ohms
32
+ self._R_P: SOCDependent = cast(SOCDependent, lambda soc: np.polyval(self._R_P_coefficients, soc)) # Ohms
33
+ self._C_P: SOCDependent = cast(SOCDependent, lambda soc: np.polyval(self._C_P_coefficients, soc)) # Farads
34
+
35
+ self._Q_total = Q_total
36
+
37
+ @property
38
+ def get_Uoc(self) -> SOCDependent:
39
+ return self._U_oc
40
+
41
+ @property
42
+ def get_R_0(self) -> SOCDependent:
43
+ return self._R_0
44
+
45
+ @property
46
+ def get_R_P(self) -> SOCDependent:
47
+ return self._R_P
48
+
49
+ @property
50
+ def get_C_P(self) -> SOCDependent:
51
+ return self._C_P
52
+
53
+ @property
54
+ def Q_total(self) -> float:
55
+ return self._Q_total
56
+
57
+
58
+ class KalmanFilterConfig:
59
+ def __init__(
60
+ self,
61
+ battery_model_config: BatteryModelConfig,
62
+ process_noise_matrix: NDArray,
63
+ state_covariance_matrix: NDArray,
64
+ measurement_noise_vector: NDArray
65
+ ):
66
+ self._battery_model_config = battery_model_config
67
+ self._process_noise_matrix = process_noise_matrix
68
+ self._state_covariance_matrix = state_covariance_matrix
69
+ self._measurement_noise_vector = measurement_noise_vector
70
+
71
+ @property
72
+ def battery_model_config(self) -> BatteryModelConfig:
73
+ """
74
+ Configuration of the underlying `EquivalentCircuitModel`.
75
+ """
76
+ return self._battery_model_config
77
+
78
+ @property
79
+ def process_noise_matrix(self) -> NDArray[float]:
80
+ """
81
+ A 2x2 matrix containing the process noise covariance matrix where [0, 0] is the SOC evolution
82
+ noise and [1, 1] is the polarization potential evolution noise.
83
+ """
84
+ return self._process_noise_matrix
85
+
86
+ @property
87
+ def state_covariance_matrix(self) -> NDArray[float]:
88
+ """
89
+ A 2x2 matrix containing the state covariance matrix where [0, 0] is the SOC covariance
90
+ noise and [1, 1] is the polarization potential covariance.
91
+ """
92
+ return self._state_covariance_matrix
93
+
94
+ @property
95
+ def measurement_noise_vector(self) -> NDArray[float]:
96
+ """
97
+ A 1x1 vector containing the noise expected in the terminal voltage measurement.
98
+ """
99
+ return self._measurement_noise_vector
100
+
101
+
102
+ def load_battery_config(absolute_path: str | pathlib.Path) -> BatteryModelConfig:
18
103
  # Build the full path to the config file
19
104
  full_path = pathlib.Path(absolute_path)
20
105
  with open(full_path, 'rb') as f:
21
- data = tomli.load(f)
22
- return BatteryModelConfig.model_validate(data)
106
+ data = tomllib.load(f)
107
+ return BatteryModelConfig(**data)
@@ -1,8 +1,6 @@
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
1
+ R_0_data = [0.17953765302439662, 0.15580951404728172, 0.14176929930784543, 0.11043950958574644, 0.13930042505446938, 0.1552885289394773, 0.044070982259896085, 0.2208806896239539, 0.15116267852908616, 0.6553961767519164]
2
+ R_P_data = [0.04153180244191346, 0.10674683402208612, 0.061085424180509884, 0.0781407642082238, 0.05537901113775878, 0.09732054673529467, 0.07662520885708152, 0.09799857401036915, 0.42622740149661487, 0.2718418915736874]
3
+ C_P_data = [14824.398495212006, 1587.5971318119796, 341.1064063616048, 1243.182413110655, 619.5791066439332, 2252.7885790042164, 954.5884882581622, 515.7219779825028, 431.10892633451135, 195.14394897766627]
4
+ Uoc_data = [131.88002282453857, 129.4574321366064, 125.5750277614186, 121.99586066440303, 118.69893412178982, 115.71854177322408, 111.99025635444923, 108.29354777060836, 98.23397960300946, 95.24125831782388]
5
+ Q_total = 151000.0
6
+ Soc_data = [1.0000113624123392, 0.8815263722745977, 0.7671918526292492, 0.6206071038045673, 0.4911613638651783, 0.3606311083423134, 0.23687514228021178, 0.12073345089992571, 0.01456057818183809, 0.0070648691224265425]