ubc-solar-physics 1.5.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.
Files changed (122) hide show
  1. ubc_solar_physics-1.7.0/.gitattributes +1 -0
  2. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/Cargo.lock +10 -10
  3. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/Cargo.toml +2 -2
  4. ubc_solar_physics-1.7.0/MANIFEST.in +1 -0
  5. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/PKG-INFO +2 -1
  6. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/docs_requirements.txt +2 -2
  7. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/conf.py +7 -14
  8. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/models/battery.rst +12 -2
  9. ubc_solar_physics-1.7.0/examples/battery_model_examples/battery_config.toml +6 -0
  10. ubc_solar_physics-1.7.0/examples/battery_model_examples/battery_model_example.py +66 -0
  11. ubc_solar_physics-1.7.0/examples/kalman_filter_examples/battery_config.toml +6 -0
  12. ubc_solar_physics-1.7.0/examples/kalman_filter_examples/kalman_filter_example.py +96 -0
  13. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/_version.py +2 -2
  14. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/gis/base_gis.py +14 -0
  15. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/gis/gis.py +37 -3
  16. ubc_solar_physics-1.7.0/physics/environment/gis/gis.rs +113 -0
  17. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/clouded_meteorology.py +4 -4
  18. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/irradiant_meteorology.py +6 -6
  19. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/lib.rs +58 -26
  20. ubc_solar_physics-1.7.0/physics/models/battery/__init__.py +18 -0
  21. ubc_solar_physics-1.7.0/physics/models/battery/battery.rs +102 -0
  22. ubc_solar_physics-1.7.0/physics/models/battery/battery_config.py +107 -0
  23. ubc_solar_physics-1.7.0/physics/models/battery/battery_config.toml +6 -0
  24. ubc_solar_physics-1.7.0/physics/models/battery/battery_model.py +226 -0
  25. ubc_solar_physics-1.7.0/physics/models/battery/kalman_filter.py +223 -0
  26. ubc_solar_physics-1.7.0/physics_rs/__init__.pyi +111 -0
  27. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/pyproject.toml +4 -3
  28. ubc_solar_physics-1.7.0/tests/gis_tests/test_calculate_driving_speeds.py +70 -0
  29. ubc_solar_physics-1.7.0/tests/gis_tests/test_gis_driving_speeds.py +322 -0
  30. ubc_solar_physics-1.7.0/tests/kalman_filter_tests/test_basic_kalman_filter.py +72 -0
  31. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/ubc_solar_physics.egg-info/PKG-INFO +2 -1
  32. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/ubc_solar_physics.egg-info/SOURCES.txt +7 -4
  33. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/ubc_solar_physics.egg-info/requires.txt +1 -0
  34. ubc_solar_physics-1.7.0/ubc_solar_physics.egg-info/top_level.txt +2 -0
  35. ubc_solar_physics-1.5.0/.github/pull_request_template.md +0 -1
  36. ubc_solar_physics-1.5.0/examples/battery_model_examples/battery_model_example.py +0 -46
  37. ubc_solar_physics-1.5.0/examples/kalman_filter_examples/battery_config.toml +0 -8
  38. ubc_solar_physics-1.5.0/examples/kalman_filter_examples/kalman_filter_example.py +0 -97
  39. ubc_solar_physics-1.5.0/physics/environment/gis/gis.rs +0 -25
  40. ubc_solar_physics-1.5.0/physics/models/battery/__init__.py +0 -14
  41. ubc_solar_physics-1.5.0/physics/models/battery/battery.rs +0 -78
  42. ubc_solar_physics-1.5.0/physics/models/battery/battery_config.py +0 -22
  43. ubc_solar_physics-1.5.0/physics/models/battery/battery_config.toml +0 -8
  44. ubc_solar_physics-1.5.0/physics/models/battery/battery_model.py +0 -135
  45. ubc_solar_physics-1.5.0/physics/models/battery/kalman_filter.py +0 -341
  46. ubc_solar_physics-1.5.0/tests/battery_config.toml +0 -8
  47. ubc_solar_physics-1.5.0/tests/kalman_filter_tests/test_basic_kalman_filter.py +0 -46
  48. ubc_solar_physics-1.5.0/tests/kalman_filter_tests/test_filter_real_data.py +0 -104
  49. ubc_solar_physics-1.5.0/ubc_solar_physics.egg-info/top_level.txt +0 -1
  50. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/.github/workflows/build_and_publish.yaml +0 -0
  51. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/.github/workflows/run_tests.yaml +0 -0
  52. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/.gitignore +0 -0
  53. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/.readthedocs.yaml +0 -0
  54. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/LICENSE +0 -0
  55. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/README.md +0 -0
  56. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/data.png +0 -0
  57. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/Makefile +0 -0
  58. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/_generate_version.py +0 -0
  59. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/make.bat +0 -0
  60. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/README.md +0 -0
  61. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/api.rst +0 -0
  62. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/environment/gis.rst +0 -0
  63. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/environment/index.rst +0 -0
  64. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/environment/meteorology.rst +0 -0
  65. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/index.rst +0 -0
  66. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/models/arrays.rst +0 -0
  67. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/models/index.rst +0 -0
  68. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/models/lvs.rst +0 -0
  69. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/models/motor.rst +0 -0
  70. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/docs/source/models/regen.rst +0 -0
  71. {ubc_solar_physics-1.5.0/examples/data_query → ubc_solar_physics-1.7.0/examples/battery_model_examples}/current.csv +0 -0
  72. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/examples/battery_model_examples/data_py.png +0 -0
  73. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/examples/battery_model_examples/data_rust.png +0 -0
  74. {ubc_solar_physics-1.5.0/examples/data_query → ubc_solar_physics-1.7.0/examples/battery_model_examples}/voltage.csv +0 -0
  75. {ubc_solar_physics-1.5.0/examples/kalman_filter_examples → ubc_solar_physics-1.7.0/examples/data_query}/current.csv +0 -0
  76. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/examples/data_query/poetry.lock +0 -0
  77. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/examples/data_query/pyproject.toml +0 -0
  78. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/examples/data_query/query_data.py +0 -0
  79. {ubc_solar_physics-1.5.0/examples/kalman_filter_examples → ubc_solar_physics-1.7.0/examples/data_query}/voltage.csv +0 -0
  80. {ubc_solar_physics-1.5.0/tests/kalman_filter_tests → ubc_solar_physics-1.7.0/examples/kalman_filter_examples}/current.csv +0 -0
  81. {ubc_solar_physics-1.5.0/tests/kalman_filter_tests → ubc_solar_physics-1.7.0/examples/kalman_filter_examples}/voltage.csv +0 -0
  82. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/__init__.py +0 -0
  83. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/__init__.py +0 -0
  84. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/environment.rs +0 -0
  85. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/gis/__init__.py +0 -0
  86. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/gis.rs +0 -0
  87. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/__init__.py +0 -0
  88. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/base_meteorology.py +0 -0
  89. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology/meteorology.rs +0 -0
  90. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment/meteorology.rs +0 -0
  91. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/environment.rs +0 -0
  92. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/__init__.py +0 -0
  93. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/arrays/__init__.py +0 -0
  94. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/arrays/arrays.rs +0 -0
  95. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/arrays/base_array.py +0 -0
  96. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/arrays/basic_array.py +0 -0
  97. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/arrays.rs +0 -0
  98. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/battery/base_battery.py +0 -0
  99. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/battery/basic_battery.py +0 -0
  100. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/battery.rs +0 -0
  101. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/constants.py +0 -0
  102. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/lvs/__init__.py +0 -0
  103. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/lvs/base_lvs.py +0 -0
  104. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/lvs/basic_lvs.py +0 -0
  105. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/lvs/lvs.rs +0 -0
  106. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/lvs.rs +0 -0
  107. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/motor/__init__.py +0 -0
  108. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/motor/advanced_motor.py +0 -0
  109. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/motor/base_motor.py +0 -0
  110. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/motor/basic_motor.py +0 -0
  111. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/motor/motor.rs +0 -0
  112. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/motor.rs +0 -0
  113. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/regen/__init__.py +0 -0
  114. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/regen/base_regen.py +0 -0
  115. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/regen/basic_regen.py +0 -0
  116. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/regen/regen.rs +0 -0
  117. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models/regen.rs +0 -0
  118. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/physics/models.rs +0 -0
  119. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/setup.cfg +0 -0
  120. {ubc_solar_physics-1.5.0/examples/battery_model_examples → ubc_solar_physics-1.7.0/tests}/battery_config.toml +0 -0
  121. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/tests/test_versioning.py +0 -0
  122. {ubc_solar_physics-1.5.0 → ubc_solar_physics-1.7.0}/ubc_solar_physics.egg-info/dependency_links.txt +0 -0
@@ -0,0 +1 @@
1
+ **/*.csv linguist-generated=true
@@ -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
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"
@@ -1,10 +1,10 @@
1
1
  [package]
2
- name = "core"
2
+ name = "physics_rs"
3
3
  version = "0.1.0"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
7
- name = "core"
7
+ name = "physics_rs"
8
8
  path = "physics/lib.rs"
9
9
  crate-type = ["cdylib"]
10
10
 
@@ -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.5.0
3
+ Version: 1.7.0
4
4
  Summary: UBC Solar's Simulation Environment
5
5
  Author: Fisher Xue, Mihir Nimgade, Chris Chang, David Widjaja, Justin Hua, Ilya Veksler, Renu Rajamagesh, Ritchie Xia, Erik Langille, Chris Aung, Nicolas Ric, Ishaan Trivedi, Jason Liang, Felix Toft, Mack Wilson, Jonah Lee, Tamzeed Quazi, Joshua Riefman
6
6
  Author-email: UBC Solar <strategy@ubcsolar.com>
@@ -73,6 +73,7 @@ Requires-Dist: pandas
73
73
  Requires-Dist: pydantic==2.9.2
74
74
  Requires-Dist: scipy
75
75
  Requires-Dist: tomli
76
+ Requires-Dist: scipy-stubs
76
77
 
77
78
  # UBC Solar Physics
78
79
 
@@ -38,7 +38,6 @@ rich
38
38
  snowballstemmer
39
39
  Sphinx
40
40
  sphinx-autodoc-typehints
41
- sphinx-rtd-theme==2.0.0
42
41
  sphinxcontrib-applehelp
43
42
  sphinxcontrib-devhelp
44
43
  sphinxcontrib-htmlhelp
@@ -50,4 +49,5 @@ tqdm
50
49
  urllib3
51
50
  zipp
52
51
  setuptools_scm
53
- pydata-sphinx-theme==0.15.4
52
+ pydata-sphinx-theme==0.15.4
53
+ ubc-solar-physics==1.5.0
@@ -1,16 +1,9 @@
1
- # from setuptools_scm import get_version
2
- # from pathlib import Path
3
- # import importlib
4
-
5
- # Generate the version file
6
- # version = get_version()
7
- # version_file = Path('physics/_version.py')
8
- # version_file.write_text(f"__version__ = '{version}'\n")
9
-
10
- # Dynamically import the version
11
- # importlib.invalidate_caches()
12
- # physics_module = importlib.import_module("physics")
13
- # __version__ = physics_module.__version__
1
+ import os
2
+ import sys
3
+ sys.path.insert(0, os.path.abspath('../../')) # Adjust as necessary
4
+ print(sys.path)
5
+ print(os.listdir(os.getcwd()))
6
+ print(os.listdir(sys.path[0]))
14
7
 
15
8
  # Configuration file for the Sphinx documentation builder.
16
9
  #
@@ -23,7 +16,7 @@
23
16
  project = 'UBC Solar Physics'
24
17
  copyright = '2024, UBC Solar'
25
18
  author = 'Joshua Riefman'
26
- release = '1.0.0'
19
+ release = "1.0.0"
27
20
 
28
21
  # -- General configuration ---------------------------------------------------
29
22
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -21,7 +21,17 @@ Battery
21
21
  :undoc-members:
22
22
  :show-inheritance:
23
23
 
24
- .. autoclass:: physics.models.battery.load_battery_config
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()
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.5.0'
16
- __version_tuple__ = version_tuple = (1, 5, 0)
15
+ __version__ = version = '1.7.0'
16
+ __version_tuple__ = version_tuple = (1, 7, 0)
@@ -1,5 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  import numpy as np
3
+ from numpy.typing import ArrayLike, NDArray
3
4
 
4
5
 
5
6
  class BaseGIS(ABC):
@@ -22,3 +23,16 @@ class BaseGIS(ABC):
22
23
  @abstractmethod
23
24
  def get_path(self) -> np.ndarray:
24
25
  raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ def calculate_current_heading_array(self) -> np.ndarray:
29
+ raise NotImplementedError
30
+
31
+ def calculate_driving_speeds(
32
+ self,
33
+ average_lap_speeds: ArrayLike,
34
+ simulation_dt: int,
35
+ driving_allowed: ArrayLike,
36
+ idle_time: int
37
+ ) -> NDArray[float]:
38
+ raise NotImplementedError
@@ -1,9 +1,10 @@
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
 
7
+ from numpy.typing import ArrayLike, NDArray
7
8
  from tqdm import tqdm
8
9
  from xml.dom import minidom
9
10
  from haversine import haversine, Unit
@@ -80,13 +81,46 @@ class GIS(BaseGIS):
80
81
  :rtype: np.ndarray
81
82
 
82
83
  """
83
- return core.closest_gis_indices_loop(distances, self.path_distances)
84
+ return physics_rs.closest_gis_indices_loop(distances, self.path_distances)
85
+
86
+ def calculate_driving_speeds(
87
+ self,
88
+ average_lap_speeds: ArrayLike,
89
+ simulation_dt: int,
90
+ driving_allowed: ArrayLike,
91
+ idle_time: int
92
+ ) -> NDArray[float]:
93
+ """
94
+ Generate valid driving speeds as a simulation-time array given a set of average speeds for each
95
+ simulated lap.
96
+ Driving speeds will only be non-zero when we are allowed to drive, and the speed
97
+ for every tick during a lap will be that lap's corresponding desired average speed for as long
98
+ as it takes to complete the lap.
99
+
100
+ :param average_lap_speeds: An array of average speeds in m/s, one for each simulated lap.
101
+ If there are more speeds given than laps available, the unused speeds will be silently ignored.
102
+ If there are too few, an error will be returned.
103
+ :param simulation_dt: The simulated tick length.
104
+ :param driving_allowed: A simulation-time boolean where the `True` elements are when we
105
+ are allowed to drive, and `False` is when we are not. Requires that (at least) the first element is
106
+ `False` due to the race beginning in the morning before we are allowed to drive.
107
+ :param idle_time: The length of time to pause driving upon processing a "0m/s" average speed.
108
+ :return: A simulation-time array of driving speeds in m/s, or an error if there weren't enough
109
+ laps provided to fill the entire simulation time.
110
+ """
111
+ return physics_rs.get_driving_speeds(
112
+ np.array(average_lap_speeds).astype(np.float64),
113
+ simulation_dt,
114
+ np.array(driving_allowed).astype(bool),
115
+ self.path_length,
116
+ idle_time
117
+ )
84
118
 
85
119
  @staticmethod
86
120
  def _python_calculate_closest_gis_indices(distances, path_distances):
87
121
  """
88
122
 
89
- 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.
90
124
 
91
125
  """
92
126
 
@@ -0,0 +1,113 @@
1
+ use numpy::ndarray::{ArrayViewD, ArrayView1};
2
+
3
+ pub fn rust_closest_gis_indices_loop(
4
+ distances: ArrayViewD<'_, f64>,
5
+ path_distances: ArrayViewD<'_, f64>,
6
+ ) -> Vec<i64> {
7
+ let mut current_coord_index: usize = 0;
8
+ let mut distance_travelled: f64 = 0.0;
9
+ let mut result: Vec<i64> = Vec::with_capacity(distances.len());
10
+
11
+ for &distance in distances {
12
+ distance_travelled += distance;
13
+
14
+ while distance_travelled > path_distances[current_coord_index] {
15
+ distance_travelled -= path_distances[current_coord_index];
16
+ current_coord_index += 1;
17
+ if current_coord_index >= path_distances.len() {
18
+ current_coord_index = 0;
19
+ }
20
+ }
21
+
22
+ current_coord_index = std::cmp::min(current_coord_index, path_distances.len() - 1);
23
+ result.push(current_coord_index as i64);
24
+ }
25
+
26
+ result
27
+ }
28
+
29
+ ///
30
+ /// Generate valid driving speeds as a simulation-time array given a set of average speeds for each
31
+ /// simulated lap.
32
+ /// Driving speeds will only be non-zero when we are allowed to drive, and the speed
33
+ /// for every tick during a lap will be that lap's corresponding desired average speed for as long
34
+ /// as it takes to complete the lap.
35
+ /// An average speed of 0m/s for a lap will be interpreted as "sit and charge" for `idle_time`
36
+ /// ticks.
37
+ ///
38
+ /// # Arguments
39
+ ///
40
+ /// * `average_speeds`: An array of average speeds in m/s, one for each simulated lap. If there are more
41
+ /// speeds given than laps available, the unused speeds will be silently ignored. If there are too
42
+ /// few, an error will be returned.
43
+ /// * `simulation_dt`: The simulated tick length
44
+ /// * `driving_allowed_boolean`: A simulation-time boolean where the `True` elements are when we
45
+ /// are allowed to drive, and `False` is when we are not.
46
+ /// * `track_length`: The length of the track in meters.
47
+ /// * `idle_time`: The number of ticks to "sit and charge" when desired.
48
+ ///
49
+ /// Returns: A simulation-time array of driving speeds in m/s, or an error if there weren't enough
50
+ /// laps provided to fill the entire simulation time.
51
+ ///
52
+ pub fn get_driving_speeds(
53
+ average_speeds: ArrayView1<'_, f64>, // Average speeds in m/s
54
+ simulation_dt: i64, // Time step in seconds
55
+ driving_allowed_boolean: ArrayView1<'_, bool>, // Simulation-time boolean array
56
+ track_length: f64, // Track length in meters
57
+ idle_time: i64 // Time to idle in seconds
58
+ ) -> Result<Vec<f64>, &'static str> {
59
+ let ticks_to_complete_lap: Vec<i64> = average_speeds.iter().map(| &average_speed | {
60
+ if average_speed > 0.0 {
61
+ // The number of ticks is the number of seconds, divided by seconds per tick
62
+ (track_length / average_speed / simulation_dt as f64).ceil() as i64
63
+ } else {
64
+ (idle_time as f64 / simulation_dt as f64).ceil() as i64
65
+ }
66
+ }).collect();
67
+
68
+ let mut lap_index: usize = 0;
69
+ let mut lap_speed: f64 = average_speeds[lap_index];
70
+
71
+ let mut ticks_to_lap_completion: i64 = ticks_to_complete_lap[lap_index];
72
+
73
+ let mut driving_speeds: Vec<f64> = Vec::with_capacity(driving_allowed_boolean.len());
74
+ for driving_allowed in driving_allowed_boolean.iter() {
75
+ if !driving_allowed {
76
+ // If we aren't allowed to drive, speed should be zero. Also, we should mark that we are
77
+ // done our lap since it means we ended the day in the middle of the lap, and we will
78
+ // start the next day at the beginning of a new lap, not where we ended off.
79
+
80
+ // If it's the first lap, we don't want to skip because we are probably in the morning
81
+ // where we haven't begun driving yet.
82
+ if lap_index > 0 {
83
+ ticks_to_lap_completion = 0;
84
+ }
85
+
86
+ driving_speeds.push(0.0)
87
+ } else {
88
+ // If we are driving, we should decrement ticks to lap completion. If its already
89
+ // zero, that means that we are done the lap and should move onto the next lap.
90
+ if ticks_to_lap_completion > 0 {
91
+ ticks_to_lap_completion -= 1;
92
+
93
+ driving_speeds.push(lap_speed)
94
+ } else {
95
+ // To advance to the next lap, increment the index and evaluate new variables
96
+ lap_index += 1;
97
+ if lap_index >= average_speeds.len() {
98
+ return Err("Not enough average speeds!")
99
+ }
100
+
101
+ // We subtract 1 since this iteration counts for the next lap, not the one
102
+ // that we just finished
103
+ ticks_to_lap_completion = ticks_to_complete_lap[lap_index] - 1;
104
+ lap_speed = average_speeds[lap_index];
105
+
106
+ driving_speeds.push(lap_speed)
107
+ }
108
+ }
109
+
110
+ }
111
+
112
+ Ok(driving_speeds)
113
+ }
@@ -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
-