batplot 1.8.26__tar.gz → 1.8.27__tar.gz
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.
- {batplot-1.8.26/batplot.egg-info → batplot-1.8.27}/PKG-INFO +1 -1
- {batplot-1.8.26 → batplot-1.8.27}/batplot/__init__.py +1 -1
- {batplot-1.8.26 → batplot-1.8.27}/batplot/args.py +18 -7
- {batplot-1.8.26 → batplot-1.8.27}/batplot/batch.py +123 -32
- {batplot-1.8.26 → batplot-1.8.27}/batplot/batplot.py +343 -79
- {batplot-1.8.26 → batplot-1.8.27}/batplot/cpc_interactive.py +858 -312
- {batplot-1.8.26 → batplot-1.8.27}/batplot/data/CHANGELOG.md +14 -1
- {batplot-1.8.26 → batplot-1.8.27}/batplot/data/USER_MANUAL.md +26 -1
- {batplot-1.8.26 → batplot-1.8.27}/batplot/electrochem_interactive.py +580 -331
- {batplot-1.8.26 → batplot-1.8.27}/batplot/interactive.py +111 -99
- {batplot-1.8.26 → batplot-1.8.27}/batplot/modes.py +16 -1
- {batplot-1.8.26 → batplot-1.8.27}/batplot/operando_ec_interactive.py +225 -215
- {batplot-1.8.26 → batplot-1.8.27}/batplot/readers.py +114 -12
- {batplot-1.8.26 → batplot-1.8.27}/batplot/session.py +142 -90
- {batplot-1.8.26 → batplot-1.8.27}/batplot/style.py +16 -7
- {batplot-1.8.26 → batplot-1.8.27}/batplot/utils.py +39 -12
- {batplot-1.8.26 → batplot-1.8.27}/batplot/version_check.py +3 -2
- {batplot-1.8.26 → batplot-1.8.27/batplot.egg-info}/PKG-INFO +1 -1
- {batplot-1.8.26 → batplot-1.8.27}/pyproject.toml +1 -1
- {batplot-1.8.26 → batplot-1.8.27}/LICENSE +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/MANIFEST.in +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/NOTICE +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/README.md +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/USER_MANUAL.md +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/cif.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/cli.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/color_utils.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/config.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/converters.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/dev_upgrade.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/manual.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/operando.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/plotting.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot/ui.py +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot.egg-info/SOURCES.txt +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/setup.cfg +0 -0
- {batplot-1.8.26 → batplot-1.8.27}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batplot
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.27
|
|
4
4
|
Summary: Interactive plotting tool for material science (1D plot) and electrochemistry (GC, CV, dQ/dV, CPC, operando) with batch processing
|
|
5
5
|
Author-email: Tian Dai <tianda@uio.no>
|
|
6
6
|
License: MIT License
|
|
@@ -296,18 +296,28 @@ def _print_ec_help() -> None:
|
|
|
296
296
|
"Use --interactive for styling, colors, line widths, axis scales, etc.\n"
|
|
297
297
|
"GC from .mpt: requires active mass in mg to compute mAh g⁻¹.\n"
|
|
298
298
|
" batplot --gc file.mpt --mass 6.5 --interactive\n\n"
|
|
299
|
-
"GC from supported .csv: specific capacity
|
|
300
|
-
"
|
|
301
|
-
"
|
|
302
|
-
" batplot --
|
|
299
|
+
"GC from supported .csv: specific capacity read directly when available; use --mass for\n"
|
|
300
|
+
" Neware absolute-capacity files (Cycle Index / Step Index / DataPoint format).\n"
|
|
301
|
+
" batplot --gc file.csv\n"
|
|
302
|
+
" batplot --gc file.csv --mass 3.52 # Neware absolute-capacity CSV\n\n"
|
|
303
|
+
"Per-file mass: repeat --mass once per file that needs it, in file order.\n"
|
|
304
|
+
" batplot f1.mpt --mass 6.5 f2.csv f3.mpt --mass 7.0 --gc\n"
|
|
305
|
+
" batplot f1.csv --mass 3.52 f2.mpt --mass 5.0 --cpc\n"
|
|
306
|
+
" # Files without --mass between them use the global --mass value (or none)\n"
|
|
307
|
+
" # Single --mass applies to all files: batplot f1.mpt f2.mpt --gc --mass 7.0\n\n"
|
|
308
|
+
"dQ/dV from supported .csv (pre-calculated column or computed from GC data):\n"
|
|
309
|
+
" batplot --dqdv file.csv\n"
|
|
310
|
+
" batplot --dqdv file.csv --mass 3.52 # Neware absolute-capacity CSV\n\n"
|
|
303
311
|
"Cyclic voltammetry (CV) from .mpt or .txt: plots voltage vs current for each cycle.\n"
|
|
304
312
|
" batplot --cv file.mpt\n"
|
|
305
313
|
" batplot --cv file.txt\n\n"
|
|
306
314
|
"Capacity-per-cycle (CPC) with coulombic efficiency from .csv, .xlsx, or .mpt.\n"
|
|
307
315
|
"Supports multiple files with individual color customization:\n"
|
|
308
|
-
" batplot --cpc file.csv # Neware CSV\n"
|
|
316
|
+
" batplot --cpc file.csv # Neware CSV (specific capacity)\n"
|
|
317
|
+
" batplot --cpc file.csv --mass 3.52 # Neware absolute-capacity CSV\n"
|
|
309
318
|
" batplot --cpc file.xlsx # Landt/Lanhe Excel (Chinese tester)\n"
|
|
310
|
-
" batplot --cpc file.mpt --mass 1.2
|
|
319
|
+
" batplot --cpc file.mpt --mass 1.2 # Biologic MPT\n"
|
|
320
|
+
" batplot file1.csv --mass 3.52 file2.mpt --mass 1.2 --cpc # Per-file mass\n"
|
|
311
321
|
" batplot --cpc file1.csv file2.xlsx file3.mpt --mass 1.2 --interactive\n\n"
|
|
312
322
|
"Excel support: Landt/Lanhe (蓝电/蓝河) .xlsx files with Chinese headers:\n"
|
|
313
323
|
" Expected structure: Row 1=filename, Row 2=headers, Row 3+=data\n"
|
|
@@ -457,10 +467,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
457
467
|
parser.add_argument("--operando", "--contour", action="store_true", dest="operando", help=argparse.SUPPRESS)
|
|
458
468
|
parser.add_argument("--debug", action="store_true", help=argparse.SUPPRESS)
|
|
459
469
|
parser.add_argument("--gc", action="store_true", help=argparse.SUPPRESS)
|
|
460
|
-
parser.add_argument("--mass", type=float, help=argparse.SUPPRESS)
|
|
470
|
+
parser.add_argument("--mass", type=float, action='append', help=argparse.SUPPRESS)
|
|
461
471
|
parser.add_argument("--dqdv", action="store_true", help=argparse.SUPPRESS)
|
|
462
472
|
parser.add_argument("--cv", action="store_true", help=argparse.SUPPRESS)
|
|
463
473
|
parser.add_argument("--cpc", action="store_true", help=argparse.SUPPRESS)
|
|
474
|
+
parser.add_argument("--epc", action="store_true", help=argparse.SUPPRESS)
|
|
464
475
|
parser.add_argument("--pw", nargs=2, type=float, metavar=('V_MIN', 'V_MAX'),
|
|
465
476
|
help=argparse.SUPPRESS)
|
|
466
477
|
parser.add_argument("--cd", type=float, help=argparse.SUPPRESS)
|
|
@@ -18,8 +18,26 @@ from .readers import (
|
|
|
18
18
|
read_mpt_file,
|
|
19
19
|
read_ec_csv_file,
|
|
20
20
|
read_ec_csv_dqdv_file,
|
|
21
|
+
compute_dqdv_numerical,
|
|
22
|
+
is_cs_b_format,
|
|
23
|
+
_load_csv_header_and_rows,
|
|
21
24
|
read_biologic_txt_file,
|
|
22
25
|
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _resolve_mass(mass_arg, file_idx: int = 0):
|
|
29
|
+
"""Return mass (mg) for file at file_idx from a --mass list or single value."""
|
|
30
|
+
if mass_arg is None:
|
|
31
|
+
return None
|
|
32
|
+
if isinstance(mass_arg, (int, float)):
|
|
33
|
+
return float(mass_arg)
|
|
34
|
+
if isinstance(mass_arg, list):
|
|
35
|
+
if len(mass_arg) == 1:
|
|
36
|
+
return float(mass_arg[0])
|
|
37
|
+
if file_idx < len(mass_arg):
|
|
38
|
+
return float(mass_arg[file_idx])
|
|
39
|
+
return float(mass_arg[-1])
|
|
40
|
+
return None
|
|
23
41
|
from .utils import _confirm_overwrite, natural_sort_key, ensure_subdirectory
|
|
24
42
|
|
|
25
43
|
|
|
@@ -888,6 +906,9 @@ def batch_process_ec(directory: str, args):
|
|
|
888
906
|
elif getattr(args, 'cpc', False):
|
|
889
907
|
mode = 'cpc'
|
|
890
908
|
supported_ext = {'.mpt', '.csv'}
|
|
909
|
+
elif getattr(args, 'epc', False):
|
|
910
|
+
mode = 'epc'
|
|
911
|
+
supported_ext = {'.mpt', '.csv'}
|
|
891
912
|
else:
|
|
892
913
|
print("EC batch mode requires one of: --gc, --cv, --dqdv, or --cpc")
|
|
893
914
|
return
|
|
@@ -966,9 +987,10 @@ def batch_process_ec(directory: str, args):
|
|
|
966
987
|
|
|
967
988
|
return colors[:n_colors] # Ensure exact count
|
|
968
989
|
|
|
969
|
-
for fname in files:
|
|
990
|
+
for _batch_file_idx, fname in enumerate(files):
|
|
970
991
|
fpath = os.path.join(directory, fname)
|
|
971
992
|
ext = os.path.splitext(fname)[1].lower()
|
|
993
|
+
mass_mg = _resolve_mass(getattr(args, 'mass', None), _batch_file_idx)
|
|
972
994
|
|
|
973
995
|
try:
|
|
974
996
|
fig_b, ax_b = plt.subplots(figsize=(6, 4))
|
|
@@ -976,7 +998,6 @@ def batch_process_ec(directory: str, args):
|
|
|
976
998
|
# ---- GC Mode ----
|
|
977
999
|
if mode == 'gc':
|
|
978
1000
|
if ext == '.mpt':
|
|
979
|
-
mass_mg = getattr(args, 'mass', None)
|
|
980
1001
|
if mass_mg is None:
|
|
981
1002
|
print(f" Skipped {fname}: GC mode (.mpt) requires --mass parameter")
|
|
982
1003
|
plt.close(fig_b)
|
|
@@ -1088,10 +1109,39 @@ def batch_process_ec(directory: str, args):
|
|
|
1088
1109
|
elif mode == 'dqdv':
|
|
1089
1110
|
if ext != '.csv':
|
|
1090
1111
|
raise ValueError("dQdV mode requires .csv file")
|
|
1091
|
-
|
|
1092
|
-
#
|
|
1093
|
-
|
|
1094
|
-
|
|
1112
|
+
|
|
1113
|
+
# Try to load pre-calculated dQ/dV columns; fall back to numerical computation
|
|
1114
|
+
_b_dqdv_header = None
|
|
1115
|
+
try:
|
|
1116
|
+
_b_dqdv_header, _, _ = _load_csv_header_and_rows(fpath)
|
|
1117
|
+
except Exception:
|
|
1118
|
+
pass
|
|
1119
|
+
|
|
1120
|
+
_b_loaded = False
|
|
1121
|
+
if not _b_loaded:
|
|
1122
|
+
try:
|
|
1123
|
+
voltage, dqdv, cycles, charge_mask, discharge_mask, y_label = \
|
|
1124
|
+
read_ec_csv_dqdv_file(fpath, prefer_specific=True)
|
|
1125
|
+
_b_loaded = True
|
|
1126
|
+
except ValueError:
|
|
1127
|
+
pass
|
|
1128
|
+
|
|
1129
|
+
if not _b_loaded:
|
|
1130
|
+
_b_gc_cap, _b_gc_volt, _b_gc_cyc, _b_gc_chgm, _b_gc_dchm = \
|
|
1131
|
+
read_ec_csv_file(fpath, prefer_specific=True)
|
|
1132
|
+
_b_mass = mass_mg
|
|
1133
|
+
if _b_dqdv_header is not None:
|
|
1134
|
+
_b_hdrs = [h.strip().replace('\t', '') for h in _b_dqdv_header]
|
|
1135
|
+
_b_has_spec = any('Spec. Cap.(mAh/g)' in h for h in _b_hdrs)
|
|
1136
|
+
_b_has_abs = any(h == 'Capacity(mAh)' for h in _b_hdrs)
|
|
1137
|
+
if _b_has_abs and not _b_has_spec:
|
|
1138
|
+
if _b_mass and _b_mass > 0:
|
|
1139
|
+
_b_gc_cap = _b_gc_cap * (1000.0 / float(_b_mass))
|
|
1140
|
+
else:
|
|
1141
|
+
print(f"dQ/dV batch: {fname!r} — pass --mass <mg> for specific dQ/dV.")
|
|
1142
|
+
voltage, dqdv, cycles, charge_mask, discharge_mask, y_label = \
|
|
1143
|
+
compute_dqdv_numerical(_b_gc_cap, _b_gc_volt, _b_gc_cyc, _b_gc_chgm, _b_gc_dchm)
|
|
1144
|
+
print(f"dQ/dV batch: computing numerically from GC data for {fname!r}.")
|
|
1095
1145
|
|
|
1096
1146
|
# Process cycles similar to GC mode
|
|
1097
1147
|
if cycles is not None and cycles.size > 0:
|
|
@@ -1141,50 +1191,91 @@ def batch_process_ec(directory: str, args):
|
|
|
1141
1191
|
legend = ax_b.legend(loc='best', fontsize='small', framealpha=0.8, title='Cycle')
|
|
1142
1192
|
legend.get_title().set_fontsize('small')
|
|
1143
1193
|
|
|
1144
|
-
# ---- CPC Mode ----
|
|
1145
|
-
elif mode
|
|
1194
|
+
# ---- CPC / EPC Mode ----
|
|
1195
|
+
elif mode in ('cpc', 'epc'):
|
|
1146
1196
|
if ext == '.mpt':
|
|
1147
|
-
mass_mg = getattr(args, 'mass', None)
|
|
1148
1197
|
if mass_mg is None:
|
|
1149
|
-
print(f" Skipped {fname}:
|
|
1198
|
+
print(f" Skipped {fname}: {mode.upper()} mode (.mpt) requires --mass parameter")
|
|
1150
1199
|
plt.close(fig_b)
|
|
1151
1200
|
continue
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1201
|
+
if mode == 'cpc':
|
|
1202
|
+
cyc_nums, cap_charge, cap_discharge, _eff_dummy = cast(
|
|
1203
|
+
Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray],
|
|
1204
|
+
read_mpt_file(fpath, mode='cpc', mass_mg=mass_mg),
|
|
1205
|
+
)
|
|
1206
|
+
y_label = r'Specific Capacity (mAh g$^{-1}$)'
|
|
1207
|
+
else:
|
|
1208
|
+
cap_x, voltage, cycle_numbers, charge_mask, discharge_mask = cast(
|
|
1209
|
+
Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray],
|
|
1210
|
+
read_mpt_file(fpath, mode='gc', mass_mg=mass_mg),
|
|
1211
|
+
)
|
|
1212
|
+
cyc_int_raw = np.array(np.rint(cycle_numbers), dtype=int)
|
|
1213
|
+
if cyc_int_raw.size:
|
|
1214
|
+
cycles_present = sorted(int(c) for c in np.unique(cyc_int_raw))
|
|
1215
|
+
en_charge = []
|
|
1216
|
+
en_discharge = []
|
|
1217
|
+
for cyc in cycles_present:
|
|
1218
|
+
mask_c = (cyc_int_raw == cyc) & charge_mask
|
|
1219
|
+
mask_d = (cyc_int_raw == cyc) & discharge_mask
|
|
1220
|
+
if np.count_nonzero(mask_c) >= 2:
|
|
1221
|
+
en_c = float(np.trapz(voltage[mask_c], cap_x[mask_c]))
|
|
1222
|
+
else:
|
|
1223
|
+
en_c = 0.0
|
|
1224
|
+
if np.count_nonzero(mask_d) >= 2:
|
|
1225
|
+
en_d = float(np.trapz(voltage[mask_d], cap_x[mask_d]))
|
|
1226
|
+
else:
|
|
1227
|
+
en_d = 0.0
|
|
1228
|
+
en_charge.append(en_c)
|
|
1229
|
+
en_discharge.append(en_d)
|
|
1230
|
+
cyc_nums = np.array(cycles_present)
|
|
1231
|
+
cap_charge = np.array(en_charge)
|
|
1232
|
+
cap_discharge = np.array(en_discharge)
|
|
1233
|
+
else:
|
|
1234
|
+
cyc_nums = np.array([1.0])
|
|
1235
|
+
cap_charge = np.array([0.0])
|
|
1236
|
+
cap_discharge = np.array([0.0])
|
|
1237
|
+
y_label = r'Specific Energy (mWh g$^{-1}$)'
|
|
1157
1238
|
elif ext == '.csv':
|
|
1158
1239
|
# For CSV CPC, read as GC-like data
|
|
1159
1240
|
cap_x, voltage, cycle_numbers, charge_mask, discharge_mask = \
|
|
1160
1241
|
read_ec_csv_file(fpath, prefer_specific=True)
|
|
1161
|
-
# Plot capacity vs cycle number
|
|
1162
1242
|
if cycle_numbers is not None:
|
|
1163
1243
|
cyc_int_raw = np.array(np.rint(cycle_numbers), dtype=int)
|
|
1164
1244
|
if cyc_int_raw.size:
|
|
1165
1245
|
cycles_present = sorted(int(c) for c in np.unique(cyc_int_raw))
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
cap_discharge = []
|
|
1246
|
+
cap_charge_list = []
|
|
1247
|
+
cap_discharge_list = []
|
|
1169
1248
|
for cyc in cycles_present:
|
|
1170
1249
|
mask_c = (cyc_int_raw == cyc) & charge_mask
|
|
1171
1250
|
mask_d = (cyc_int_raw == cyc) & discharge_mask
|
|
1172
|
-
|
|
1173
|
-
|
|
1251
|
+
if mode == 'cpc':
|
|
1252
|
+
val_c = np.max(cap_x[mask_c]) if np.any(mask_c) else 0.0
|
|
1253
|
+
val_d = np.max(cap_x[mask_d]) if np.any(mask_d) else 0.0
|
|
1254
|
+
else:
|
|
1255
|
+
if np.count_nonzero(mask_c) >= 2:
|
|
1256
|
+
val_c = float(np.trapz(voltage[mask_c], cap_x[mask_c]))
|
|
1257
|
+
else:
|
|
1258
|
+
val_c = 0.0
|
|
1259
|
+
if np.count_nonzero(mask_d) >= 2:
|
|
1260
|
+
val_d = float(np.trapz(voltage[mask_d], cap_x[mask_d]))
|
|
1261
|
+
else:
|
|
1262
|
+
val_d = 0.0
|
|
1263
|
+
cap_charge_list.append(val_c)
|
|
1264
|
+
cap_discharge_list.append(val_d)
|
|
1174
1265
|
cyc_nums = np.array(cycles_present)
|
|
1175
|
-
cap_charge = np.array(
|
|
1176
|
-
cap_discharge = np.array(
|
|
1266
|
+
cap_charge = np.array(cap_charge_list)
|
|
1267
|
+
cap_discharge = np.array(cap_discharge_list)
|
|
1177
1268
|
else:
|
|
1178
|
-
cyc_nums = np.array([1])
|
|
1179
|
-
cap_charge = np.array([0])
|
|
1180
|
-
cap_discharge = np.array([0])
|
|
1269
|
+
cyc_nums = np.array([1.0])
|
|
1270
|
+
cap_charge = np.array([0.0])
|
|
1271
|
+
cap_discharge = np.array([0.0])
|
|
1181
1272
|
else:
|
|
1182
|
-
cyc_nums = np.array([1])
|
|
1183
|
-
cap_charge = np.array([0])
|
|
1184
|
-
cap_discharge = np.array([0])
|
|
1185
|
-
|
|
1273
|
+
cyc_nums = np.array([1.0])
|
|
1274
|
+
cap_charge = np.array([0.0])
|
|
1275
|
+
cap_discharge = np.array([0.0])
|
|
1276
|
+
y_label = r'Specific Capacity (mAh g$^{-1}$)' if mode == 'cpc' else r'Specific Energy (mWh g$^{-1}$)'
|
|
1186
1277
|
else:
|
|
1187
|
-
raise ValueError(f"Unsupported file type for
|
|
1278
|
+
raise ValueError(f"Unsupported file type for {mode.upper()}: {ext}")
|
|
1188
1279
|
|
|
1189
1280
|
# Plot CPC data
|
|
1190
1281
|
ax_b.plot(cyc_nums, cap_charge, 'o-', color='#1f77b4',
|
|
@@ -1192,7 +1283,7 @@ def batch_process_ec(directory: str, args):
|
|
|
1192
1283
|
ax_b.plot(cyc_nums, cap_discharge, 's-', color='#ff7f0e',
|
|
1193
1284
|
linewidth=1.5, markersize=4, label='Discharge', alpha=0.8)
|
|
1194
1285
|
ax_b.set_xlabel('Cycle Number')
|
|
1195
|
-
ax_b.set_ylabel(
|
|
1286
|
+
ax_b.set_ylabel(y_label)
|
|
1196
1287
|
ax_b.legend()
|
|
1197
1288
|
ax_b.set_title(f"{fname}")
|
|
1198
1289
|
|