Skip to content

Commit 56d9c64

Browse files
committed
eclab: Implement support for BCD technique (#242)
* assign typo * Implement BCD * tests * Docs. * fix docs * Set I/C 1 and Set I/C 2 in mpr & mpt * mpt fix
1 parent 6859554 commit 56d9c64

16 files changed

+2738
-13
lines changed

docs/source/version.7_0.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**yadg** version next
2+
`````````````````````
3+
4+
..
5+
.. image:: https://img.shields.io/static/v1?label=yadg&message=v6.2&color=blue&logo=github
6+
:target: https://github.com/PeterKraus/yadg/tree/6.2
7+
.. image:: https://img.shields.io/static/v1?label=yadg&message=v6.2&color=blue&logo=pypi
8+
:target: https://pypi.org/project/yadg/6.2/
9+
.. image:: https://img.shields.io/static/v1?label=release%20date&message=2025-08-20&color=red&logo=pypi
10+
11+
12+
Developed in the `ConCat Lab <https://tu.berlin/en/concat>`_ at Technische Universität Berlin (Berlin, DE).
13+
14+
New features in ``yadg-next`` are:
15+
16+
- Support for Battery Capacity Determination (BCD) technique in :mod:`yadg.extractors.eclab.mpr` and :mod:`yadg.extractors.eclab.mpt`. Note that the ``Set I/C`` parameters in BCD are renamed to ``Set I/C 1`` and ``Set I/C 2`` in both :mod:`~yadg.extractors.eclab.mpr` and :mod:`~yadg.extractors.eclab.mpt` files. Thank you to `Joachim Laviolette <https://github.com/JL-CEA>`_ for providing test files.
17+
18+
Breaking changes in ``yadg-next`` are:
19+
20+
Bug fixes in ``yadg-next`` include:
21+
22+
- The parameter ``Set I/C`` in :mod:`yadg.extractors.eclab.mpr` files should be ``C / N`` when set to 1, not ``C``.
23+

docs/source/version.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
**yadg** version history
22
------------------------
33

4+
.. include:: version.7_0.rst
5+
46
.. include:: version.6_2.rst
57

68
.. include:: version.6_1.rst

src/yadg/extractors/eclab/mpr.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@
201201
flag_columns,
202202
data_columns,
203203
conflict_columns,
204+
technique_dependent_ids,
204205
log_dtypes,
205206
extdev_dtypes,
206207
)
@@ -279,10 +280,12 @@ def process_settings(data: bytes, minver: str) -> tuple[dict, list]:
279280
params = {k: [d[k] for d in params] for k in params[0]}
280281
else:
281282
params = {}
282-
return settings, params
283+
return technique, settings, params
283284

284285

285-
def parse_columns(column_ids: list[int]) -> tuple[list, list, list, dict]:
286+
def parse_columns(
287+
column_ids: list[int], technique: str
288+
) -> tuple[list, list, list, dict]:
286289
"""Puts together column info from a list of data column IDs.
287290
288291
Note
@@ -319,6 +322,8 @@ def parse_columns(column_ids: list[int]) -> tuple[list, list, list, dict]:
319322
units.append(None)
320323
elif idd in data_columns and idd not in conflict_columns:
321324
dtype, name, unit = data_columns[idd]
325+
if idd in technique_dependent_ids:
326+
name = technique_dependent_ids[idd].get(technique, name)
322327
if name in names:
323328
logger.error(
324329
"Column ID %d (%d) is a duplicate of '%s' with unit '%s'. "
@@ -383,6 +388,7 @@ def process_data(
383388
Eranges: list[float],
384389
Iranges: list[float],
385390
controls: list[str],
391+
technique: str,
386392
):
387393
"""Processes the contents of data modules.
388394
@@ -411,7 +417,7 @@ def process_data(
411417
column_ids = np.frombuffer(data, offset=0x005, dtype="<u2", count=n_columns)
412418
logger.debug("Found %d columns with IDs: %s", n_columns, column_ids)
413419
# Length of each datapoint depends on number and IDs of columns.
414-
namelist, dtypelist, unitlist, flaglist = parse_columns(column_ids)
420+
namelist, dtypelist, unitlist, flaglist = parse_columns(column_ids, technique)
415421
units = {k: v for k, v in zip(namelist, unitlist) if v is not None}
416422
data_dtype = np.dtype(list(zip(namelist, dtypelist)))
417423
# Depending on module version, datapoints start at different offsets.
@@ -554,6 +560,7 @@ def process_modules(contents: bytes) -> tuple[dict, list, list, dict, dict]:
554560
"""
555561
modules = contents.split(b"MODULE")[1:]
556562
settings = log = loop = ext = None
563+
technique = None
557564
for module in modules:
558565
for mhd in module_header_dtypes:
559566
try:
@@ -584,7 +591,7 @@ def process_modules(contents: bytes) -> tuple[dict, list, list, dict, dict]:
584591
logger.debug("Read '%s' with version '%s' ('%s')", name, version, minver)
585592
module_data = module[mhd.itemsize :]
586593
if name == "VMP Set":
587-
settings, params = process_settings(module_data, minver)
594+
technique, settings, params = process_settings(module_data, minver)
588595

589596
E_range_max = params.get("E range max (V)", [float("inf")])
590597
E_range_min = params.get("E range min (V)", [float("-inf")])
@@ -597,7 +604,7 @@ def process_modules(contents: bytes) -> tuple[dict, list, list, dict, dict]:
597604
else:
598605
ctrls = [None] * len(Iranges)
599606
elif name == "VMP data":
600-
ds = process_data(module_data, version, Eranges, Iranges, ctrls)
607+
ds = process_data(module_data, version, Eranges, Iranges, ctrls, technique)
601608
elif name == "VMP LOG":
602609
log = process_log(module_data)
603610
elif name == "VMP loop":

src/yadg/extractors/eclab/mpr_columns.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,12 @@
174174
},
175175
}
176176

177+
technique_dependent_ids = {
178+
6: {
179+
"BCD": "Ecell",
180+
}
181+
}
182+
177183
# Relates the offset in log data to the corresponding dtype and name.
178184
# NOTE: The safety limits are maybe at 0x200?
179185
# NOTE: The log also seems to contain the settings again. These are left

src/yadg/extractors/eclab/mpt.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,15 @@ def process_params(technique: str, lines: list[str], locale: str) -> dict[str, A
101101
name = f"{prev} {items[0]}"
102102
else:
103103
name = items[0]
104-
if name not in params:
104+
if technique == "Battery Capacity Determination" and name == "Set I/C":
105+
if "Set I/C 1" in params:
106+
params["Set I/C 2"] = vals
107+
else:
108+
params["Set I/C 1"] = vals
109+
elif name not in params:
105110
params[name] = vals
106111
else:
107-
raise RuntimeError(f"Trying to assing same parameter {items[0]!r} twice.")
112+
raise RuntimeError(f"Trying to assign same parameter {items[0]!r} twice.")
108113
prev = name
109114
return params
110115

src/yadg/extractors/eclab/techniques.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- MP - Modular Potentio
2121
- CoV - Constant Voltage
2222
- CoC - Constant Current
23+
- BCD - Battery Capacity Determination
2324
2425
The module also implements resolution determination for parameters of techniques,
2526
in :func:`get_resolution`.
@@ -1318,6 +1319,52 @@
13181319
)
13191320
]
13201321

1322+
# ~~~~~~~~~~~~~ Battery Capacity Determination ~~~~~~~~~~~~~
1323+
_bcd_params_dtypes = [
1324+
(
1325+
np.dtype(
1326+
[
1327+
("Set I/C 1", "|u1"),
1328+
("Is1", "<f4"),
1329+
("unit Is1", "|u1"),
1330+
("N1", "<f4"),
1331+
("I1 sign", "|u1"),
1332+
("t1 (h:m:s)", "<f4"),
1333+
("EM1 (V)", "<f4"),
1334+
("EM1 vs.", "|u1"),
1335+
("EM2 (V)", "<f4"),
1336+
("EM2 vs.", "|u1"),
1337+
("dE1 (mV)", "<f4"),
1338+
("dt1 (s)", "<f4"),
1339+
("Hold NONE/EM1/EM2", "|u1"),
1340+
("tM (h:m:s)", "<f4"),
1341+
("Set I limit", "|u1"),
1342+
("Im", "<f4"),
1343+
("unit Im", "|u1"),
1344+
("Nl", "<f4"),
1345+
("I Range", "|u1"),
1346+
("Bandwidth", "|u1"),
1347+
("E range min (V)", "<f4"),
1348+
("E range max (V)", "<f4"),
1349+
("tR (h:m:s)", "<f4"),
1350+
("dER/dt (mV/h)", "<f4"),
1351+
("dER (mV)", "<f4"),
1352+
("dtR (s)", "<f4"),
1353+
("Charge/Discharge", "|u1"),
1354+
("Set I/C 2", "|u1"),
1355+
("Is2", "<f4"),
1356+
("unit Is2", "|u1"),
1357+
("N2", "<f4"),
1358+
("I2 sign", "|u1"),
1359+
("t2 (h:m:s)", "<f4"),
1360+
("Return Ei", "|u1"),
1361+
("use C", "|u1"),
1362+
]
1363+
),
1364+
{"10.40"},
1365+
)
1366+
]
1367+
13211368

13221369
# Maps the technique byte to its corresponding dtype.
13231370
technique_params_dtypes = {
@@ -1338,6 +1385,7 @@
13381385
0x76: ("coC", _coc_params_dtypes),
13391386
0x77: ("GCPL", _gcpl_params_dtypes),
13401387
0x7F: ("MB", _mb_params_dtypes),
1388+
0x88: ("BCD", _bcd_params_dtypes),
13411389
}
13421390

13431391
unit_map = {
@@ -1371,6 +1419,12 @@
13711419
("Ref", 4),
13721420
)
13731421

1422+
ic_map = (
1423+
("I", 0),
1424+
("C / N", 1),
1425+
("C x N", 2),
1426+
)
1427+
13741428
param_map = {
13751429
"I Range": (
13761430
("10 mA", 1, 1e-2),
@@ -1582,6 +1636,8 @@
15821636
"unit Imax": unit_map["I"],
15831637
"unit Imin": unit_map["I"],
15841638
"unit Is": unit_map["I"],
1639+
"unit Is1": unit_map["I"],
1640+
"unit Is2": unit_map["I"],
15851641
"unit Is vs.": unit_map["vs."],
15861642
"E (V) vs.": vs_map,
15871643
"E1 (V) vs.": vs_map,
@@ -1590,12 +1646,11 @@
15901646
"Ei (V) vs.": vs_map,
15911647
"EL (V) vs.": vs_map,
15921648
"Es (V) vs.": vs_map,
1593-
"Set I/C": (
1594-
("I", 0),
1595-
("C", 1),
1596-
("C x N", 2),
1597-
("C / N", 3),
1598-
),
1649+
"EM1 vs.": vs_map,
1650+
"EM2 vs.": vs_map,
1651+
"Set I/C": ic_map,
1652+
"Set I/C 1": ic_map,
1653+
"Set I/C 2": ic_map,
15991654
"Apply I/C": (
16001655
("I", 0),
16011656
("C", 1),

tests/test_x_eclab.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def compare_params(left, right):
4343
@pytest.mark.parametrize(
4444
"froot, locale",
4545
[
46+
("bcd.issue_241", "en_US"),
4647
("ca", "en_US"),
4748
("ca.issue_134", "en_US"),
4849
("ca.issue_149", "de_DE"),
148 KB
Binary file not shown.
492 KB
Binary file not shown.

0 commit comments

Comments
 (0)