ubc-solar-physics 1.6.0__tar.gz → 1.7.0__tar.gz
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.
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/Cargo.lock +10 -10
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/Cargo.toml +2 -2
- ubc_solar_physics-1.7.0/MANIFEST.in +1 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/PKG-INFO +2 -1
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/models/battery.rst +12 -2
- ubc_solar_physics-1.7.0/examples/battery_model_examples/battery_config.toml +6 -0
- ubc_solar_physics-1.7.0/examples/battery_model_examples/battery_model_example.py +66 -0
- ubc_solar_physics-1.7.0/examples/kalman_filter_examples/battery_config.toml +6 -0
- ubc_solar_physics-1.7.0/examples/kalman_filter_examples/kalman_filter_example.py +96 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/_version.py +2 -2
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/gis/gis.py +4 -4
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/clouded_meteorology.py +4 -4
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/irradiant_meteorology.py +6 -6
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/lib.rs +30 -22
- ubc_solar_physics-1.7.0/physics/models/battery/__init__.py +18 -0
- ubc_solar_physics-1.7.0/physics/models/battery/battery.rs +102 -0
- ubc_solar_physics-1.7.0/physics/models/battery/battery_config.py +107 -0
- ubc_solar_physics-1.7.0/physics/models/battery/battery_config.toml +6 -0
- ubc_solar_physics-1.7.0/physics/models/battery/battery_model.py +226 -0
- ubc_solar_physics-1.7.0/physics/models/battery/kalman_filter.py +223 -0
- ubc_solar_physics-1.7.0/physics_rs/__init__.pyi +111 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/pyproject.toml +4 -3
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/tests/gis_tests/test_calculate_driving_speeds.py +6 -6
- ubc_solar_physics-1.7.0/tests/kalman_filter_tests/test_basic_kalman_filter.py +72 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/ubc_solar_physics.egg-info/PKG-INFO +2 -1
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/ubc_solar_physics.egg-info/SOURCES.txt +4 -4
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/ubc_solar_physics.egg-info/requires.txt +1 -0
- ubc_solar_physics-1.7.0/ubc_solar_physics.egg-info/top_level.txt +2 -0
- ubc_solar_physics-1.6.0/.github/PULL_REQUEST_TEMPLATE.md +0 -24
- ubc_solar_physics-1.6.0/examples/battery_model_examples/battery_model_example.py +0 -46
- ubc_solar_physics-1.6.0/examples/kalman_filter_examples/battery_config.toml +0 -8
- ubc_solar_physics-1.6.0/examples/kalman_filter_examples/kalman_filter_example.py +0 -97
- ubc_solar_physics-1.6.0/physics/models/battery/__init__.py +0 -14
- ubc_solar_physics-1.6.0/physics/models/battery/battery.rs +0 -78
- ubc_solar_physics-1.6.0/physics/models/battery/battery_config.py +0 -22
- ubc_solar_physics-1.6.0/physics/models/battery/battery_config.toml +0 -8
- ubc_solar_physics-1.6.0/physics/models/battery/battery_model.py +0 -135
- ubc_solar_physics-1.6.0/physics/models/battery/kalman_filter.py +0 -341
- ubc_solar_physics-1.6.0/tests/battery_config.toml +0 -8
- ubc_solar_physics-1.6.0/tests/kalman_filter_tests/test_basic_kalman_filter.py +0 -46
- ubc_solar_physics-1.6.0/tests/kalman_filter_tests/test_filter_real_data.py +0 -104
- ubc_solar_physics-1.6.0/ubc_solar_physics.egg-info/top_level.txt +0 -1
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/.gitattributes +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/.github/workflows/build_and_publish.yaml +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/.github/workflows/run_tests.yaml +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/.gitignore +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/.readthedocs.yaml +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/LICENSE +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/README.md +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/data.png +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/Makefile +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/_generate_version.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/docs_requirements.txt +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/make.bat +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/README.md +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/api.rst +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/conf.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/environment/gis.rst +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/environment/index.rst +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/environment/meteorology.rst +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/index.rst +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/models/arrays.rst +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/models/index.rst +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/models/lvs.rst +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/models/motor.rst +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/docs/source/models/regen.rst +0 -0
- {ubc_solar_physics-1.6.0/examples/data_query → ubc_solar_physics-1.7.0/examples/battery_model_examples}/current.csv +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/examples/battery_model_examples/data_py.png +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/examples/battery_model_examples/data_rust.png +0 -0
- {ubc_solar_physics-1.6.0/examples/data_query → ubc_solar_physics-1.7.0/examples/battery_model_examples}/voltage.csv +0 -0
- {ubc_solar_physics-1.6.0/examples/kalman_filter_examples → ubc_solar_physics-1.7.0/examples/data_query}/current.csv +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/examples/data_query/poetry.lock +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/examples/data_query/pyproject.toml +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/examples/data_query/query_data.py +0 -0
- {ubc_solar_physics-1.6.0/examples/kalman_filter_examples → ubc_solar_physics-1.7.0/examples/data_query}/voltage.csv +0 -0
- {ubc_solar_physics-1.6.0/tests/kalman_filter_tests → ubc_solar_physics-1.7.0/examples/kalman_filter_examples}/current.csv +0 -0
- {ubc_solar_physics-1.6.0/tests/kalman_filter_tests → ubc_solar_physics-1.7.0/examples/kalman_filter_examples}/voltage.csv +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/__init__.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/__init__.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/environment.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/gis/__init__.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/gis/base_gis.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/gis/gis.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/gis.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/__init__.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/base_meteorology.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/meteorology.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/environment.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/__init__.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/arrays/__init__.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/arrays/arrays.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/arrays/base_array.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/arrays/basic_array.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/arrays.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/battery/base_battery.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/battery/basic_battery.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/battery.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/constants.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/lvs/__init__.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/lvs/base_lvs.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/lvs/basic_lvs.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/lvs/lvs.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/lvs.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/motor/__init__.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/motor/advanced_motor.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/motor/base_motor.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/motor/basic_motor.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/motor/motor.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/motor.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/regen/__init__.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/regen/base_regen.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/regen/basic_regen.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/regen/regen.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models/regen.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/physics/models.rs +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/setup.cfg +0 -0
- {ubc_solar_physics-1.6.0/examples/battery_model_examples → ubc_solar_physics-1.7.0/tests}/battery_config.toml +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/tests/gis_tests/test_gis_driving_speeds.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/tests/test_versioning.py +0 -0
- {ubc_solar_physics-1.6.0 → ubc_solar_physics-1.7.0}/ubc_solar_physics.egg-info/dependency_links.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
# This file is automatically @generated by Cargo.
|
2
2
|
# It is not intended for manual editing.
|
3
|
-
version =
|
3
|
+
version = 4
|
4
4
|
|
5
5
|
[[package]]
|
6
6
|
name = "android-tzdata"
|
@@ -64,15 +64,6 @@ dependencies = [
|
|
64
64
|
"windows-targets",
|
65
65
|
]
|
66
66
|
|
67
|
-
[[package]]
|
68
|
-
name = "core"
|
69
|
-
version = "0.1.0"
|
70
|
-
dependencies = [
|
71
|
-
"chrono",
|
72
|
-
"numpy",
|
73
|
-
"pyo3",
|
74
|
-
]
|
75
|
-
|
76
67
|
[[package]]
|
77
68
|
name = "core-foundation-sys"
|
78
69
|
version = "0.8.7"
|
@@ -248,6 +239,15 @@ dependencies = [
|
|
248
239
|
"windows-targets",
|
249
240
|
]
|
250
241
|
|
242
|
+
[[package]]
|
243
|
+
name = "physics_rs"
|
244
|
+
version = "0.1.0"
|
245
|
+
dependencies = [
|
246
|
+
"chrono",
|
247
|
+
"numpy",
|
248
|
+
"pyo3",
|
249
|
+
]
|
250
|
+
|
251
251
|
[[package]]
|
252
252
|
name = "proc-macro2"
|
253
253
|
version = "1.0.86"
|
@@ -0,0 +1 @@
|
|
1
|
+
include physics_rs.pyi
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ubc-solar-physics
|
3
|
-
Version: 1.
|
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
|
|
@@ -21,7 +21,17 @@ Battery
|
|
21
21
|
:undoc-members:
|
22
22
|
:show-inheritance:
|
23
23
|
|
24
|
-
.. autoclass:: physics.models.battery.
|
24
|
+
.. autoclass:: physics.models.battery.KalmanFilterConfig
|
25
25
|
:members:
|
26
26
|
:undoc-members:
|
27
|
-
:show-inheritance:
|
27
|
+
:show-inheritance:
|
28
|
+
|
29
|
+
.. autoclass:: physics.models.battery.EquivalentCircuitBatteryModel
|
30
|
+
:members:
|
31
|
+
:undoc-members:
|
32
|
+
:show-inheritance:
|
33
|
+
|
34
|
+
.. autoclass:: physics.models.battery.FilteredBatteryModel
|
35
|
+
:members:
|
36
|
+
:undoc-members:
|
37
|
+
:show-inheritance:
|
@@ -0,0 +1,6 @@
|
|
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 = 150000.0
|
6
|
+
Soc_data = [1.0000113624123392, 0.8815263722745977, 0.7671918526292492, 0.6206071038045673, 0.4911613638651783, 0.3606311083423134, 0.23687514228021178, 0.12073345089992571, 0.01456057818183809, 0.0070648691224265425]
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import pandas as pd
|
3
|
+
import pathlib
|
4
|
+
import matplotlib.pyplot as plt
|
5
|
+
from physics.models.battery import EquivalentCircuitBatteryModel, BatteryModelConfig, load_battery_config
|
6
|
+
|
7
|
+
|
8
|
+
# This test requires a voltage.csv and current.csv in the same directory to run
|
9
|
+
def csv_to_timeseries_tuples(csv_file):
|
10
|
+
path = pathlib.Path(__file__).parent / csv_file
|
11
|
+
df = pd.read_csv(path)
|
12
|
+
df['Time'] = pd.to_datetime(df['Time'])
|
13
|
+
return np.array(list(zip(df['Time'].dt.to_pydatetime(), df['Value'])))
|
14
|
+
|
15
|
+
|
16
|
+
def plot_results(soc_array, predicted_ut_array, voltage_data, window=None):
|
17
|
+
fig, ax = plt.subplots()
|
18
|
+
|
19
|
+
if window is None:
|
20
|
+
window = slice(0, len(predicted_ut_array), 1)
|
21
|
+
|
22
|
+
ax.plot(predicted_ut_array, label=r"Predicted $U_t$", color="tab:red")
|
23
|
+
ax.plot(voltage_data, label=r"Measured $U_t$", color="tab:orange")
|
24
|
+
ax.set_ylim(75, 140)
|
25
|
+
ax.set_xticks([])
|
26
|
+
ax.set_ylabel("Voltage")
|
27
|
+
|
28
|
+
ax2 = ax.twinx()
|
29
|
+
ax2.plot(soc_array[window], color="tab:blue", label="Filtered SOC")
|
30
|
+
ax2.set_ylabel("SOC")
|
31
|
+
|
32
|
+
ax.legend(loc='upper right')
|
33
|
+
ax2.legend(loc='lower left')
|
34
|
+
|
35
|
+
plt.title("Simulation of first-order Thevenin equivalent battery model")
|
36
|
+
plt.show()
|
37
|
+
|
38
|
+
|
39
|
+
def battery_model():
|
40
|
+
voltage_data = csv_to_timeseries_tuples('voltage.csv')
|
41
|
+
current_data = csv_to_timeseries_tuples('current.csv')
|
42
|
+
|
43
|
+
# This dataset has 0.1s period between measurements
|
44
|
+
time_difference = 0.1
|
45
|
+
|
46
|
+
current_raw = current_data[:, 1]
|
47
|
+
current_error = np.polyval([-0.00388, 1547], current_raw * 1000.0)
|
48
|
+
|
49
|
+
current = current_raw - (current_error / 1000)
|
50
|
+
voltage = voltage_data[:, 1]
|
51
|
+
|
52
|
+
energy_array = current * voltage * time_difference
|
53
|
+
|
54
|
+
model_config: BatteryModelConfig = load_battery_config(pathlib.Path(__file__).parent / 'battery_config.toml')
|
55
|
+
|
56
|
+
battery_model = EquivalentCircuitBatteryModel(model_config, state_of_charge=1.04)
|
57
|
+
|
58
|
+
soc_array, predicted_ut_array = battery_model.update_array(tick=time_difference, current_array=np.array(-current, dtype=float))
|
59
|
+
# soc_array, predicted_ut_array = battery_model.update_array(tick=time_difference, delta_energy_array=np.array(-energy_array, dtype=float))
|
60
|
+
|
61
|
+
# example usage
|
62
|
+
plot_results(soc_array, predicted_ut_array, voltage)
|
63
|
+
|
64
|
+
|
65
|
+
if __name__ == '__main__':
|
66
|
+
battery_model()
|
@@ -0,0 +1,6 @@
|
|
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 = 145000.0
|
6
|
+
Soc_data = [1.0000113624123392, 0.8815263722745977, 0.7671918526292492, 0.6206071038045673, 0.4911613638651783, 0.3606311083423134, 0.23687514228021178, 0.12073345089992571, 0.01456057818183809, 0.0070648691224265425]
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import pandas as pd
|
3
|
+
import pathlib
|
4
|
+
import matplotlib.pyplot as plt
|
5
|
+
from physics.models.battery.kalman_filter import FilteredBatteryModel
|
6
|
+
from physics.models.battery.battery_config import BatteryModelConfig, load_battery_config, KalmanFilterConfig
|
7
|
+
|
8
|
+
|
9
|
+
# This test requires a voltage.csv and current.csv in the same directory to run
|
10
|
+
def csv_to_timeseries_tuples(csv_file):
|
11
|
+
path = pathlib.Path(__file__).parent / csv_file
|
12
|
+
df = pd.read_csv(path)
|
13
|
+
df['Time'] = pd.to_datetime(df['Time'])
|
14
|
+
return np.array(list(zip(df['Time'].dt.to_pydatetime(), df['Value'])))
|
15
|
+
|
16
|
+
|
17
|
+
def plot_kalman_results(measured_Ut, predicted_Ut_array, predicted_Uc_array, SOC_array, window=None):
|
18
|
+
fig, ax = plt.subplots()
|
19
|
+
|
20
|
+
if window is None:
|
21
|
+
window = slice(0, len(predicted_Ut_array), 1)
|
22
|
+
|
23
|
+
# ax.plot(predicted_Uoc_array[window], label="Predicted OCV", color="tab:cyan")
|
24
|
+
ax.plot(predicted_Ut_array[window], label=r"Filtered Predicted $U_t$", color="orange")
|
25
|
+
ax.plot(measured_Ut[window], label=r"Measured $U_t$", color="tab:red")
|
26
|
+
ax.plot(predicted_Uc_array[window] + 100, label=r"Predicted $U_c$", color="magenta")
|
27
|
+
ax.axhline(y=100, linestyle='dotted', color="magenta")
|
28
|
+
# ax.set_ylim(75, 140)
|
29
|
+
ax.set_xticks([])
|
30
|
+
ax.set_ylabel("Voltage")
|
31
|
+
|
32
|
+
ax2 = ax.twinx()
|
33
|
+
ax2.plot(SOC_array[window], color="tab:cyan", label="Filtered SOC")
|
34
|
+
ax2.set_ylabel("SOC")
|
35
|
+
|
36
|
+
ax.legend(loc='upper right')
|
37
|
+
ax2.legend(loc='lower left')
|
38
|
+
|
39
|
+
plt.title("Impact of Kalman filtering on equivalent-circuit battery modeling ")
|
40
|
+
plt.show()
|
41
|
+
|
42
|
+
|
43
|
+
def kalman_filter():
|
44
|
+
|
45
|
+
voltage_data = csv_to_timeseries_tuples('voltage.csv')
|
46
|
+
current_data = csv_to_timeseries_tuples('current.csv')
|
47
|
+
|
48
|
+
model_config: BatteryModelConfig = load_battery_config(pathlib.Path(__file__).parent / 'battery_config.toml')
|
49
|
+
|
50
|
+
filter_config: KalmanFilterConfig = KalmanFilterConfig(
|
51
|
+
model_config,
|
52
|
+
process_noise_matrix=np.diag([
|
53
|
+
1e-10 * 0.1,
|
54
|
+
1e-6 * 0.1
|
55
|
+
]),
|
56
|
+
state_covariance_matrix=np.diag(
|
57
|
+
[1e-2 * 0.5,
|
58
|
+
1e-1]
|
59
|
+
),
|
60
|
+
measurement_noise_vector=np.eye(1, dtype=float) * 1e0 * 0.5
|
61
|
+
)
|
62
|
+
|
63
|
+
ekf = FilteredBatteryModel(filter_config, initial_SOC=1.04, initial_Uc=0.0)
|
64
|
+
|
65
|
+
SOC_array = np.zeros(len(voltage_data))
|
66
|
+
Ut_array = np.zeros(len(voltage_data))
|
67
|
+
current_array = np.zeros(len(voltage_data))
|
68
|
+
predicted_Ut_array = np.zeros(len(voltage_data))
|
69
|
+
predicted_Uc_array = np.zeros(len(voltage_data))
|
70
|
+
predicted_Uoc_array = np.zeros(len(voltage_data))
|
71
|
+
|
72
|
+
# This dataset has 0.1s period between measurements
|
73
|
+
time_difference = 0.1
|
74
|
+
|
75
|
+
for i in range(int(len(voltage_data))):
|
76
|
+
# for i in range(20000):
|
77
|
+
# Calculate time difference between current and previous measurements
|
78
|
+
|
79
|
+
Ut = float(voltage_data[i][1])
|
80
|
+
I = float(current_data[i][1])
|
81
|
+
|
82
|
+
ekf.predict_then_update(Ut, I, time_difference)
|
83
|
+
|
84
|
+
SOC_array[i] = ekf.SOC
|
85
|
+
Ut_array[i] = Ut
|
86
|
+
current_array[i] = I
|
87
|
+
predicted_Ut_array[i] = ekf.Ut
|
88
|
+
predicted_Uc_array[i] = ekf.Uc
|
89
|
+
predicted_Uoc_array[i] = ekf._U_oc(ekf.SOC)
|
90
|
+
|
91
|
+
# example usage
|
92
|
+
plot_kalman_results(voltage_data[:, 1], predicted_Ut_array, predicted_Uc_array, SOC_array)
|
93
|
+
|
94
|
+
|
95
|
+
if __name__ == '__main__':
|
96
|
+
kalman_filter()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
import math
|
3
|
-
import
|
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
|
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
|
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
|
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
|
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
|
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 =
|
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 =
|
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
|
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 =
|
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 =
|
80
|
-
|
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
|
-
|
@@ -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::
|
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 = "
|
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 = "
|
99
|
-
fn
|
98
|
+
#[pyo3(name = "update_battery_state")]
|
99
|
+
fn update_battery_state_py<'py>(
|
100
100
|
py: Python<'py>,
|
101
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
let
|
115
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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)
|
126
|
-
let py_voltage_array = PyArray::from_vec(py, voltage_array)
|
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
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from .base_battery import BaseBattery
|
2
|
+
from .basic_battery import BasicBattery
|
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
|
+
|
7
|
+
__all__ = [
|
8
|
+
"BaseBattery",
|
9
|
+
"BasicBattery",
|
10
|
+
"EquivalentCircuitBatteryModel",
|
11
|
+
"FilteredBatteryModel",
|
12
|
+
"BatteryModelConfig",
|
13
|
+
"load_battery_config",
|
14
|
+
"EquivalentCircuitModelConfig",
|
15
|
+
"FilteredBatteryModelConfig",
|
16
|
+
"KalmanFilterConfig",
|
17
|
+
"SOCDependent"
|
18
|
+
]
|
@@ -0,0 +1,102 @@
|
|
1
|
+
use std::f64;
|
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
|
+
}
|
11
|
+
|
12
|
+
/// Evaluate a polynomial given coefficients and an input value (x)
|
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]
|
16
|
+
}
|
17
|
+
|
18
|
+
/// Evolve the battery state for a single step
|
19
|
+
fn battery_evolve(
|
20
|
+
current: f64, // Amperes
|
21
|
+
tick: f64, // Seconds
|
22
|
+
state_of_charge: f64, // Dimensionless, 0 < SOC < 1
|
23
|
+
polarization_potential: f64, // Volts
|
24
|
+
polarization_resistance: f64, // Ohms
|
25
|
+
internal_resistance: f64, // Ohms
|
26
|
+
open_circuit_voltage: f64, // Volts
|
27
|
+
time_constant: f64, // Seconds
|
28
|
+
nominal_charge_capacity: f64, // Nominal charge capacity (Coulombs)
|
29
|
+
) -> (f64, f64, f64) {
|
30
|
+
// Update state of charge and polarization potential
|
31
|
+
let new_state_of_charge: f64 = state_of_charge + (current * tick / nominal_charge_capacity);
|
32
|
+
let new_polarization_potential: f64 = f64::exp(-tick / time_constant) * polarization_potential
|
33
|
+
+ current * polarization_resistance * (1.0 - f64::exp(-tick / time_constant));
|
34
|
+
let terminal_voltage: f64 = open_circuit_voltage + new_polarization_potential
|
35
|
+
+ (current * internal_resistance); // Terminal voltage
|
36
|
+
|
37
|
+
(new_state_of_charge, new_polarization_potential, terminal_voltage)
|
38
|
+
}
|
39
|
+
|
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
|
+
|
55
|
+
) -> (Vec<f64>, Vec<f64>) {
|
56
|
+
let mut state_of_charge: f64 = initial_state_of_charge;
|
57
|
+
let mut polarization_potential: f64 = initial_polarization_potential;
|
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());
|
60
|
+
|
61
|
+
for &input in energy_or_current_array.iter() {
|
62
|
+
// Interpolate values from coefficient
|
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
|
+
};
|
79
|
+
|
80
|
+
let (new_state_of_charge, new_polarization_potential, terminal_voltage) = battery_evolve(
|
81
|
+
current,
|
82
|
+
tick,
|
83
|
+
state_of_charge,
|
84
|
+
polarization_potential,
|
85
|
+
polarization_resistance,
|
86
|
+
internal_resistance,
|
87
|
+
open_circuit_voltage,
|
88
|
+
time_constant,
|
89
|
+
nominal_charge_capacity,
|
90
|
+
);
|
91
|
+
|
92
|
+
// Update state for the next iteration
|
93
|
+
state_of_charge = new_state_of_charge;
|
94
|
+
polarization_potential = new_polarization_potential;
|
95
|
+
|
96
|
+
// Store results
|
97
|
+
soc_array.push(new_state_of_charge);
|
98
|
+
voltage_array.push(terminal_voltage);
|
99
|
+
}
|
100
|
+
|
101
|
+
(soc_array, voltage_array)
|
102
|
+
}
|