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,274 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
+ from pydpeet.process.analyze.utils import (
7
+ StepTimer,
8
+ drop_duplicate_testtime,
9
+ )
10
+
11
+
12
+ def merge_into_series(
13
+ dfs: list[pd.DataFrame],
14
+ time_between_tests_seconds: float = 60.0,
15
+ verbose: bool = True,
16
+ sort_dfs: bool = True,
17
+ ) -> pd.DataFrame:
18
+ """
19
+ Run a list of DataFrames as a single aging test series.
20
+
21
+ Each DataFrame is treated as a single test in the series. The 'Test_Time[s]' column of each DataFrame is adjusted to create a continuous time axis. A pause row is added after each DataFrame (except the last one) to indicate a pause in the test series.
22
+
23
+ The function returns a single DataFrame with the merged test series.
24
+
25
+ Parameters:
26
+ df_list (List[List[pandas.DataFrame]]): List of DataFrames (or list of (df, filename) pairs).
27
+ time_between_tests_seconds (float, optional): Time between tests in seconds. Defaults to 60.0.
28
+ verbose (bool, optional): If True, log debug messages. Defaults to True.
29
+ sort_dfs (bool, optional): If True, sort the DataFrames by their 'Date_Time' column. Defaults to True.
30
+
31
+ Returns:
32
+ pandas.DataFrame: Merged DataFrame with a single test series.
33
+ """
34
+ if not dfs:
35
+ logging.info("No DataFrames provided.")
36
+ return pd.DataFrame()
37
+
38
+ if time_between_tests_seconds <= 0:
39
+ raise ValueError("time_between_tests_seconds must be > 0")
40
+
41
+ first_item = dfs[0]
42
+ if isinstance(first_item, tuple | list) and len(first_item) == 2:
43
+ # Already (df, filename) pairs
44
+ input_pairs = dfs
45
+ else:
46
+ # Wrap plain DataFrames with None as filename
47
+ input_pairs = [(df, None) for df in dfs]
48
+
49
+ if verbose:
50
+ logging.info("Sorting DataFrames...")
51
+
52
+ if sort_dfs:
53
+ sorted_dfs = _sort_dfs(input_pairs, verbose=verbose)
54
+ else:
55
+ # Keep only the DataFrame from the pairs (df, filename)
56
+ sorted_dfs = [df for df, _ in input_pairs]
57
+
58
+ time_offset = 0.0
59
+ numeric_cols, datetime_cols, object_cols = [], [], []
60
+ col_order = []
61
+ numeric_storage = []
62
+ other_storage = {}
63
+ valid_index = 0 # counts only valid DataFrames
64
+
65
+ if verbose:
66
+ logging.info(f"Adjusting Testtime and adding {time_between_tests_seconds}s pauses...")
67
+
68
+ with StepTimer(verbose) as st:
69
+ for i, raw_df in enumerate(sorted_dfs):
70
+ if raw_df is None or raw_df.empty or "Test_Time[s]" not in raw_df.columns:
71
+ continue
72
+
73
+ df = raw_df.copy()
74
+
75
+ # Skip battery description files (existing behavior)
76
+ if "Step_Count" in df.columns:
77
+ first_val = df["Step_Count"].iloc[0]
78
+ if isinstance(first_val, str) and first_val.lower() == "test":
79
+ logging.warning(f"Skipping DataFrame {i} because Step_Count starts with 'Test'")
80
+ continue
81
+
82
+ last_valid_idx = df["Test_Time[s]"].last_valid_index()
83
+ if last_valid_idx is None:
84
+ logging.info(f"DataFrame {i} skipped: 'Test_Time[s]' is all NaN")
85
+ continue
86
+
87
+ # Initialize column types only once
88
+ if not numeric_cols:
89
+ col_order = df.columns.tolist()
90
+ for column in df.columns:
91
+ dt = df[column].dtype
92
+ if pd.api.types.is_numeric_dtype(dt):
93
+ numeric_cols.append(column)
94
+ elif pd.api.types.is_datetime64_any_dtype(dt):
95
+ datetime_cols.append(column)
96
+ else:
97
+ object_cols.append(column)
98
+ other_storage = {c: [] for c in datetime_cols + object_cols}
99
+
100
+ # Convert Step_Count to int64
101
+ if "Step_Count" in df.columns:
102
+ df["Step_Count"] = pd.to_numeric(df["Step_Count"], errors="coerce").fillna(0).astype(np.int64)
103
+
104
+ # Ensure Test_Time[s] is numeric and safe for addition
105
+ df["Test_Time[s]"] = pd.to_numeric(df["Test_Time[s]"], errors="coerce").fillna(0.0)
106
+
107
+ # --- Apply the current cumulative offset to this DataFrame ---
108
+ df["Test_Time[s]"] = df["Test_Time[s]"] + time_offset
109
+
110
+ # Assign sequential TestIndex
111
+ df["TestIndex"] = valid_index
112
+ valid_index += 1
113
+
114
+ # Ensure TestIndex is tracked in numeric columns / col_order
115
+ if "TestIndex" not in numeric_cols:
116
+ numeric_cols.append("TestIndex")
117
+ if "TestIndex" not in col_order:
118
+ col_order.append("TestIndex")
119
+
120
+ # Store numeric and other columns
121
+ numeric_storage.append(df[numeric_cols].to_numpy())
122
+ for column in datetime_cols + object_cols:
123
+ other_storage[column].extend(df[column].tolist())
124
+
125
+ # Compute the maximum Testtime after offset (this is the basis for next offset)
126
+ df_max = df["Test_Time[s]"].dropna().max()
127
+
128
+ # If not the last DataFrame, add a pause row placed at df_max + time_between_tests_seconds
129
+ if i < len(sorted_dfs) - 1:
130
+ pause_value = float(df_max + time_between_tests_seconds)
131
+ pause_numeric = []
132
+ for column in numeric_cols:
133
+ if column == "Test_Time[s]":
134
+ pause_numeric.append(pause_value)
135
+ elif column == "TestIndex":
136
+ pause_numeric.append(-1)
137
+ else:
138
+ pause_numeric.append(np.nan)
139
+ # ensure shape matches (1, n_numeric_cols)
140
+ numeric_storage.append(np.array([pause_numeric]))
141
+
142
+ for column in datetime_cols:
143
+ other_storage[column].append(pd.NaT)
144
+ for column in object_cols:
145
+ other_storage[column].append(pd.NA)
146
+
147
+ if verbose:
148
+ st.log(f"Added pause row after DataFrame {i} at {pause_value}s")
149
+
150
+ # --- Set time_offset for the next DataFrame to df_max + pause ---
151
+ time_offset = df_max + time_between_tests_seconds
152
+ else:
153
+ # Last DataFrame: update time_offset to the end of this df (no extra pause needed)
154
+ time_offset = df_max
155
+
156
+ # If no valid numeric data, return empty DataFrame
157
+ if not numeric_storage:
158
+ return pd.DataFrame()
159
+
160
+ with StepTimer(verbose) as st:
161
+ logging.info("Merging DataFrames...")
162
+ numeric_array = np.vstack(numeric_storage)
163
+
164
+ data_dict = {}
165
+ for idx, column in enumerate(numeric_cols):
166
+ data_dict[column] = numeric_array[:, idx]
167
+ logging.info(f"Merged numeric column: {column}")
168
+ for column in datetime_cols + object_cols:
169
+ data_dict[column] = other_storage[column]
170
+ logging.info(f"Merged non-numeric column: {column}")
171
+
172
+ dfs_merged = pd.DataFrame(data_dict, columns=col_order)
173
+ dfs_merged["Test_Time[s]"] = dfs_merged["Test_Time[s]"].astype(float)
174
+ st.log(f"Final merged DataFrame shape: {dfs_merged.shape}")
175
+
176
+ dfs_merged = drop_duplicate_testtime(dfs_merged)
177
+
178
+ return dfs_merged
179
+
180
+
181
+ def _sort_dfs(
182
+ dfs: list[pd.DataFrame],
183
+ verbose: bool = True,
184
+ ) -> list[pd.DataFrame]:
185
+ """
186
+ Sorts a list of DataFrames by their 'Date_Time' column.
187
+ Falls back to filename order if no valid absolute time is found.
188
+
189
+ Parameters
190
+ ----------
191
+ df_list : list of pandas.DataFrame or list of (DataFrame, str)
192
+ List of DataFrames to be sorted. Optionally, pass (df, filename) tuples
193
+ so filenames can be used as a fallback sorting key.
194
+
195
+ Returns
196
+ -------
197
+ list of pandas.DataFrame
198
+ Sorted list of DataFrames.
199
+ """
200
+ if not dfs:
201
+ return []
202
+
203
+ time_col = "Date_Time"
204
+
205
+ # ensure df_list always work with (df, filename) pairs
206
+ if isinstance(dfs[0], tuple | list) and len(dfs[0]) == 2:
207
+ pairs = [(df, fn) for df, fn in dfs if df is not None and not df.empty]
208
+ else:
209
+ pairs = [(df, None) for df in dfs if df is not None and not df.empty]
210
+
211
+ if not pairs:
212
+ logging.warning("No valid DataFrames found.")
213
+ return []
214
+
215
+ # Check if at least one DF has a valid absolute time column
216
+ has_abs_time = all(time_col in df.columns and df[time_col].notna().any() for df, _ in pairs)
217
+
218
+ if has_abs_time:
219
+ if verbose:
220
+ logging.info("Sorting DataFrames by absolute time...")
221
+ sorted_pairs = sorted(
222
+ pairs,
223
+ key=lambda x: (
224
+ pd.to_datetime(x[0][time_col].dropna().iloc[0])
225
+ if time_col in x[0].columns and x[0][time_col].notna().any()
226
+ else pd.Timestamp.min
227
+ ),
228
+ )
229
+ else:
230
+ if any(fn is not None for _, fn in pairs):
231
+ if verbose:
232
+ logging.info("No valid absolute time found, sorting by filename...")
233
+ sorted_pairs = sorted(pairs, key=lambda x: x[1] or "")
234
+ else:
235
+ if verbose:
236
+ logging.info("No valid absolute time found, keeping original order...")
237
+ sorted_pairs = pairs
238
+
239
+ if verbose:
240
+ logging.info("Sorting complete.")
241
+
242
+ return [df for df, _ in sorted_pairs]
243
+
244
+
245
+ def merge_into_campaign(
246
+ dfs_list: list[list[pd.DataFrame]],
247
+ verbose: bool = True,
248
+ ) -> list[pd.DataFrame]:
249
+ """
250
+ Execute a list of test series and return a list of merged DataFrames.
251
+
252
+ Parameters:
253
+ -----------
254
+ test_series_list : List[List[pandas.DataFrame]]
255
+ List of lists of DataFrames, where each DataFrame represents a single test.
256
+ verbose : bool, optional
257
+ If True, log debug messages.
258
+
259
+ Returns:
260
+ --------
261
+ List of pandas.DataFrame
262
+ List of DataFrames, where each DataFrame represents a merged test series.
263
+ """
264
+ test_campaigns = []
265
+ for test in dfs_list:
266
+ with StepTimer(verbose) as st:
267
+ test_campaign = merge_into_series(test, verbose=verbose)
268
+ st.log("Executed one test series")
269
+ test_campaigns.append(test_campaign)
270
+
271
+ if verbose:
272
+ logging.info(f"Completed campaign with {len(test_campaigns)} test series.")
273
+
274
+ return test_campaigns
@@ -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__ = []
@@ -0,0 +1,17 @@
1
+ BASYTEC_CTS = [ # BaSyTec
2
+ 0.002, # ACCURACY_VOLTAGE_SIGNAL
3
+ 0.003, # ACCURACY_CURRENT_SIGNAL
4
+ 0.002, # ACCURACY_VOLTAGE_MEASUREMENT
5
+ 0.003, # ACCURACY_CURRENT_MEASUREMENT
6
+ 6, # FS_VOLTAGE
7
+ 5, # FS_CURRENT
8
+ ]
9
+
10
+ NEWARE_CT_4008Q_5V12A_S1 = [
11
+ 0.0005, # ACCURACY_VOLTAGE_SIGNAL
12
+ 0.0005, # ACCURACY_CURRENT_SIGNAL
13
+ 0.0005, # ACCURACY_VOLTAGE_MEASUREMENT
14
+ 0.0005, # ACCURACY_CURRENT_MEASUREMENT
15
+ 5, # FS_VOLTAGE
16
+ 3, # FS_CURRENT
17
+ ]