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