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,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
|