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.
- pydpeet/__init__.py +56 -0
- pydpeet/citations/__init__.py +7 -0
- pydpeet/citations/citeme.py +397 -0
- pydpeet/citations/html_writer.py +137 -0
- pydpeet/dev_utils/__init__.py +7 -0
- pydpeet/dev_utils/generate_inits/__init__.py +7 -0
- pydpeet/dev_utils/generate_inits/generate_inits.py +327 -0
- pydpeet/io/__init__.py +7 -0
- pydpeet/io/configs/__init__.py +7 -0
- pydpeet/io/configs/config.py +227 -0
- pydpeet/io/configs/const.py +1 -0
- pydpeet/io/convert.py +594 -0
- pydpeet/io/device/__init__.py +7 -0
- pydpeet/io/device/arbin_4_23_PV090331/__init__.py +7 -0
- pydpeet/io/device/arbin_4_23_PV090331/formatter.py +22 -0
- pydpeet/io/device/arbin_4_23_PV090331/mapper.py +18 -0
- pydpeet/io/device/arbin_4_23_PV090331/reader.py +68 -0
- pydpeet/io/device/arbin_8_00_PV221201/__init__.py +7 -0
- pydpeet/io/device/arbin_8_00_PV221201/formatter.py +26 -0
- pydpeet/io/device/arbin_8_00_PV221201/mapper.py +18 -0
- pydpeet/io/device/arbin_8_00_PV221201/reader.py +66 -0
- pydpeet/io/device/basytec_6_3_1_0/__init__.py +7 -0
- pydpeet/io/device/basytec_6_3_1_0/formatter.py +18 -0
- pydpeet/io/device/basytec_6_3_1_0/mapper.py +18 -0
- pydpeet/io/device/basytec_6_3_1_0/reader.py +54 -0
- pydpeet/io/device/digatron_4_20_6_236/__init__.py +7 -0
- pydpeet/io/device/digatron_4_20_6_236/formatter.py +27 -0
- pydpeet/io/device/digatron_4_20_6_236/mapper.py +18 -0
- pydpeet/io/device/digatron_4_20_6_236/reader.py +45 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/__init__.py +7 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/formatter.py +39 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/mapper.py +18 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/reader.py +42 -0
- pydpeet/io/device/neware_8_0_0_516/__init__.py +7 -0
- pydpeet/io/device/neware_8_0_0_516/formatter.py +31 -0
- pydpeet/io/device/neware_8_0_0_516/mapper.py +18 -0
- pydpeet/io/device/neware_8_0_0_516/reader.py +397 -0
- pydpeet/io/device/parstat_2_63_3/__init__.py +7 -0
- pydpeet/io/device/parstat_2_63_3/formatter.py +34 -0
- pydpeet/io/device/parstat_2_63_3/mapper.py +18 -0
- pydpeet/io/device/parstat_2_63_3/reader.py +34 -0
- pydpeet/io/device/safion_1_9/__init__.py +7 -0
- pydpeet/io/device/safion_1_9/formatter.py +25 -0
- pydpeet/io/device/safion_1_9/mapper.py +18 -0
- pydpeet/io/device/safion_1_9/reader.py +89 -0
- pydpeet/io/device/zahner/__init__.py +7 -0
- pydpeet/io/device/zahner/formatter.py +73 -0
- pydpeet/io/device/zahner/mapper.py +34 -0
- pydpeet/io/device/zahner/reader.py +46 -0
- pydpeet/io/device/zahner_new/__init__.py +7 -0
- pydpeet/io/device/zahner_new/formatter.py +72 -0
- pydpeet/io/device/zahner_new/mapper.py +50 -0
- pydpeet/io/device/zahner_new/reader.py +46 -0
- pydpeet/io/map.py +69 -0
- pydpeet/io/read.py +50 -0
- pydpeet/io/utils/__init__.py +7 -0
- pydpeet/io/utils/ext_path.py +34 -0
- pydpeet/io/utils/formatter_utils.py +429 -0
- pydpeet/io/utils/load_custom_module.py +33 -0
- pydpeet/io/utils/timing.py +36 -0
- pydpeet/io/write.py +99 -0
- pydpeet/process/__init__.py +7 -0
- pydpeet/process/analyze/__init__.py +7 -0
- pydpeet/process/analyze/average.py +297 -0
- pydpeet/process/analyze/capacity.py +208 -0
- pydpeet/process/analyze/configs/__init__.py +7 -0
- pydpeet/process/analyze/configs/battery_config.py +38 -0
- pydpeet/process/analyze/configs/ocv_config.py +177 -0
- pydpeet/process/analyze/configs/step_analyzer_config.py +178 -0
- pydpeet/process/analyze/cycle.py +63 -0
- pydpeet/process/analyze/efficiency.py +182 -0
- pydpeet/process/analyze/energy.py +51 -0
- pydpeet/process/analyze/extract/__init__.py +7 -0
- pydpeet/process/analyze/extract/dva_ica.py +224 -0
- pydpeet/process/analyze/extract/ocv.py +196 -0
- pydpeet/process/analyze/power.py +32 -0
- pydpeet/process/analyze/resistance.py +87 -0
- pydpeet/process/analyze/soc.py +418 -0
- pydpeet/process/analyze/soh.py +72 -0
- pydpeet/process/analyze/utils.py +122 -0
- pydpeet/process/merge/__init__.py +7 -0
- pydpeet/process/merge/series.py +274 -0
- pydpeet/process/sequence/__init__.py +7 -0
- pydpeet/process/sequence/configs/__init__.py +7 -0
- pydpeet/process/sequence/configs/config.py +17 -0
- pydpeet/process/sequence/step_analyzer.py +440 -0
- pydpeet/process/sequence/utils/__init__.py +7 -0
- pydpeet/process/sequence/utils/annotate/__init__.py +7 -0
- pydpeet/process/sequence/utils/annotate/annotate_primitives.py +419 -0
- pydpeet/process/sequence/utils/configs/CONFIG_Fallback.py +231 -0
- pydpeet/process/sequence/utils/configs/CONFIG_preprocessing.py +243 -0
- pydpeet/process/sequence/utils/configs/__init__.py +7 -0
- pydpeet/process/sequence/utils/console_prints/__init__.py +7 -0
- pydpeet/process/sequence/utils/console_prints/log_time.py +30 -0
- pydpeet/process/sequence/utils/postprocessing/__init__.py +7 -0
- pydpeet/process/sequence/utils/postprocessing/df_primitives_correction.py +166 -0
- pydpeet/process/sequence/utils/postprocessing/filter_df.py +168 -0
- pydpeet/process/sequence/utils/postprocessing/generate_instructions.py +297 -0
- pydpeet/process/sequence/utils/preprocessing/__init__.py +7 -0
- pydpeet/process/sequence/utils/preprocessing/calculate_thresholds.py +27 -0
- pydpeet/process/sequence/utils/processing/__init__.py +7 -0
- pydpeet/process/sequence/utils/processing/analyze_segments.py +368 -0
- pydpeet/process/sequence/utils/processing/attempt_to_merge_neighboring_segments.py +122 -0
- pydpeet/process/sequence/utils/processing/check_CV_results.py +226 -0
- pydpeet/process/sequence/utils/processing/check_power_zero_watt_segments.py +85 -0
- pydpeet/process/sequence/utils/processing/check_zero_length.py +117 -0
- pydpeet/process/sequence/utils/processing/split_in_segments.py +97 -0
- pydpeet/process/sequence/utils/processing/supress_smaller_segments.py +99 -0
- pydpeet/process/sequence/utils/processing/widen_constant_segments.py +185 -0
- pydpeet/process/sequence/utils/visualize/__init__.py +7 -0
- pydpeet/process/sequence/utils/visualize/visualize_data.py +288 -0
- pydpeet/res/__init__.py +7 -0
- pydpeet/settings.py +0 -0
- pydpeet/utils/__init__.py +7 -0
- pydpeet/utils/logging_style.py +28 -0
- pydpeet/version.py +0 -0
- pydpeet-0.2.0.dist-info/METADATA +85 -0
- pydpeet-0.2.0.dist-info/RECORD +122 -0
- pydpeet-0.2.0.dist-info/WHEEL +5 -0
- pydpeet-0.2.0.dist-info/licenses/AUTHORS.md +0 -0
- pydpeet-0.2.0.dist-info/licenses/LICENCE.md +27 -0
- 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
|
+
)
|