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,297 @@
1
+ import logging
2
+
3
+ import pandas as pd
4
+
5
+ from pydpeet.process.sequence.step_analyzer import extract_sequence_overview
6
+
7
+ SEGMENTS_CONFIG_STANDARD: dict[str, dict] = {
8
+ "Pause": {
9
+ "rules": {
10
+ "type": "Rest",
11
+ }
12
+ },
13
+ "CC_Charge": {
14
+ "rules": {
15
+ "variable": "I",
16
+ "type": "Constant",
17
+ "direction": "Charge",
18
+ }
19
+ },
20
+ "CV_Charge": {
21
+ "rules": {
22
+ "variable": "V",
23
+ "type": "Constant",
24
+ "direction": "Charge",
25
+ }
26
+ },
27
+ "CP_Charge": {
28
+ "rules": {
29
+ "variable": "P",
30
+ "type": "Constant",
31
+ "direction": "Charge",
32
+ }
33
+ },
34
+ "CC_Discharge": {
35
+ "rules": {
36
+ "variable": "I",
37
+ "type": "Constant",
38
+ "direction": "Discharge",
39
+ }
40
+ },
41
+ "CV_Discharge": {
42
+ "rules": {
43
+ "variable": "V",
44
+ "type": "Constant",
45
+ "direction": "Discharge",
46
+ }
47
+ },
48
+ "CP_Discharge": {
49
+ "rules": {
50
+ "variable": "P",
51
+ "type": "Constant",
52
+ "direction": "Discharge",
53
+ }
54
+ },
55
+ "CRamp_Charge": {
56
+ "rules": {
57
+ "variable": "I",
58
+ "type": "Ramp",
59
+ "direction": "Up",
60
+ }
61
+ },
62
+ "VRamp_Charge": {
63
+ "rules": {
64
+ "variable": "V",
65
+ "type": "Ramp",
66
+ "direction": "Up",
67
+ }
68
+ },
69
+ "PRamp_Charge": {
70
+ "rules": {
71
+ "variable": "P",
72
+ "type": "Ramp",
73
+ "direction": "Up",
74
+ }
75
+ },
76
+ "CRamp_Discharge": {
77
+ "rules": {
78
+ "variable": "I",
79
+ "type": "Ramp",
80
+ "direction": "Down",
81
+ }
82
+ },
83
+ "VRamp_Discharge": {
84
+ "rules": {
85
+ "variable": "V",
86
+ "type": "Ramp",
87
+ "direction": "Down",
88
+ }
89
+ },
90
+ "PRamp_Discharge": {
91
+ "rules": {
92
+ "variable": "P",
93
+ "type": "Ramp",
94
+ "direction": "Down",
95
+ }
96
+ },
97
+ }
98
+
99
+
100
+ def _parse_segment_type(seg_type: str) -> tuple[str, str] | tuple[str, None]:
101
+ """
102
+ Parse a segment type string into its base and direction.
103
+
104
+ Parameters:
105
+ seg_type (str): segment type string
106
+
107
+ Returns:
108
+ tuple: (base, direction)
109
+
110
+ base (str): base of the segment type (e.g. CC, CV, CP, CRamp, VRamp, PRamp)
111
+ direction (str): direction of the segment type (e.g. Charge, Discharge)
112
+
113
+ Example:
114
+ _parse_segment_type("CC_Charge") -> ("CC", "Charge")
115
+ _parse_segment_type("Ramp_Current_Charge") -> ("CRamp", "Charge")
116
+ """
117
+ if seg_type in ("Pause", "Rest"):
118
+ return "Pause", None
119
+
120
+ # Case 1: already in the form CC_Charge, CRamp_Discharge, etc.
121
+ if "_" in seg_type and seg_type.split("_")[0] in {"CC", "CV", "CP", "CRamp", "VRamp", "PRamp"}:
122
+ base, *tail = seg_type.split("_")
123
+ direction = tail[-1] if tail else None
124
+
125
+ return base, direction
126
+
127
+ # Case 2: new naming scheme ► Ramp_{Current|Voltage|Power}_{Charge|Discharge}
128
+ if seg_type.startswith("Ramp_"):
129
+ _, signal, *tail = seg_type.split("_")
130
+ mapping = {"Current": "CRamp", "Voltage": "VRamp", "Power": "PRamp"}
131
+ base = mapping.get(signal, "UNKNOWN")
132
+ direction = tail[-1] if tail else None
133
+
134
+ return base, direction
135
+
136
+ # Fallback
137
+ return "UNKNOWN", None
138
+
139
+
140
+ def _get_important_entries_per_segment(
141
+ df_primitives: pd.DataFrame, df_segments_and_sequences: pd.DataFrame
142
+ ) -> pd.DataFrame:
143
+ """
144
+ Get the last entries per segment from a primitives dataframe and a segments_and_sequences dataframe.
145
+
146
+ Parameters:
147
+ df_primitives (pd.DataFrame): primitives dataframe
148
+ df_segments_and_sequences (pd.DataFrame): segments_and_sequences dataframe
149
+
150
+ Returns:
151
+ pd.DataFrame: dataframe with the last entries per segment (ID, Segment_Type, End_Value_Voltage[V],
152
+ End_Value_Current[A], End_Value_Power[W], End_Value_Length, Type, Direction, AVG_Current)
153
+
154
+ """
155
+ segment_type_cols = [c for c in df_segments_and_sequences.columns if c not in ["ID", "Sequence"]]
156
+
157
+ records = []
158
+ for _, row in df_segments_and_sequences.iterrows():
159
+ seg_id = row["ID"]
160
+ segment_type = row[segment_type_cols].idxmax()
161
+
162
+ segment_df = df_primitives[df_primitives["ID"] == seg_id]
163
+ last_row = segment_df.iloc[-1]
164
+
165
+ records.append(
166
+ {
167
+ "ID": seg_id,
168
+ "Segment_Type": segment_type,
169
+ "End_Value_Voltage[V]": last_row["Voltage[V]"],
170
+ "End_Value_Current[A]": last_row["Current[A]"],
171
+ "End_Value_Power[W]": last_row["Power[W]"],
172
+ "End_Value_Length": last_row["Length"],
173
+ "Type": last_row["Type"],
174
+ "Direction": last_row["Direction"],
175
+ "AVG_Current": segment_df["Current[A]"].mean(),
176
+ # "AVG_Voltage": segment_df["Voltage[V]"].mean(),
177
+ # "AVG_Power": segment_df["Power[W]"].mean(),
178
+ }
179
+ )
180
+
181
+ dataframe_records = pd.DataFrame(records)
182
+
183
+ return dataframe_records
184
+
185
+
186
+ def generate_instructions(
187
+ df_primitives: pd.DataFrame,
188
+ end_condition_map: dict = None,
189
+ threshold_warnings: int = 5,
190
+ ) -> list[str]:
191
+ """
192
+ Generate PyBaMM instructions based on the given primitives dataframe and end condition map.
193
+ Replaces Ramps with AVG Current for time.
194
+
195
+ Parameters:
196
+ df_primitives (pd.DataFrame): primitives dataframe
197
+ end_condition_map (dict): end condition map (key: segment type, value: end condition)
198
+ Example: {"CC": "voltage", "CV": "current", "CP": "voltage","Pause": "time"}
199
+
200
+ Returns:
201
+ list: list of instructions
202
+ """
203
+ # Set default values for mutable data structures
204
+ if end_condition_map is None:
205
+ end_condition_map = {
206
+ "CC": "voltage",
207
+ "CV": "current",
208
+ "CP": "voltage",
209
+ "Pause": "time",
210
+ }
211
+ df_segments_and_sequences = extract_sequence_overview(
212
+ df_primitives, SEGMENT_SEQUENCE_CONFIG=SEGMENTS_CONFIG_STANDARD
213
+ )
214
+ results = _get_important_entries_per_segment(df_primitives, df_segments_and_sequences)
215
+ instructions = []
216
+ unknown_seen = False
217
+
218
+ too_many_warnings = 0
219
+ for _, row in results.iterrows():
220
+ raw_seg_type = row["Segment_Type"]
221
+ base_type, direction = _parse_segment_type(raw_seg_type)
222
+
223
+ if base_type == "UNKNOWN":
224
+ instructions.append(
225
+ f"\033[91mUnknown segment type '{raw_seg_type}' " f"for {row['End_Value_Length']}s encountered\033[0m"
226
+ )
227
+ unknown_seen = True
228
+ continue
229
+
230
+ current = row["AVG_Current"] if "Ramp" in base_type else row["End_Value_Current[A]"]
231
+ length = row["End_Value_Length"]
232
+
233
+ # Determine end condition
234
+ end_condition = end_condition_map.get(base_type.replace("Ramp", ""), "time")
235
+
236
+ # TODO: Docstring
237
+ def build_instruction(
238
+ action: str,
239
+ value_str: str,
240
+ current: float = current,
241
+ row: pd.Series = row,
242
+ length: int = length,
243
+ end_condition: str = end_condition,
244
+ ) -> str:
245
+ if end_condition == "time":
246
+ return f"{action} at {value_str} for {length} seconds"
247
+
248
+ cond_val, units = {
249
+ "current": (abs(current), "A"),
250
+ "voltage": (abs(row["End_Value_Voltage[V]"]), "V"),
251
+ "power": (abs(row["End_Value_Power[W]"]), "W"),
252
+ }.get(end_condition, (length, "seconds"))
253
+
254
+ return f"{action} at {value_str} until {cond_val}{units}"
255
+
256
+ # Handle Ramp replacements with current-based logic
257
+ if base_type in {"CRamp", "VRamp", "PRamp"}:
258
+ if too_many_warnings < threshold_warnings:
259
+ logging.warning(f"{base_type} segment (ID: {row['ID']}) replaced by CC with Average Current")
260
+ too_many_warnings += 1
261
+ if direction == "Charge" or (direction is None and current > 0):
262
+ instructions.append(build_instruction("Charge", f"{abs(current):.3f}A"))
263
+ elif direction == "Discharge" or (direction is None and current < 0):
264
+ instructions.append(build_instruction("Discharge", f"{abs(current):.3f}A"))
265
+
266
+ # Standard CC (non-ramp current)
267
+ elif base_type == "CC":
268
+ if direction == "Charge" or (direction is None and current > 0):
269
+ instructions.append(build_instruction("Charge", f"{abs(current):.3f}A"))
270
+ elif direction == "Discharge" or (direction is None and current < 0):
271
+ instructions.append(build_instruction("Discharge", f"{abs(current):.3f}A"))
272
+
273
+ # Constant voltage (CV)
274
+ elif base_type == "CV":
275
+ voltage = row["End_Value_Voltage[V]"]
276
+ if direction == "Charge" or (direction is None and current > 0):
277
+ instructions.append(build_instruction("Hold", f"{abs(voltage):.3f}V"))
278
+ elif direction == "Discharge" or (direction is None and current < 0):
279
+ instructions.append(build_instruction("Hold", f"{abs(voltage):.3f}V"))
280
+
281
+ # Constant power (CP)
282
+ elif base_type == "CP":
283
+ power = row["End_Value_Power[W]"]
284
+ if direction == "Charge" or (direction is None and power > 0):
285
+ instructions.append(build_instruction("Charge", f"{abs(power):.2f}W"))
286
+ elif direction == "Discharge" or (direction is None and power < 0):
287
+ instructions.append(build_instruction("Discharge", f"{abs(power):.2f}W"))
288
+
289
+ # Pause/Rest
290
+ elif base_type == "Pause":
291
+ instructions.append(f"Rest for {length} seconds")
292
+ if too_many_warnings > threshold_warnings:
293
+ logging.warning("...")
294
+ if unknown_seen:
295
+ logging.warning("Unknown segment types encountered. " "Instructions may be incomplete!")
296
+
297
+ return instructions
@@ -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,27 @@
1
+ def calculate_minimum_definitive_differences(
2
+ accuracy_voltage_signal: float,
3
+ accuracy_current_signal: float,
4
+ accuracy_voltage_measurement: float,
5
+ accuracy_current_measurement: float,
6
+ fs_voltage: float,
7
+ fs_current: float,
8
+ ) -> tuple[float, float]:
9
+ """
10
+ Calculate minimum definitive differences, that can't be described by noise.
11
+
12
+ Parameters:
13
+ accuracy_voltage_signal (float): Accuracy of voltage signal/stimulus.
14
+ accuracy_current_signal (float): Accuracy of current signal/stimulus.
15
+ accuracy_voltage_measurement (float): Accuracy of voltage measurement.
16
+ accuracy_current_measurement (float): Accuracy of current measurement.
17
+ fs_voltage (float): Full-scale voltage value.
18
+ fs_current (float): Full-scale current value.
19
+
20
+ Returns:
21
+ minimum_definitive_voltage_difference (float): Minimum definitive voltage difference.
22
+ minimum_definitive_current_difference (float): Minimum definitive current difference.
23
+ """
24
+ minimum_definitive_voltage_difference = (accuracy_voltage_signal + accuracy_voltage_measurement) * fs_voltage
25
+ minimum_definitive_current_difference = (accuracy_current_signal + accuracy_current_measurement) * fs_current
26
+
27
+ return minimum_definitive_voltage_difference, minimum_definitive_current_difference
@@ -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__ = []