PyDPEET 0.2.0__py3-none-any.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.
Files changed (122) hide show
  1. pydpeet/__init__.py +56 -0
  2. pydpeet/citations/__init__.py +7 -0
  3. pydpeet/citations/citeme.py +397 -0
  4. pydpeet/citations/html_writer.py +137 -0
  5. pydpeet/dev_utils/__init__.py +7 -0
  6. pydpeet/dev_utils/generate_inits/__init__.py +7 -0
  7. pydpeet/dev_utils/generate_inits/generate_inits.py +327 -0
  8. pydpeet/io/__init__.py +7 -0
  9. pydpeet/io/configs/__init__.py +7 -0
  10. pydpeet/io/configs/config.py +227 -0
  11. pydpeet/io/configs/const.py +1 -0
  12. pydpeet/io/convert.py +594 -0
  13. pydpeet/io/device/__init__.py +7 -0
  14. pydpeet/io/device/arbin_4_23_PV090331/__init__.py +7 -0
  15. pydpeet/io/device/arbin_4_23_PV090331/formatter.py +22 -0
  16. pydpeet/io/device/arbin_4_23_PV090331/mapper.py +18 -0
  17. pydpeet/io/device/arbin_4_23_PV090331/reader.py +68 -0
  18. pydpeet/io/device/arbin_8_00_PV221201/__init__.py +7 -0
  19. pydpeet/io/device/arbin_8_00_PV221201/formatter.py +26 -0
  20. pydpeet/io/device/arbin_8_00_PV221201/mapper.py +18 -0
  21. pydpeet/io/device/arbin_8_00_PV221201/reader.py +66 -0
  22. pydpeet/io/device/basytec_6_3_1_0/__init__.py +7 -0
  23. pydpeet/io/device/basytec_6_3_1_0/formatter.py +18 -0
  24. pydpeet/io/device/basytec_6_3_1_0/mapper.py +18 -0
  25. pydpeet/io/device/basytec_6_3_1_0/reader.py +54 -0
  26. pydpeet/io/device/digatron_4_20_6_236/__init__.py +7 -0
  27. pydpeet/io/device/digatron_4_20_6_236/formatter.py +27 -0
  28. pydpeet/io/device/digatron_4_20_6_236/mapper.py +18 -0
  29. pydpeet/io/device/digatron_4_20_6_236/reader.py +45 -0
  30. pydpeet/io/device/digatron_eis_4_20_6_236/__init__.py +7 -0
  31. pydpeet/io/device/digatron_eis_4_20_6_236/formatter.py +39 -0
  32. pydpeet/io/device/digatron_eis_4_20_6_236/mapper.py +18 -0
  33. pydpeet/io/device/digatron_eis_4_20_6_236/reader.py +42 -0
  34. pydpeet/io/device/neware_8_0_0_516/__init__.py +7 -0
  35. pydpeet/io/device/neware_8_0_0_516/formatter.py +31 -0
  36. pydpeet/io/device/neware_8_0_0_516/mapper.py +18 -0
  37. pydpeet/io/device/neware_8_0_0_516/reader.py +397 -0
  38. pydpeet/io/device/parstat_2_63_3/__init__.py +7 -0
  39. pydpeet/io/device/parstat_2_63_3/formatter.py +34 -0
  40. pydpeet/io/device/parstat_2_63_3/mapper.py +18 -0
  41. pydpeet/io/device/parstat_2_63_3/reader.py +34 -0
  42. pydpeet/io/device/safion_1_9/__init__.py +7 -0
  43. pydpeet/io/device/safion_1_9/formatter.py +25 -0
  44. pydpeet/io/device/safion_1_9/mapper.py +18 -0
  45. pydpeet/io/device/safion_1_9/reader.py +89 -0
  46. pydpeet/io/device/zahner/__init__.py +7 -0
  47. pydpeet/io/device/zahner/formatter.py +73 -0
  48. pydpeet/io/device/zahner/mapper.py +34 -0
  49. pydpeet/io/device/zahner/reader.py +46 -0
  50. pydpeet/io/device/zahner_new/__init__.py +7 -0
  51. pydpeet/io/device/zahner_new/formatter.py +72 -0
  52. pydpeet/io/device/zahner_new/mapper.py +50 -0
  53. pydpeet/io/device/zahner_new/reader.py +46 -0
  54. pydpeet/io/map.py +69 -0
  55. pydpeet/io/read.py +50 -0
  56. pydpeet/io/utils/__init__.py +7 -0
  57. pydpeet/io/utils/ext_path.py +34 -0
  58. pydpeet/io/utils/formatter_utils.py +429 -0
  59. pydpeet/io/utils/load_custom_module.py +33 -0
  60. pydpeet/io/utils/timing.py +36 -0
  61. pydpeet/io/write.py +99 -0
  62. pydpeet/process/__init__.py +7 -0
  63. pydpeet/process/analyze/__init__.py +7 -0
  64. pydpeet/process/analyze/average.py +297 -0
  65. pydpeet/process/analyze/capacity.py +208 -0
  66. pydpeet/process/analyze/configs/__init__.py +7 -0
  67. pydpeet/process/analyze/configs/battery_config.py +38 -0
  68. pydpeet/process/analyze/configs/ocv_config.py +177 -0
  69. pydpeet/process/analyze/configs/step_analyzer_config.py +178 -0
  70. pydpeet/process/analyze/cycle.py +63 -0
  71. pydpeet/process/analyze/efficiency.py +182 -0
  72. pydpeet/process/analyze/energy.py +51 -0
  73. pydpeet/process/analyze/extract/__init__.py +7 -0
  74. pydpeet/process/analyze/extract/dva_ica.py +224 -0
  75. pydpeet/process/analyze/extract/ocv.py +196 -0
  76. pydpeet/process/analyze/power.py +32 -0
  77. pydpeet/process/analyze/resistance.py +87 -0
  78. pydpeet/process/analyze/soc.py +418 -0
  79. pydpeet/process/analyze/soh.py +72 -0
  80. pydpeet/process/analyze/utils.py +122 -0
  81. pydpeet/process/merge/__init__.py +7 -0
  82. pydpeet/process/merge/series.py +274 -0
  83. pydpeet/process/sequence/__init__.py +7 -0
  84. pydpeet/process/sequence/configs/__init__.py +7 -0
  85. pydpeet/process/sequence/configs/config.py +17 -0
  86. pydpeet/process/sequence/step_analyzer.py +440 -0
  87. pydpeet/process/sequence/utils/__init__.py +7 -0
  88. pydpeet/process/sequence/utils/annotate/__init__.py +7 -0
  89. pydpeet/process/sequence/utils/annotate/annotate_primitives.py +419 -0
  90. pydpeet/process/sequence/utils/configs/CONFIG_Fallback.py +231 -0
  91. pydpeet/process/sequence/utils/configs/CONFIG_preprocessing.py +243 -0
  92. pydpeet/process/sequence/utils/configs/__init__.py +7 -0
  93. pydpeet/process/sequence/utils/console_prints/__init__.py +7 -0
  94. pydpeet/process/sequence/utils/console_prints/log_time.py +30 -0
  95. pydpeet/process/sequence/utils/postprocessing/__init__.py +7 -0
  96. pydpeet/process/sequence/utils/postprocessing/df_primitives_correction.py +166 -0
  97. pydpeet/process/sequence/utils/postprocessing/filter_df.py +168 -0
  98. pydpeet/process/sequence/utils/postprocessing/generate_instructions.py +297 -0
  99. pydpeet/process/sequence/utils/preprocessing/__init__.py +7 -0
  100. pydpeet/process/sequence/utils/preprocessing/calculate_thresholds.py +27 -0
  101. pydpeet/process/sequence/utils/processing/__init__.py +7 -0
  102. pydpeet/process/sequence/utils/processing/analyze_segments.py +368 -0
  103. pydpeet/process/sequence/utils/processing/attempt_to_merge_neighboring_segments.py +122 -0
  104. pydpeet/process/sequence/utils/processing/check_CV_results.py +226 -0
  105. pydpeet/process/sequence/utils/processing/check_power_zero_watt_segments.py +85 -0
  106. pydpeet/process/sequence/utils/processing/check_zero_length.py +117 -0
  107. pydpeet/process/sequence/utils/processing/split_in_segments.py +97 -0
  108. pydpeet/process/sequence/utils/processing/supress_smaller_segments.py +99 -0
  109. pydpeet/process/sequence/utils/processing/widen_constant_segments.py +185 -0
  110. pydpeet/process/sequence/utils/visualize/__init__.py +7 -0
  111. pydpeet/process/sequence/utils/visualize/visualize_data.py +288 -0
  112. pydpeet/res/__init__.py +7 -0
  113. pydpeet/settings.py +0 -0
  114. pydpeet/utils/__init__.py +7 -0
  115. pydpeet/utils/logging_style.py +28 -0
  116. pydpeet/version.py +0 -0
  117. pydpeet-0.2.0.dist-info/METADATA +85 -0
  118. pydpeet-0.2.0.dist-info/RECORD +122 -0
  119. pydpeet-0.2.0.dist-info/WHEEL +5 -0
  120. pydpeet-0.2.0.dist-info/licenses/AUTHORS.md +0 -0
  121. pydpeet-0.2.0.dist-info/licenses/LICENCE.md +27 -0
  122. pydpeet-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,440 @@
1
+ import logging
2
+ import os
3
+
4
+ import pandas as pd
5
+
6
+ from pydpeet.process.sequence.utils.annotate.annotate_primitives import (
7
+ _annotate_primitives,
8
+ _merged_annotations,
9
+ )
10
+ from pydpeet.process.sequence.utils.configs.CONFIG_Fallback import FALLBACK_CONFIG
11
+ from pydpeet.process.sequence.utils.console_prints.log_time import log_time
12
+ from pydpeet.process.sequence.utils.postprocessing.df_primitives_correction import df_primitives_correction
13
+ from pydpeet.process.sequence.utils.processing.analyze_segments import _analyze_segments
14
+ from pydpeet.process.sequence.utils.processing.attempt_to_merge_neighboring_segments import (
15
+ _attempt_to_merge_neighboring_segments,
16
+ )
17
+ from pydpeet.process.sequence.utils.processing.check_CV_results import _check_CV_0Aend_segments
18
+ from pydpeet.process.sequence.utils.processing.check_power_zero_watt_segments import _check_power_zero_watt_segments
19
+ from pydpeet.process.sequence.utils.processing.check_zero_length import _check_zero_length_segments
20
+ from pydpeet.process.sequence.utils.processing.split_in_segments import _split_in_segments_using_incremental_linear_fit
21
+ from pydpeet.process.sequence.utils.processing.supress_smaller_segments import (
22
+ _add_segment_lengths,
23
+ _keep_max_segment_id,
24
+ )
25
+ from pydpeet.process.sequence.utils.processing.widen_constant_segments import _widen_constant_segments
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ def add_primitive_segments(
31
+ df: pd.DataFrame,
32
+ STEP_ANALYZER_PRIMITIVES_CONFIG: dict = None,
33
+ SEGMENTS_TO_DETECT_CONFIG: list[tuple[str, float]] = None,
34
+ ADJUST_SEGMENTS_CONFIG: list[tuple[str, float]] = None,
35
+ THRESHOLDS_PRIMITIVE_ANNOTATION: dict[str, float] = None,
36
+ THRESHOLD_CV_SEGMENTS_0A_END: float = None,
37
+ THRESHOLD_CONSOLE_PRINTS_CV_CHECK: int = None,
38
+ THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK: int = None,
39
+ THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH: int = None,
40
+ THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK: int = None,
41
+ SHOW_RUNTIME: bool = True,
42
+ check_CV_0Aend_segments_bool: bool = True,
43
+ check_zero_length_segments_bool: bool = True,
44
+ check_Power_zero_W_segments_bool: bool = True,
45
+ supress_IO_warnings: bool = False,
46
+ PRECOMPILE: bool = True, # When debugging use PRECOMPILE=False, else you'll see the dummy data!
47
+ FORCE_PRECOMPILATION: bool = False,
48
+ ) -> pd.DataFrame:
49
+ """
50
+ Function to perform step analysis to create a segmentation of a dataframe into primitive Segments.
51
+
52
+ Parameters:
53
+ df (pd.DataFrame): Input dataframe to perform primitive step analysis on.
54
+ STEP_ANALYZER_PRIMITIVES_CONFIG (Dict): Configuration for step analysis (combined Dict of following parameters)
55
+ SEGMENTS_TO_DETECT_CONFIG (List[Tuple[str, float]]): Threshold values for each column to detect segments
56
+ Example: [("Voltage[V]", 0.1), ("Current[A]", 0.1), ("Power[W]", 0.1)]
57
+ ADJUST_SEGMENTS_CONFIG (List[Tuple[str, float]]): Threshold values for each column to adjust the segments
58
+ Example: [("Voltage[V]", 0.1), ("Current[A]", 0.1), ("Power[W]", 0.1)]
59
+ THRESHOLDS_PRIMITIVE_ANNOTATION (Dict[str, float]): Threshold values for each column to annotate primitives
60
+ Example: {"V": 0.1, "I": 0.1, "P": 0.1} #Same key as DATA_COLUMNS
61
+ THRESHOLD_CV_SEGMENTS_0A_END (float): Threshold value for CV 0A end segments
62
+ THRESHOLD_CONSOLE_PRINTS_CV_CHECK (int): Threshold value for CV check console prints
63
+ THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK (int): Threshold value for zero length check console prints
64
+ THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH (int): Threshold value for finetuning width check console prints
65
+ THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK (int): Threshold value for power zero watt check console prints
66
+ SHOW_RUNTIME (bool): Whether to show the runtime of this function, by default True
67
+ check_CV_0Aend_segments_bool (bool): Whether to check CV 0A end segments, by default True
68
+ check_zero_length_segments_bool (bool): Whether to check zero length segments, by default True
69
+ check_Power_zero_W_segments_bool (bool): Whether to check power zero watt segments, by default True
70
+ supress_IO_warnings (bool): Whether to supress IO warnings, by default False
71
+ PRECOMPILE (bool): Whether to allow to precompile the functions, by default True
72
+ FORCE_PRECOMPILATION (bool): Whether to force precompilation, by default False
73
+
74
+ Returns:
75
+ df_primitives (pd.DataFrame): The final annotated dataframe
76
+ """
77
+ # --- Merge Configuration Priority ---
78
+ fallback = FALLBACK_CONFIG.copy()
79
+ config_dict = STEP_ANALYZER_PRIMITIVES_CONFIG or {}
80
+ merged_config = {**fallback, **config_dict}
81
+
82
+ explicit_overrides = {
83
+ "SEGMENTS_TO_DETECT_CONFIG": SEGMENTS_TO_DETECT_CONFIG,
84
+ "ADJUST_SEGMENTS_CONFIG": ADJUST_SEGMENTS_CONFIG,
85
+ "THRESHOLDS_PRIMITIVE_ANNOTATION": THRESHOLDS_PRIMITIVE_ANNOTATION,
86
+ "THRESHOLD_CV_SEGMENTS_0A_END": THRESHOLD_CV_SEGMENTS_0A_END,
87
+ "THRESHOLD_CONSOLE_PRINTS_CV_CHECK": THRESHOLD_CONSOLE_PRINTS_CV_CHECK,
88
+ "THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK": THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK,
89
+ "THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH": THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH,
90
+ "THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK": THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK,
91
+ "SHOW_RUNTIME": SHOW_RUNTIME,
92
+ "check_CV_0Aend_segments_bool": check_CV_0Aend_segments_bool,
93
+ "check_zero_length_segments_bool": check_zero_length_segments_bool,
94
+ "check_Power_zero_W_segments_bool": check_Power_zero_W_segments_bool,
95
+ "supress_IO_warnings": supress_IO_warnings,
96
+ "PRECOMPILE": PRECOMPILE,
97
+ "FORCE_PRECOMPILATION": FORCE_PRECOMPILATION,
98
+ }
99
+
100
+ # prefer manual variables over the fallback
101
+ for key, val in explicit_overrides.items():
102
+ if val is not None:
103
+ merged_config[key] = val
104
+
105
+ SEGMENTS_TO_DETECT_CONFIG = merged_config["SEGMENTS_TO_DETECT_CONFIG"]
106
+ ADJUST_SEGMENTS_CONFIG = merged_config["ADJUST_SEGMENTS_CONFIG"]
107
+ THRESHOLDS_PRIMITIVE_ANNOTATION = merged_config["THRESHOLDS_PRIMITIVE_ANNOTATION"]
108
+ THRESHOLD_CV_SEGMENTS_0A_END = merged_config["THRESHOLD_CV_SEGMENTS_0A_END"]
109
+ SHOW_RUNTIME = merged_config["SHOW_RUNTIME"]
110
+ THRESHOLD_CONSOLE_PRINTS_CV_CHECK = merged_config["THRESHOLD_CONSOLE_PRINTS_CV_CHECK"]
111
+ THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK = merged_config["THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK"]
112
+ THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH = merged_config["THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH"]
113
+ THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK = merged_config["THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK"]
114
+ check_CV_0Aend_segments_bool = merged_config["check_CV_0Aend_segments_bool"]
115
+ check_zero_length_segments_bool = merged_config["check_zero_length_segments_bool"]
116
+ check_Power_zero_W_segments_bool = merged_config["check_Power_zero_W_segments_bool"]
117
+ supress_IO_warnings = merged_config["supress_IO_warnings"]
118
+ PRECOMPILE = merged_config["PRECOMPILE"]
119
+ FORCE_PRECOMPILATION = merged_config["FORCE_PRECOMPILATION"]
120
+
121
+ DATA_COLUMNS = { # standard values
122
+ "V": "Voltage[V]",
123
+ "I": "Current[A]",
124
+ "P": "Power[W]",
125
+ }
126
+
127
+ # --- Warn if using fallback ---
128
+ if STEP_ANALYZER_PRIMITIVES_CONFIG is None and not supress_IO_warnings:
129
+ logger.warning(
130
+ "Using EXAMPLE_STEP_ANALYZER_PRIMITIVES_CONFIG as fallback configuration. Manual Parameters will be kept."
131
+ )
132
+
133
+ # TODO variable to choose if copy should be used?
134
+ df_step = df.copy()
135
+
136
+ # --- Guardrails ---
137
+ if df is None or df.empty or not isinstance(df, pd.DataFrame):
138
+ raise ValueError("Input dataframe is None or empty")
139
+ if "Voltage[V]" not in df_step.columns:
140
+ raise ValueError("'Voltage[V]' column not found in input dataframe.")
141
+ if "Current[A]" not in df_step.columns:
142
+ raise ValueError("'Current[A]' column not found in input dataframe.")
143
+ if "Test_Time[s]" not in df_step.columns:
144
+ raise ValueError("'Test_Time[s]' column not found in input dataframe.")
145
+ if SEGMENTS_TO_DETECT_CONFIG is None or len(SEGMENTS_TO_DETECT_CONFIG) == 0:
146
+ raise ValueError("SEGMENTS_TO_DETECT_CONFIG is None or empty")
147
+ if ADJUST_SEGMENTS_CONFIG is None or len(ADJUST_SEGMENTS_CONFIG) == 0:
148
+ raise ValueError("ADJUST_SEGMENTS_CONFIG is None or empty")
149
+ if THRESHOLDS_PRIMITIVE_ANNOTATION is None or len(THRESHOLDS_PRIMITIVE_ANNOTATION) == 0:
150
+ raise ValueError("THRESHOLDS_PRIMITIVE_ANNOTATION is None or empty")
151
+ if THRESHOLD_CV_SEGMENTS_0A_END is None or not isinstance(THRESHOLD_CV_SEGMENTS_0A_END, float):
152
+ raise ValueError("THRESHOLD_CV_SEGMENTS_OA_END is None or not a float")
153
+ if THRESHOLD_CONSOLE_PRINTS_CV_CHECK is None or not isinstance(THRESHOLD_CONSOLE_PRINTS_CV_CHECK, int):
154
+ raise ValueError("THRESHOLD_CONSOLE_PRINTS_CV_CHECK is None or not a int")
155
+ if THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK is None or not isinstance(
156
+ THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK, int
157
+ ):
158
+ raise ValueError("THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK is None or not a int")
159
+ if THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH is None or not isinstance(
160
+ THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH, int
161
+ ):
162
+ raise ValueError("THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH is None or not a int")
163
+ if THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK is None or not isinstance(
164
+ THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK, int
165
+ ):
166
+ raise ValueError("THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK is None or not a int")
167
+
168
+ # --- Data cleanup ---
169
+ if not supress_IO_warnings:
170
+ logger.warning("Dropping NaN values in 'Test_Time[s]', dropping duplicates and sorting 'Test_Time[s]' column.")
171
+ df_step.dropna(subset=["Test_Time[s]"], inplace=True)
172
+ df_step.drop_duplicates(subset=["Test_Time[s]"], inplace=True)
173
+ df_step.sort_values(by=["Test_Time[s]"], inplace=True)
174
+
175
+ # --- Guardrails & IO Warnings ---
176
+ if not supress_IO_warnings:
177
+ for column_name, threshold in SEGMENTS_TO_DETECT_CONFIG:
178
+ if threshold < 0:
179
+ logger.warning(f"Threshold for {column_name} is negative, using abs({threshold}) instead.")
180
+ SEGMENTS_TO_DETECT_CONFIG = [
181
+ (column_name, abs(threshold)) for column_name, threshold in SEGMENTS_TO_DETECT_CONFIG
182
+ ]
183
+ if THRESHOLD_CV_SEGMENTS_0A_END < 0.0:
184
+ logger.warning(
185
+ f"THRESHOLD_CV_SEGMENTS_0A_END is negative, using abs({THRESHOLD_CV_SEGMENTS_0A_END}) instead."
186
+ )
187
+ THRESHOLD_CV_SEGMENTS_0A_END = abs(THRESHOLD_CV_SEGMENTS_0A_END)
188
+ if THRESHOLD_CONSOLE_PRINTS_CV_CHECK < 0:
189
+ logger.warning(
190
+ f"THRESHOLD_CONSOLE_PRINTS_CV_CHECK is negative, using abs({THRESHOLD_CONSOLE_PRINTS_CV_CHECK}) instead."
191
+ )
192
+ THRESHOLD_CONSOLE_PRINTS_CV_CHECK = abs(THRESHOLD_CONSOLE_PRINTS_CV_CHECK)
193
+ if THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK < 0:
194
+ logger.warning(
195
+ f"THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK is negative, using abs({THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK}) instead."
196
+ )
197
+ THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK = abs(THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK)
198
+ if THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH < 0:
199
+ logger.warning(
200
+ f"THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH is negative, using abs({THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH}) instead."
201
+ )
202
+ THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH = abs(THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH)
203
+ if THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK < 0:
204
+ logger.warning(
205
+ f"THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK is negative, using abs({THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK}) instead."
206
+ )
207
+ THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK = abs(THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK)
208
+
209
+ if PRECOMPILE:
210
+ if len(df_step) > 100_000 or FORCE_PRECOMPILATION:
211
+ with log_time(
212
+ "precompiling step_analyzer_primitives and df_primitives_correction", SHOW_RUNTIME=SHOW_RUNTIME
213
+ ):
214
+ _precompile_step_analyzer()
215
+
216
+ else:
217
+ logger.warning(
218
+ f"Input dataframe is small ({len(df_step)} < 100_000 rows. Skipping precompilation unless you set FORCE_PRECOMPILATION = True."
219
+ )
220
+
221
+ if SHOW_RUNTIME:
222
+ logger.info(f"detecting segments in dataframe of size {len(df_step)}...")
223
+
224
+ with log_time("calculating Power[W]", SHOW_RUNTIME=SHOW_RUNTIME):
225
+ df_step["Power[W]"] = df_step["Voltage[V]"] * df_step["Current[A]"]
226
+
227
+ for column_name, threshold in SEGMENTS_TO_DETECT_CONFIG:
228
+ with log_time(
229
+ f"separating {column_name} into segments using incremental linear fit", SHOW_RUNTIME=SHOW_RUNTIME
230
+ ):
231
+ df_step = _split_in_segments_using_incremental_linear_fit(
232
+ df=df_step, column_name=column_name, threshold=threshold
233
+ )
234
+
235
+ keep_max_segment_id_config = []
236
+ for _, col_name in DATA_COLUMNS.items():
237
+ segment_col = f"Segment_{col_name}"
238
+ with log_time(f"adding Length_{segment_col} to calculate the dominating segments", SHOW_RUNTIME=SHOW_RUNTIME):
239
+ df_step = _add_segment_lengths(df=df_step, column_name=col_name)
240
+ keep_max_segment_id_config.append((f"Length_{segment_col}", segment_col))
241
+
242
+ with log_time("suppressing smaller segments", SHOW_RUNTIME=SHOW_RUNTIME):
243
+ df_step = _keep_max_segment_id(df=df_step, keep_max_segment_id_config=keep_max_segment_id_config)
244
+
245
+ with log_time("attempting to merge neighboring segments", SHOW_RUNTIME=SHOW_RUNTIME):
246
+ df_step = _attempt_to_merge_neighboring_segments(df=df_step, adjust_segments_config=ADJUST_SEGMENTS_CONFIG)
247
+
248
+ with log_time("fine tuning width of constant segments to better fit the data", SHOW_RUNTIME=SHOW_RUNTIME):
249
+ df_step = _widen_constant_segments(
250
+ df=df_step,
251
+ adjust_segments_config=ADJUST_SEGMENTS_CONFIG,
252
+ Threshold_segments_to_print=THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH,
253
+ supress_IO_warnings=supress_IO_warnings,
254
+ )
255
+
256
+ if SHOW_RUNTIME:
257
+ logger.info("starting annotation...")
258
+
259
+ df_primitives = _annotate_primitives(
260
+ df_step, data_columns=DATA_COLUMNS, thresholds=THRESHOLDS_PRIMITIVE_ANNOTATION, show_runtime=SHOW_RUNTIME
261
+ )
262
+
263
+ with log_time("dropping temporary length and segment columns", SHOW_RUNTIME=SHOW_RUNTIME):
264
+ columns_to_drop = [f"Length_Segment_{v}" for v in DATA_COLUMNS.values()] + [
265
+ f"Segment_{v}" for v in DATA_COLUMNS.values()
266
+ ]
267
+ df_primitives = df_primitives.drop(columns=columns_to_drop)
268
+
269
+ # Can be removed if we choose to always apply these additional corrections
270
+ if check_CV_0Aend_segments_bool or check_Power_zero_W_segments_bool or check_zero_length_segments_bool:
271
+ if SHOW_RUNTIME:
272
+ logger.info("starting additional data checks and corrections...")
273
+ else:
274
+ if not supress_IO_warnings:
275
+ logger.warning("Skipping additional data checks and corrections...")
276
+
277
+ if check_CV_0Aend_segments_bool:
278
+ df_primitives = _check_CV_0Aend_segments(
279
+ df_primitives=df_primitives,
280
+ tolerance=THRESHOLD_CV_SEGMENTS_0A_END,
281
+ SHOW_RUNTIME=SHOW_RUNTIME,
282
+ DATA_COLUMNS=DATA_COLUMNS,
283
+ THRESHOLDS_PRIMITIVE_ANNOTATION=THRESHOLDS_PRIMITIVE_ANNOTATION,
284
+ supress_IO_warnings=supress_IO_warnings,
285
+ THRESHOLD_CONSOLE_PRINTS_CV_CHECK=THRESHOLD_CONSOLE_PRINTS_CV_CHECK,
286
+ THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK=THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK,
287
+ )
288
+
289
+ if check_zero_length_segments_bool:
290
+ df_primitives = _check_zero_length_segments(
291
+ df_primitives,
292
+ SHOW_RUNTIME=SHOW_RUNTIME,
293
+ DATA_COLUMNS=DATA_COLUMNS,
294
+ THRESHOLDS_PRIMITIVE_ANNOTATION=THRESHOLDS_PRIMITIVE_ANNOTATION,
295
+ supress_IO_warnings=supress_IO_warnings,
296
+ THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK=THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK,
297
+ )
298
+
299
+ if check_Power_zero_W_segments_bool:
300
+ df_primitives = _check_power_zero_watt_segments(
301
+ df_primitives=df_primitives,
302
+ SHOW_RUNTIME=SHOW_RUNTIME,
303
+ THRESHOLDS_PRIMITIVE_ANNOTATION=THRESHOLDS_PRIMITIVE_ANNOTATION,
304
+ supress_IO_warnings=supress_IO_warnings,
305
+ THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK=THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK,
306
+ DATA_COLUMNS=DATA_COLUMNS,
307
+ )
308
+
309
+ if check_CV_0Aend_segments_bool or check_zero_length_segments_bool or check_Power_zero_W_segments_bool:
310
+ with log_time("updating annotations", SHOW_RUNTIME=SHOW_RUNTIME):
311
+ df_primitives = _merged_annotations(
312
+ df=df_primitives, data_columns=DATA_COLUMNS, thresholds=THRESHOLDS_PRIMITIVE_ANNOTATION
313
+ )
314
+
315
+ return df_primitives
316
+
317
+
318
+ def extract_sequence_overview(
319
+ df_primitives: pd.DataFrame,
320
+ SEGMENT_SEQUENCE_CONFIG: dict = None,
321
+ SHOW_RUNTIME: bool = True,
322
+ ) -> pd.DataFrame:
323
+ """
324
+
325
+ Create a DataFrame of segments and sequences from a DataFrame of primitives. (ID, longest sequence, segments/sequence)
326
+ Each Segment/Sequence is listed per ID with an incrementally increasing value per match.
327
+
328
+ The rules dictionary can contain the following keys:
329
+ - "min_length_sec": minimum length of the segment in seconds
330
+ - "min_avg_abs": minimum average absolute value of the segment
331
+ - "max_slope": maximum slope of the segment
332
+ - "min_abs_slope": minimum absolute slope of the segment
333
+ - "direction": direction of the segment
334
+ - "variable": variable name of the segment
335
+ - "type": type of the segment
336
+
337
+ "loop": True can be used to support looping sequences
338
+ "merge": True can be used to support merging multiple subsequences and segments
339
+
340
+
341
+ Parameters:
342
+ df_primitives (pd.DataFrame): A DataFrame of primitives created using step_analyzer_primitives(). With the following columns:
343
+ standard_columns = ['Test_Time[s]', 'Voltage[V]', 'Current[A]', 'Power[W]', "ID", "Variable", "Duration", "Length", "Min", "Max", "Avg", "Type", "Direction", "Slope"]
344
+ SEGMENT_SEQUENCE_CONFIG (dict): A dictionary containing the configuration for the analysis.
345
+ Example:{{"Current": {"rules": {"variable": "I", ...}}}, {"Discharge_iOCV": {"loop": True, "minimum_IDs": 4, "sequence": ["CC_Discharge","Pause"]}}, ...}
346
+ SHOW_RUNTIME (bool): If True, the function logs the time taken to perform each step.
347
+
348
+ Returns:
349
+ df_segments_and_sequences (pd.DataFrame): A DataFrame containing the columns 'ID', 'Sequence', and all columns
350
+ specified in SEGMENT_SEQUENCE_CONFIG.
351
+
352
+ """
353
+ if SEGMENT_SEQUENCE_CONFIG is None or not isinstance(SEGMENT_SEQUENCE_CONFIG, dict):
354
+ raise ValueError("SEGMENT_SEQUENCE_CONFIG is None or not a dict")
355
+
356
+ if df_primitives is None or not isinstance(df_primitives, pd.DataFrame):
357
+ raise ValueError("df_primitives is None or not a DataFrame")
358
+
359
+ standard_columns = [
360
+ "Test_Time[s]",
361
+ "Voltage[V]",
362
+ "Current[A]",
363
+ "Power[W]",
364
+ "ID",
365
+ "Variable",
366
+ "Duration",
367
+ "Length",
368
+ "Min",
369
+ "Max",
370
+ "Avg",
371
+ "Type",
372
+ "Direction",
373
+ "Slope",
374
+ ]
375
+ if not set(standard_columns).issubset(set(df_primitives.columns)):
376
+ logger.warning("df_primitives doesn't have the standard columns.")
377
+
378
+ if SHOW_RUNTIME:
379
+ logger.info("analyzing segments...")
380
+ with log_time("filtering by ID", SHOW_RUNTIME=SHOW_RUNTIME):
381
+ df_ID_filtered = df_primitives.loc[df_primitives.groupby("ID")["ID"].idxmin()]
382
+
383
+ # Not with log_time() since it's handled internally
384
+ df_segments_and_sequences = _analyze_segments(
385
+ df=df_ID_filtered, SHOW_RUNTIME=SHOW_RUNTIME, SEGMENT_SEQUENCE_CONFIG=SEGMENT_SEQUENCE_CONFIG
386
+ )
387
+
388
+ return df_segments_and_sequences
389
+
390
+
391
+ def _precompile_step_analyzer() -> None:
392
+ """
393
+ Precompilation of the step analyzer function using dummy data.
394
+
395
+ This function precompiles the numba.njit subfunctions of the step analyzer using dummy data.
396
+ This is useful for having *consistent* runtimes when these subfunctions are called to run multiple times.
397
+
398
+ Parameters:
399
+ None
400
+
401
+ Returns:
402
+ None
403
+ """
404
+ from pydpeet.process.sequence.utils.configs.CONFIG_preprocessing import (
405
+ DATA_COLUMNS,
406
+ SEGMENT_SEQUENCE_CONFIG,
407
+ STEP_ANALYZER_PRIMITIVES_CONFIG_PRECOMPILE,
408
+ THRESHOLDS_PRIMITIVE_ANNOTATION,
409
+ )
410
+
411
+ # precompile using dummy data
412
+ _project_dir = os.path.dirname(os.path.abspath(__file__))
413
+ _res_dir = os.path.join(_project_dir, "../../res")
414
+ _input_path = os.path.join(_res_dir, "precompile_dummy_data.parquet")
415
+ _df_file = pd.read_parquet(_input_path)
416
+ _df_primitives = add_primitive_segments(
417
+ df=_df_file,
418
+ STEP_ANALYZER_PRIMITIVES_CONFIG=STEP_ANALYZER_PRIMITIVES_CONFIG_PRECOMPILE,
419
+ SHOW_RUNTIME=False,
420
+ check_CV_0Aend_segments_bool=True,
421
+ check_zero_length_segments_bool=True,
422
+ supress_IO_warnings=True,
423
+ PRECOMPILE=False,
424
+ )
425
+
426
+ correction_config = {
427
+ "replace_ID": {
428
+ 1: "V",
429
+ }
430
+ }
431
+ _df_primitives = df_primitives_correction(
432
+ df_primitives=_df_primitives,
433
+ correction_config=correction_config,
434
+ data_columns=DATA_COLUMNS,
435
+ thresholds=THRESHOLDS_PRIMITIVE_ANNOTATION,
436
+ )
437
+
438
+ _ = extract_sequence_overview(
439
+ df_primitives=_df_primitives, SEGMENT_SEQUENCE_CONFIG=SEGMENT_SEQUENCE_CONFIG, SHOW_RUNTIME=False
440
+ )
@@ -0,0 +1,7 @@
1
+ """
2
+ Auto-generated __init__ file.
3
+ Created: 2026-03-06 15:11:49
4
+ """
5
+
6
+ # Restrictive package init: start with no public API
7
+ __all__ = []
@@ -0,0 +1,7 @@
1
+ """
2
+ Auto-generated __init__ file.
3
+ Created: 2026-03-06 15:11:49
4
+ """
5
+
6
+ # Restrictive package init: start with no public API
7
+ __all__ = []