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,177 @@
1
+ from pydpeet.process.sequence.configs.config import NEWARE_CT_4008Q_5V12A_S1
2
+ from pydpeet.process.sequence.utils.preprocessing.calculate_thresholds import calculate_minimum_definitive_differences
3
+
4
+ THRESHOLD_DICT_Custom = [ # ARBIN_OLD
5
+ 0.0005, # ACCURACY_VOLTAGE_SIGNAL
6
+ 0.01, # ACCURACY_CURRENT_SIGNAL
7
+ 0.0005, # ACCURACY_VOLTAGE_MEASUREMENT
8
+ 0.01, # ACCURACY_CURRENT_MEASUREMENT
9
+ 5, # FS_VOLTAGE
10
+ 3, # FS_CURRENT
11
+ ]
12
+ # use THRESHOLD_DICT = THRESHOLD_DICT_Custom if you don't want to use a predefined dictionary
13
+ THRESHOLD_DICT = NEWARE_CT_4008Q_5V12A_S1 # .threshold_dict_neware
14
+ MIN_DEFINITIVE_VOLTAGE_DIFFERENCE, MIN_DEFINITIVE_CURRENT_DIFFERENCE = calculate_minimum_definitive_differences(
15
+ *THRESHOLD_DICT
16
+ )
17
+
18
+ ####### depending on the Noise needs to be adjusted even for measurements of the same device #######
19
+ SEGMENTS_TO_DETECT_CONFIG = [
20
+ # devide threshold by 2 because it's looking above and below the target line
21
+ ("Voltage[V]", MIN_DEFINITIVE_VOLTAGE_DIFFERENCE / 2),
22
+ ("Current[A]", MIN_DEFINITIVE_CURRENT_DIFFERENCE / 2),
23
+ ("Power[W]", (MIN_DEFINITIVE_VOLTAGE_DIFFERENCE + MIN_DEFINITIVE_CURRENT_DIFFERENCE) / 2),
24
+ ]
25
+ ####### depending on the Noise needs to be adjusted even for measurements of the same device #######
26
+ # ORDER IS IMPORTANT!
27
+ ADJUST_SEGMENTS_CONFIG = [
28
+ ("Voltage[V]", MIN_DEFINITIVE_VOLTAGE_DIFFERENCE),
29
+ ("Current[A]", MIN_DEFINITIVE_CURRENT_DIFFERENCE),
30
+ ("Power[W]", (MIN_DEFINITIVE_VOLTAGE_DIFFERENCE + MIN_DEFINITIVE_CURRENT_DIFFERENCE)),
31
+ ]
32
+
33
+ ####### HAS TO USE SAME KEY AS DATA_COLUMNS! only change the values of thresholds! ########
34
+ THRESHOLDS_PRIMITIVE_ANNOTATION = {
35
+ "V": MIN_DEFINITIVE_VOLTAGE_DIFFERENCE,
36
+ "I": MIN_DEFINITIVE_CURRENT_DIFFERENCE,
37
+ "P": MIN_DEFINITIVE_VOLTAGE_DIFFERENCE + MIN_DEFINITIVE_CURRENT_DIFFERENCE,
38
+ }
39
+
40
+ ########################################################################################################################
41
+ SHOW_RUNTIME = True
42
+ SHOW_RUNTIME_ANNOTATION = True
43
+ SHOW_RUNTIME_ANALYZE = True
44
+ SHOW_RUNTIME_VISUALIZATION = True
45
+ THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK = 2
46
+ THRESHOLD_CONSOLE_PRINTS_CV_CHECK = 2
47
+ ########################################################################################################################
48
+ # Visualize Data
49
+ VISUALIZE_PHASES_CONFIG = [
50
+ ("V", "blue"),
51
+ ("I", "red"),
52
+ ("P", "green"),
53
+ ]
54
+ LINE_VISUALIZATION_CONFIG = [
55
+ ("Voltage[V]", "blue", (2.4, 4.3)),
56
+ ("Current[A]", "red", (-10, 10)),
57
+ # ("Power[W]", "green", (-40, 20)),
58
+ ]
59
+ # Bilder Bachelorarbeit Text
60
+ # ("Voltage[V]", "blue", (2.4, 4.3)),
61
+ # ("Current[A]", "red", (-10, 10)),
62
+ START = 0 # 55000.0
63
+ END = 1e10 # 90000
64
+ USE_LINES_FOR_SEGMENTS = True
65
+ SHOW_COLUMN_NAMES = False
66
+ SHOW_TIME = False
67
+ SHOW_ID = False
68
+ SEGMENT_ALPHA = 0.3
69
+ WIDTH_HEIGHT_RATIO = [1.0, 0.3]
70
+ ########################################################################################################################
71
+ # Generate Instructions
72
+ END_CONDITION_MAP_GENERATE_INSTRUCTIONS = {
73
+ "CC": "voltage",
74
+ "CV": "current",
75
+ "CP": "voltage",
76
+ "Pause": "time",
77
+ }
78
+ ########################################################################################################################
79
+ SEQUENCES_CONFIG: dict[str, dict] = {
80
+ # Complex Sequences
81
+ # Loop rules: "loop": True, "exact_loops": 2, "min_loops": 2, "max_loops": 2, "minimum_IDs": 6
82
+ "Discharge_iOCV": {"loop": True, "sequence": ["Pause", "CC_Discharge"]},
83
+ "Charge_iOCV": {"loop": True, "sequence": ["Pause", "CC_Charge"]},
84
+ # "Discharge_iOCV": {"sequence": ["Discharge_iOCV_correction", "CC_Discharge","CV_Discharge"]},
85
+ }
86
+
87
+ SEGMENTS_CONFIG_STANDARD: dict[str, dict] = {
88
+ # Primitive segments
89
+ # Pause
90
+ # CC_Charge, CV_Charge, CP_Charge
91
+ # CC_Discharge, CV_Discharge, CP_Discharge
92
+ # Ramp_Current_Charge, Ramp_Voltage_Charge, Ramp_Power_Charge
93
+ # Ramp_Current_Discharge, Ramp_Voltage_Discharge, Ramp_Power_Discharge
94
+ "Pause": {
95
+ "rules": {
96
+ "type": "Rest",
97
+ }
98
+ },
99
+ "CC_Charge": {
100
+ "rules": {
101
+ "variable": "I",
102
+ "type": "Constant",
103
+ "direction": "Charge",
104
+ }
105
+ },
106
+ "CV_Charge": {
107
+ "rules": {
108
+ "variable": "V",
109
+ "type": "Constant",
110
+ "direction": "Charge",
111
+ }
112
+ },
113
+ "CC_Discharge": {
114
+ "rules": {
115
+ "variable": "I",
116
+ "type": "Constant",
117
+ "direction": "Discharge",
118
+ }
119
+ },
120
+ "CV_Discharge": {
121
+ "rules": {
122
+ "variable": "V",
123
+ "type": "Constant",
124
+ "direction": "Discharge",
125
+ }
126
+ },
127
+ "Ramp": {
128
+ "rules": {
129
+ "type": "Ramp",
130
+ }
131
+ },
132
+ "C_Charge": {
133
+ "rules": {
134
+ "variable": "I",
135
+ "direction": "Charge",
136
+ }
137
+ },
138
+ "C_Discharge": {
139
+ "rules": {
140
+ "variable": "I",
141
+ "direction": "Discharge",
142
+ }
143
+ },
144
+ "Discharge": {
145
+ "rules": {
146
+ "direction": "Charge",
147
+ }
148
+ },
149
+ }
150
+
151
+
152
+ SEGMENT_SEQUENCE_CONFIG = {
153
+ **SEQUENCES_CONFIG,
154
+ **SEGMENTS_CONFIG_STANDARD,
155
+ }
156
+
157
+ ########################################################################################################################
158
+ #### These shouldn't be changed when using ppb Dataframes ####
159
+ DATA_COLUMNS = {
160
+ "V": "Voltage[V]",
161
+ "I": "Current[A]",
162
+ "P": "Power[W]",
163
+ }
164
+ STANDARD_COLUMNS = ["Test_Time[s]", "Voltage[V]", "Current[A]"]
165
+ #### These shouldn't be changed when using ppb Dataframes ####
166
+
167
+ STEP_ANALYZER_PRIMITIVES_CONFIG = {
168
+ "SEGMENTS_TO_DETECT_CONFIG": SEGMENTS_TO_DETECT_CONFIG,
169
+ "ADJUST_SEGMENTS_CONFIG": ADJUST_SEGMENTS_CONFIG,
170
+ "THRESHOLDS_PRIMITIVE_ANNOTATION": THRESHOLDS_PRIMITIVE_ANNOTATION,
171
+ "SHOW_RUNTIME": SHOW_RUNTIME,
172
+ "SHOW_RUNTIME_ANNOTATION": SHOW_RUNTIME_ANNOTATION,
173
+ "DATA_COLUMNS": DATA_COLUMNS,
174
+ "MIN_DEFINITIVE_CURRENT_DIFFERENCE": MIN_DEFINITIVE_CURRENT_DIFFERENCE,
175
+ "THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK": THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK,
176
+ "THRESHOLD_CONSOLE_PRINTS_CV_CHECK": THRESHOLD_CONSOLE_PRINTS_CV_CHECK,
177
+ }
@@ -0,0 +1,178 @@
1
+ from pydpeet.process.sequence.configs import config
2
+ from pydpeet.process.sequence.utils.preprocessing.calculate_thresholds import calculate_minimum_definitive_differences
3
+
4
+ THRESHOLD_DICT_Custom = [ # ARBIN_OLD
5
+ 0.0005, # ACCURACY_VOLTAGE_SIGNAL
6
+ 0.01, # ACCURACY_CURRENT_SIGNAL
7
+ 0.0005, # ACCURACY_VOLTAGE_MEASUREMENT
8
+ 0.01, # ACCURACY_CURRENT_MEASUREMENT
9
+ 5, # FS_VOLTAGE
10
+ 3, # FS_CURRENT
11
+ ]
12
+ # use THRESHOLD_DICT = THRESHOLD_DICT_Custom if you don't want to use a predefined dictionary
13
+ THRESHOLD_DICT = config.NEWARE_CT_4008Q_5V12A_S1
14
+ MIN_DEFINITIVE_VOLTAGE_DIFFERENCE, MIN_DEFINITIVE_CURRENT_DIFFERENCE = calculate_minimum_definitive_differences(
15
+ *THRESHOLD_DICT
16
+ )
17
+
18
+ ####### depending on the Noise needs to be adjusted even for measurements of the same device #######
19
+ SEGMENTS_TO_DETECT_CONFIG = [
20
+ # devide threshold by 2 because it's looking above and below the target line
21
+ ("Voltage[V]", MIN_DEFINITIVE_VOLTAGE_DIFFERENCE / 2),
22
+ ("Current[A]", MIN_DEFINITIVE_CURRENT_DIFFERENCE / 2),
23
+ ("Power[W]", (MIN_DEFINITIVE_VOLTAGE_DIFFERENCE + MIN_DEFINITIVE_CURRENT_DIFFERENCE) / 2),
24
+ ]
25
+ ####### depending on the Noise needs to be adjusted even for measurements of the same device #######
26
+ # ORDER IS IMPORTANT!
27
+ ADJUST_SEGMENTS_CONFIG = [
28
+ ("Voltage[V]", MIN_DEFINITIVE_VOLTAGE_DIFFERENCE),
29
+ ("Current[A]", MIN_DEFINITIVE_CURRENT_DIFFERENCE),
30
+ ("Power[W]", (MIN_DEFINITIVE_VOLTAGE_DIFFERENCE + MIN_DEFINITIVE_CURRENT_DIFFERENCE)),
31
+ ]
32
+
33
+ ####### HAS TO USE SAME KEY AS DATA_COLUMNS! only change the values of thresholds! ########
34
+ THRESHOLDS_PRIMITIVE_ANNOTATION = {
35
+ "V": MIN_DEFINITIVE_VOLTAGE_DIFFERENCE,
36
+ "I": MIN_DEFINITIVE_CURRENT_DIFFERENCE,
37
+ "P": MIN_DEFINITIVE_VOLTAGE_DIFFERENCE + MIN_DEFINITIVE_CURRENT_DIFFERENCE,
38
+ }
39
+
40
+ ########################################################################################################################
41
+ SHOW_RUNTIME = True
42
+ SHOW_RUNTIME_ANNOTATION = True
43
+ SHOW_RUNTIME_ANALYZE = True
44
+ SHOW_RUNTIME_VISUALIZATION = True
45
+ THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK = 2
46
+ THRESHOLD_CONSOLE_PRINTS_CV_CHECK = 2
47
+ THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH = 2
48
+ THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK = 10
49
+ ########################################################################################################################
50
+ # Visualize Data
51
+ VISUALIZE_PHASES_CONFIG = [
52
+ ("V", "blue"),
53
+ ("I", "red"),
54
+ ("P", "green"),
55
+ ]
56
+ LINE_VISUALIZATION_CONFIG = [
57
+ ("Voltage[V]", "blue", (2.4, 4.3)),
58
+ ("Current[A]", "red", (-10, 10)),
59
+ # ("Power[W]", "green", (-40, 20)),
60
+ ]
61
+ # Bilder Bachelorarbeit Text
62
+ # ("Voltage[V]", "blue", (2.4, 4.3)),
63
+ # ("Current[A]", "red", (-10, 10)),
64
+ START = 0 # 55000.0
65
+ END = 1e100 # 90000
66
+ USE_LINES_FOR_SEGMENTS = True
67
+ SHOW_COLUMN_NAMES = False
68
+ SHOW_TIME = False
69
+ SHOW_ID = False
70
+ SEGMENT_ALPHA = 0.3
71
+ WIDTH_HEIGHT_RATIO = [0.6666, 0.3]
72
+ ########################################################################################################################
73
+ # Generate Instructions
74
+ END_CONDITION_MAP_GENERATE_INSTRUCTIONS = {
75
+ "CC": "voltage",
76
+ "CV": "current",
77
+ "CP": "voltage",
78
+ "Pause": "time",
79
+ }
80
+ ########################################################################################################################
81
+ SEQUENCES_CONFIG: dict[str, dict] = {
82
+ # Complex Sequences
83
+ # Loop rules: "loop": True, "exact_loops": 2, "min_loops": 2, "max_loops": 2, "minimum_IDs": 6
84
+ # "Discharge_iOCV": {"loop": True, "minimum_IDs": 4, "sequence": ["CC_Discharge","Pause"]},
85
+ # "Charge_iOCV": {"loop": True, "min_loops": 2, "sequence": ["Pause", "CC_Charge"]},
86
+ "CCCV_Charge": {"loop": False, "sequence": ["CC_Charge", "CV_Charge"]},
87
+ "CCCV_Discharge": {"loop": False, "sequence": ["CC_Discharge", "CV_Discharge"]},
88
+ "CC_Discharge_after_CC_Charge": {"loop": False, "sequence": ["CC_Charge", "CC_Discharge"]},
89
+ "CC_Discharge_after_CCCV_Charge": {"loop": False, "sequence": ["CCCV_Charge", "CC_Discharge"]},
90
+ "CC_Discharge_after_CV_Charge": {"loop": False, "sequence": ["CV_Charge", "CC_Discharge"]},
91
+ "CC_Discharge_after_CCCV_Charge_with_Pause": {"loop": False, "sequence": ["CCCV_Charge", "Pause", "CV_Discharge"]},
92
+ "CC_Discharge_after_CC_Charge_with_Pause": {"loop": False, "sequence": ["CC_Charge", "Pause", "CC_Discharge"]},
93
+ "CC_Discharge_after_CV_Charge_with_Pause": {"loop": False, "sequence": ["CV_Charge", "Pause", "CC_Discharge"]},
94
+ }
95
+ SEGMENTS_CONFIG_STANDARD: dict[str, dict] = {
96
+ # Primitive segments
97
+ # Pause
98
+ # CC_Charge, CV_Charge, CP_Charge
99
+ # CC_Discharge, CV_Discharge, CP_Discharge
100
+ # Ramp_Current_Charge, Ramp_Voltage_Charge, Ramp_Power_Charge
101
+ # Ramp_Current_Discharge, Ramp_Voltage_Discharge, Ramp_Power_Discharge
102
+ "Pause": {
103
+ "rules": {
104
+ "type": "Rest",
105
+ }
106
+ },
107
+ "CC_Charge": {
108
+ "rules": {
109
+ "variable": "I",
110
+ "type": "Constant",
111
+ "direction": "Charge",
112
+ }
113
+ },
114
+ "CV_Charge": {
115
+ "rules": {
116
+ "variable": "V",
117
+ "type": "Constant",
118
+ "direction": "Charge",
119
+ }
120
+ },
121
+ "CP_Charge": {
122
+ "rules": {
123
+ "variable": "P",
124
+ "type": "Constant",
125
+ "direction": "Charge",
126
+ }
127
+ },
128
+ "CC_Discharge": {
129
+ "rules": {
130
+ "variable": "I",
131
+ "type": "Constant",
132
+ "direction": "Discharge",
133
+ }
134
+ },
135
+ "CV_Discharge": {
136
+ "rules": {
137
+ "variable": "V",
138
+ "type": "Constant",
139
+ "direction": "Discharge",
140
+ }
141
+ },
142
+ "CP_Discharge": {
143
+ "rules": {
144
+ "variable": "P",
145
+ "type": "Constant",
146
+ "direction": "Discharge",
147
+ }
148
+ },
149
+ }
150
+
151
+
152
+ SEGMENT_SEQUENCE_CONFIG = {
153
+ **SEQUENCES_CONFIG,
154
+ **SEGMENTS_CONFIG_STANDARD,
155
+ }
156
+ ########################################################################################################################
157
+ #### These shouldn't be changed when using ppb Dataframes ####
158
+ DATA_COLUMNS = {
159
+ "V": "Voltage[V]",
160
+ "I": "Current[A]",
161
+ "P": "Power[W]",
162
+ }
163
+ STANDARD_COLUMNS = ["Test_Time[s]", "Voltage[V]", "Current[A]"]
164
+ #### These shouldn't be changed when using ppb Dataframes ####
165
+
166
+ STEP_ANALYZER_PRIMITIVES_CONFIG = {
167
+ "SEGMENTS_TO_DETECT_CONFIG": SEGMENTS_TO_DETECT_CONFIG,
168
+ "ADJUST_SEGMENTS_CONFIG": ADJUST_SEGMENTS_CONFIG,
169
+ "THRESHOLDS_PRIMITIVE_ANNOTATION": THRESHOLDS_PRIMITIVE_ANNOTATION,
170
+ "SHOW_RUNTIME": SHOW_RUNTIME,
171
+ "SHOW_RUNTIME_ANNOTATION": SHOW_RUNTIME_ANNOTATION,
172
+ "DATA_COLUMNS": DATA_COLUMNS,
173
+ "MIN_DEFINITIVE_CURRENT_DIFFERENCE": MIN_DEFINITIVE_CURRENT_DIFFERENCE,
174
+ "THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK": THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK,
175
+ "THRESHOLD_CONSOLE_PRINTS_CV_CHECK": THRESHOLD_CONSOLE_PRINTS_CV_CHECK,
176
+ "THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH": THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH,
177
+ "THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK": THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK,
178
+ }
@@ -0,0 +1,63 @@
1
+ import inspect
2
+ import logging
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from pydpeet.process.analyze.capacity import add_charge_throughput
8
+ from pydpeet.process.analyze.configs.battery_config import BatteryConfig
9
+ from pydpeet.process.analyze.utils import StepTimer
10
+
11
+
12
+ def add_equivalent_full_cycles(
13
+ df: pd.DataFrame,
14
+ config: BatteryConfig = None,
15
+ verbose: bool = True,
16
+ ) -> pd.DataFrame:
17
+ """
18
+ Calculate equivalent full cycles from absolute charge throughput and capacity reference.
19
+
20
+ Equivalent full cycles are calculated as the absolute charge throughput divided by two times the capacity reference,
21
+ so that every full cycle is counted as one cycle.
22
+
23
+ Parameters:
24
+ df (pandas.DataFrame): Input DataFrame containing battery test data
25
+ config (BatteryConfig, optional): Configuration for the battery. Defaults to BATTERY_DEFAULT
26
+
27
+ Returns:
28
+ pandas.DataFrame: DataFrame with added 'EquivalentFullCycles' column
29
+ """
30
+ if config is None:
31
+ func_name = inspect.currentframe().f_code.co_name
32
+ raise ValueError(f"config is None, please provide a valid config for {func_name}")
33
+
34
+ df_mod = df.copy()
35
+ logging.info(f"Starting Equivalent Full Cycles computation on dataframe of size {len(df_mod)}...")
36
+
37
+ c_ref = config.c_ref
38
+ if c_ref is None:
39
+ logging.info("c_ref is None, attempting to use first valid capacity value as reference")
40
+
41
+ first_valid_idx = df_mod["Capacity[Ah]"].first_valid_index()
42
+
43
+ if first_valid_idx is None:
44
+ logging.warning("No valid capacity values found — returning DataFrame with empty SOH column")
45
+ df_mod["SOH"] = np.nan # or whatever column you intend to fill
46
+ return df_mod
47
+ else:
48
+ c_ref = df_mod.at[first_valid_idx, "Capacity[Ah]"]
49
+ logging.info(f"Using first valid capacity value ({c_ref:.4f} Ah) as reference")
50
+
51
+ if "AbsoluteChargeThroughput[Ah]" not in df_mod.columns:
52
+ logging.info(
53
+ "AbsoluteChargeThroughput[Ah] not found, adding AbsoluteChargeThroughput[Ah] column with add_charge_throughput function..."
54
+ )
55
+ with StepTimer(verbose) as st:
56
+ df_mod = add_charge_throughput(df_mod)
57
+ st.log("added charge throughput column to df")
58
+
59
+ with StepTimer(verbose) as st:
60
+ df_mod["EquivalentFullCycles"] = df_mod["AbsoluteChargeThroughput[Ah]"] / (c_ref * 2)
61
+ st.log("computed Equivalent Full Cycles")
62
+
63
+ return df_mod
@@ -0,0 +1,182 @@
1
+ import inspect
2
+ import logging
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from pydpeet.process.analyze.average import (
8
+ calculate_total_charge,
9
+ calculate_total_discharge,
10
+ )
11
+ from pydpeet.process.analyze.configs.battery_config import BatteryConfig
12
+ from pydpeet.process.analyze.utils import StepTimer
13
+
14
+
15
+ def add_efficiency_coulomb(
16
+ df: pd.DataFrame,
17
+ df_blocks_charge: pd.DataFrame,
18
+ df_blocks_discharge: pd.DataFrame,
19
+ config: BatteryConfig = None,
20
+ max_time_diff_in_secs: int = 300,
21
+ ignore_threshold_values: bool = False,
22
+ verbose: bool = True,
23
+ ) -> pd.DataFrame:
24
+ """
25
+ Calculate the Coulomb Efficiency of a battery based on the given data.
26
+
27
+ The Coulomb efficiency is calculated as the total discharge divided by the total charge.
28
+ The time difference between a charge block and the subsequent discharge block is taken into account.
29
+ If a charge block does not have a corresponding discharge block within the given time difference,
30
+ it is ignored. Similarly, if a discharge block does not have a corresponding charge block within
31
+ the given time difference, it is ignored.
32
+
33
+ Parameters:
34
+ df (pandas.DataFrame): Input DataFrame containing battery test data
35
+ df_blocks_charge (list[pandas.DataFrame] or pandas.DataFrame): List of DataFrames containing charge blocks
36
+ df_blocks_discharge (list[pandas.DataFrame] or pandas.DataFrame): List of DataFrames containing discharge blocks
37
+ config (BatteryConfig, optional): Configuration object containing battery test parameters
38
+ max_time_diff_in_secs (int, optional): Maximum time difference in seconds between charge and discharge blocks
39
+ ignore_threshold_values (bool, optional): Whether to ignore voltage thresholds when computing Coulomb efficiency
40
+ verbose (bool, optional): If True, print debug messages
41
+
42
+ Returns:
43
+ pandas.DataFrame: DataFrame with added 'CoulombEfficiency' column
44
+ """
45
+ if config is None:
46
+ func_name = inspect.currentframe().f_code.co_name
47
+ raise ValueError(f"config is None, please provide a valid config for {func_name}")
48
+
49
+ logging.info(f"Starting Coulomb Efficiency computation on dataframe of size {len(df)}...")
50
+
51
+ # Ensure blocks are lists
52
+ if isinstance(df_blocks_charge, pd.DataFrame):
53
+ df_blocks_charge = [df_blocks_charge]
54
+ if isinstance(df_blocks_discharge, pd.DataFrame):
55
+ df_blocks_discharge = [df_blocks_discharge]
56
+
57
+ # Determine which time column to use
58
+ if "Test_Time[s]" in df.columns:
59
+ time_col = "Test_Time[s]"
60
+ parse_time = False
61
+ tolerance_val = max_time_diff_in_secs # already seconds
62
+ elif "Date_Time" in df.columns:
63
+ # TODO: Assumed stack level for now
64
+ logging.warning("Date_Time is used as time column, because there is no 'Test_Time[s]' given.", stacklevel=2)
65
+ time_col = "Date_Time"
66
+ parse_time = True
67
+ tolerance_val = pd.Timedelta(seconds=max_time_diff_in_secs)
68
+ else:
69
+ raise ValueError("No suitable time column found. Provide 'Date_Time' or 'Test_Time[s]'.")
70
+
71
+ max_voltage = config.max_voltage
72
+ min_voltage = config.min_voltage
73
+
74
+ # Voltage thresholds
75
+ max_v_adj_down = max_voltage * (1 - config.voltage_intervall)
76
+ max_v_adj_up = max_voltage * (1 + config.voltage_intervall)
77
+ min_v_adj_up = min_voltage * (1 + config.voltage_intervall)
78
+ min_v_adj_down = min_voltage * (1 - config.voltage_intervall)
79
+
80
+ df_mod = df.copy()
81
+ df_mod["CoulombEfficiency"] = np.nan # numeric NaN
82
+
83
+ # Helper to summarize blocks safely
84
+ def summarize_blocks(blocks, total_col_name):
85
+ summary = []
86
+ for block in blocks:
87
+ if block.empty:
88
+ continue
89
+ block_max = block["Voltage[V]"].max()
90
+ block_min = block["Voltage[V]"].min()
91
+ if ignore_threshold_values or (
92
+ max_v_adj_down <= block_max <= max_v_adj_up and min_v_adj_down <= block_min <= min_v_adj_up
93
+ ):
94
+ start_time = pd.to_datetime(block[time_col].iloc[0]) if parse_time else block[time_col].iloc[0]
95
+ end_time = pd.to_datetime(block[time_col].iloc[-1]) if parse_time else block[time_col].iloc[-1]
96
+ with StepTimer(verbose) as timer:
97
+ total = (
98
+ calculate_total_charge(block)
99
+ if total_col_name == "total_charge"
100
+ else calculate_total_discharge(block)
101
+ )
102
+ timer.log(f"calculated {total_col_name} for one block")
103
+ summary.append(
104
+ {
105
+ "start_time": start_time,
106
+ "end_time": end_time,
107
+ total_col_name: total,
108
+ "last_index": block.index[-1],
109
+ }
110
+ )
111
+ if not summary:
112
+ return pd.DataFrame(columns=["start_time", "end_time", total_col_name, "last_index"])
113
+ return pd.DataFrame(summary).sort_values("start_time")
114
+
115
+ # Helper to find matching blocks
116
+ with StepTimer(verbose) as st:
117
+ charge_df = summarize_blocks(df_blocks_charge, "total_charge")
118
+ discharge_df = summarize_blocks(df_blocks_discharge, "total_discharge")
119
+ st.log("summarized all charge/discharge blocks")
120
+
121
+ if charge_df.empty or discharge_df.empty:
122
+ logging.info("No valid charge/discharge blocks found. Skipping Coulomb Efficiency computation.")
123
+ return df_mod
124
+
125
+ if parse_time:
126
+ # Ensure datetime dtype
127
+ charge_df["start_time"] = pd.to_datetime(charge_df["start_time"])
128
+ charge_df["end_time"] = pd.to_datetime(charge_df["end_time"])
129
+ discharge_df["start_time"] = pd.to_datetime(discharge_df["start_time"])
130
+ discharge_df["end_time"] = pd.to_datetime(discharge_df["end_time"])
131
+
132
+ # Sort before merge_asof
133
+ charge_df = charge_df.sort_values("start_time").reset_index(drop=True)
134
+ discharge_df = discharge_df.sort_values("start_time").reset_index(drop=True)
135
+
136
+ # Forward merge: discharge after charge
137
+ with StepTimer(verbose) as st:
138
+ paired_forward = pd.merge_asof(
139
+ charge_df,
140
+ discharge_df,
141
+ left_on="end_time",
142
+ right_on="start_time",
143
+ direction="forward",
144
+ tolerance=tolerance_val,
145
+ )
146
+ st.log("performed forward merge of charge/discharge blocks")
147
+
148
+ # Backward merge: discharge before charge
149
+ with StepTimer(verbose) as st:
150
+ paired_backward = pd.merge_asof(
151
+ charge_df,
152
+ discharge_df,
153
+ left_on="start_time",
154
+ right_on="end_time",
155
+ direction="backward",
156
+ tolerance=tolerance_val,
157
+ )
158
+ st.log("performed backward merge of charge/discharge blocks")
159
+
160
+ # Combine forward/backward and pick closest in absolute time
161
+ with StepTimer(verbose) as st:
162
+ combined = pd.concat([paired_forward, paired_backward], ignore_index=True, sort=False)
163
+ combined["time_diff"] = (
164
+ pd.to_datetime(combined["start_time_y"]) - pd.to_datetime(combined["end_time_x"])
165
+ ).abs()
166
+ combined = combined.dropna(subset=["total_discharge"])
167
+ combined = combined.sort_values("time_diff").drop_duplicates(subset=["start_time_x", "end_time_x"])
168
+
169
+ # Assign Coulomb Efficiency to last index of the later block
170
+ for _, row in combined.iterrows():
171
+ total_charge = row.get("total_charge", np.nan)
172
+ total_discharge = row.get("total_discharge", np.nan)
173
+ if pd.isna(total_charge) or total_charge == 0:
174
+ ce = np.nan
175
+ else:
176
+ ce = total_discharge / total_charge
177
+ # Use nan-safe max and cast to int to avoid type checker complaints about comparing Series
178
+ last_index = int(np.nanmax([row.get("last_index_x", np.nan), row.get("last_index_y", np.nan)]))
179
+ df_mod.loc[last_index, "CoulombEfficiency"] = ce
180
+ st.log("assigned Coulomb Efficiency to dataframe")
181
+
182
+ return df_mod
@@ -0,0 +1,51 @@
1
+ import inspect
2
+ import logging
3
+
4
+ import pandas as pd
5
+ from scipy import integrate
6
+
7
+ from pydpeet.process.analyze.configs.battery_config import BatteryConfig
8
+ from pydpeet.process.analyze.power import add_power
9
+ from pydpeet.process.analyze.utils import (
10
+ StepTimer,
11
+ _check_columns,
12
+ )
13
+
14
+
15
+ def add_cumulative_energy(
16
+ df: pd.DataFrame,
17
+ config: BatteryConfig = None,
18
+ verbose: bool = True,
19
+ ) -> pd.DataFrame:
20
+ """
21
+ Calculates cumulative energy [Wh] from 'Test_Time[s]' and 'Power[W]' columns and adds it as a new column.
22
+
23
+ Parameters:
24
+ - df (pandas.DataFrame): DataFrame containing 'Test_Time[s]' and 'Power[W]' columns
25
+ - cumu_energy_method (CumulativeEnergyMethod): Method to use for cumulative energy calculation
26
+ - config (BatteryConfig): Config object containing max and min voltage values
27
+
28
+ Returns:
29
+ - pandas.DataFrame: DataFrame with added 'CumulativeEnergy[Wh]' column
30
+ """
31
+ logging.info("Calculating CumulativeEnergy[Wh]...")
32
+
33
+ if config is None:
34
+ func_name = inspect.currentframe().f_code.co_name
35
+ raise ValueError(f"config is None, please provide a valid config for {func_name}")
36
+
37
+ df_mod = df.copy()
38
+ if "Power[W]" not in df_mod.columns:
39
+ logging.info("Power[W] column missing → adding via add_power.")
40
+ with StepTimer(verbose) as st:
41
+ df_mod = add_power(df_mod)
42
+ st.log("added Power[W] column")
43
+
44
+ with StepTimer(verbose) as st:
45
+ _check_columns(df_mod, ["Test_Time[s]", "Power[W]"])
46
+ df_mod["CumulativeEnergy[Wh]"] = (
47
+ integrate.cumulative_trapezoid(df_mod["Power[W]"], x=df_mod["Test_Time[s]"], initial=0) / 3600
48
+ )
49
+ st.log("calculated CumulativeEnergy[Wh]")
50
+
51
+ return df_mod
@@ -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__ = []