rosetta-eclab 0.1.3__tar.gz → 0.1.4__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.
Files changed (41) hide show
  1. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/PKG-INFO +2 -2
  2. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/README.md +1 -1
  3. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/library.py +5 -1
  4. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/mprfile.py +13 -1
  5. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/pyproject.toml +1 -1
  6. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/rosetta_eclab.egg-info/PKG-INFO +2 -2
  7. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/tests/test_cli.py +40 -0
  8. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/LICENSE +0 -0
  9. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/__init__.py +0 -0
  10. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/cli.py +0 -0
  11. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/data/stats.json +0 -0
  12. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/data/stats_features.npy +0 -0
  13. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/debug.py +0 -0
  14. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/engine.py +0 -0
  15. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/evaluate.py +0 -0
  16. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/fallback.py +0 -0
  17. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/galvani_ref.py +0 -0
  18. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/golden.py +0 -0
  19. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/namer/misses.py +0 -0
  20. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/rosetta_eclab.egg-info/SOURCES.txt +0 -0
  21. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/rosetta_eclab.egg-info/dependency_links.txt +0 -0
  22. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/rosetta_eclab.egg-info/entry_points.txt +0 -0
  23. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/rosetta_eclab.egg-info/requires.txt +0 -0
  24. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/rosetta_eclab.egg-info/top_level.txt +0 -0
  25. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/setup.cfg +0 -0
  26. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/__init__.py +0 -0
  27. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/agent.py +0 -0
  28. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/api_check.py +0 -0
  29. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/baselines.py +0 -0
  30. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/corpus.py +0 -0
  31. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/dsl.py +0 -0
  32. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/elim.py +0 -0
  33. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/features.py +0 -0
  34. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/galvani_0.5.0_colmap.json +0 -0
  35. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/galvani_snapshot.py +0 -0
  36. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/lock.py +0 -0
  37. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/run.py +0 -0
  38. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/score.py +0 -0
  39. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/tasks.py +0 -0
  40. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/spike2/vocab.py +0 -0
  41. {rosetta_eclab-0.1.3 → rosetta_eclab-0.1.4}/tests/test_relations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosetta-eclab
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Name the columns of BioLogic EC-Lab .mpr files that your parser does not know - no language model, evidence for every answer
5
5
  Author: Nataniel Ataseven
6
6
  License-Expression: GPL-3.0-or-later
@@ -139,7 +139,7 @@ The namer improved between hold-outs, so the total includes earlier versions. Th
139
139
  - **Values EC-Lab computes after the fact** cannot be reconstructed reliably. For example, a capacitance is held per segment.
140
140
  - **Noise-like DC currents** in impedance records, and **external analog inputs** with no sibling column, usually get ABSTAIN.
141
141
  - **Two quantities with the same definition in a file** cannot be told apart from the data. In galvanostatic cycling, for example, `dQ` and `Q charge or discharge` reset at the same points. A prior over how common each name is for the technique decides between them, and the alternative is shown.
142
- - **Supported formats:** EC-Lab data-module versions 2, 3, 10 and 11. Very old files (version 0) are not supported yet.
142
+ - **Supported formats:** EC-Lab data-module versions 0, 2, 3, 10 and 11. Version 0 (older EC-Lab) is read with galvani's offsets, because yadg does not read it. All 59 public version-0 files decode byte for byte like galvani 0.5.0, and the two with an EC-Lab text export match it. The file technique is not read from version-0 files, so the namer works without it.
143
143
 
144
144
  ## Reproduce the benchmark
145
145
 
@@ -114,7 +114,7 @@ The namer improved between hold-outs, so the total includes earlier versions. Th
114
114
  - **Values EC-Lab computes after the fact** cannot be reconstructed reliably. For example, a capacitance is held per segment.
115
115
  - **Noise-like DC currents** in impedance records, and **external analog inputs** with no sibling column, usually get ABSTAIN.
116
116
  - **Two quantities with the same definition in a file** cannot be told apart from the data. In galvanostatic cycling, for example, `dQ` and `Q charge or discharge` reset at the same points. A prior over how common each name is for the technique decides between them, and the alternative is shown.
117
- - **Supported formats:** EC-Lab data-module versions 2, 3, 10 and 11. Very old files (version 0) are not supported yet.
117
+ - **Supported formats:** EC-Lab data-module versions 0, 2, 3, 10 and 11. Version 0 (older EC-Lab) is read with galvani's offsets, because yadg does not read it. All 59 public version-0 files decode byte for byte like galvani 0.5.0, and the two with an EC-Lab text export match it. The file technique is not read from version-0 files, so the namer works without it.
118
118
 
119
119
  ## Reproduce the benchmark
120
120
 
@@ -561,7 +561,11 @@ def build() -> dict[str, Quantity]:
561
561
  q("z cycle", shapes=[Shape("integer counter in a sequence with impedance", lambda c: 0.75 * is_counter(c) if c.col("freq") is not None else 0.0)])
562
562
  q("Ns", shapes=[Shape("sequence index from 0 that changes with the control", lambda c: ns_shape(c))])
563
563
  q("I Range", shapes=[Shape("instrument range code (few values, not starting at 0)", lambda c: irange_shape(c))])
564
- q("Pwe", relations=[Relation("Ewe*I", ("Ewe", CURRENT), lambda c: _e(c) * _i(c), [W_PER_V_MA])])
564
+ q("Pwe", relations=[
565
+ Relation("Ewe*I", ("Ewe", CURRENT), lambda c: _e(c) * _i(c), [W_PER_V_MA]),
566
+ # some older EC-Lab files store the power's magnitude (galvani's bio_logic1.mpr and bio_logic3.mpr)
567
+ Relation("|Ewe*I|", ("Ewe", CURRENT), lambda c: np.abs(_e(c) * _i(c)), [W_PER_V_MA]),
568
+ ])
565
569
  q("Ewe", relations=[
566
570
  Relation("Pwe/I", ("Pwe", ("I", "dq", "(Q-Qo)")), lambda c: c.col("Pwe") / _i(c), [1000.0]),
567
571
  Relation("follows potential control", ("control",), lambda c: c.col("control"), [1.0]),
@@ -131,10 +131,19 @@ def read(path: str | Path, reference: str = "galvani") -> MprData:
131
131
  elif version in (2, 3):
132
132
  yids = full = np.frombuffer(data, offset=5, dtype="<u2", count=n_columns).tolist()
133
133
  offset = 0x195 if version == 2 else 0x196
134
+ elif version == 0: # older EC-Lab files; offsets as in galvani (yadg 7.0.1 does not read version 0)
135
+ if data[5]: # one byte per column ID; all public version-0 files use this layout
136
+ yids = full = list(data[5:5 + n_columns])
137
+ offset = 100
138
+ else: # galvani: EC-Lab 11.50+ writes [0 1 0 3 0 174 ...]; no public file to test against
139
+ yids = full = list(data[6:5 + 2 * n_columns:2])
140
+ offset = 1007
134
141
  else:
135
142
  raise NotImplementedError(f"data module version {version} is not supported yet")
143
+ if n_points == 0:
144
+ raise ValueError("the file has no data points")
136
145
  region = len(data) - offset
137
- if n_points == 0 or region % n_points:
146
+ if region % n_points:
138
147
  raise ValueError(f"record size is not a whole number of bytes ({region} / {n_points})")
139
148
  rec = region // n_points
140
149
 
@@ -142,6 +151,9 @@ def read(path: str | Path, reference: str = "galvani") -> MprData:
142
151
  gref = galvani_ref.reference() if reference == "galvani" else None
143
152
  gmap = gref.colmap if gref else {}
144
153
  notes = _galvani_notes(full, gref, version in (10, 11)) if gref else []
154
+ if version == 0 and reference == "yadg":
155
+ notes.append("yadg 7.0.1 cannot read this file at all (data module version 0); "
156
+ "'known' here means listed in yadg's column map")
145
157
  layout = [] # record layout in file order: (raw_id or None for the flags byte, ...)
146
158
  for yid, fid in zip(yids, full):
147
159
  if yid in FLAG_IDS: # all flag IDs share one byte, at the position of the first one
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rosetta-eclab"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "Name the columns of BioLogic EC-Lab .mpr files that your parser does not know - no language model, evidence for every answer"
9
9
  readme = "README.md"
10
10
  license = "GPL-3.0-or-later"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosetta-eclab
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Name the columns of BioLogic EC-Lab .mpr files that your parser does not know - no language model, evidence for every answer
5
5
  Author: Nataniel Ataseven
6
6
  License-Expression: GPL-3.0-or-later
@@ -139,7 +139,7 @@ The namer improved between hold-outs, so the total includes earlier versions. Th
139
139
  - **Values EC-Lab computes after the fact** cannot be reconstructed reliably. For example, a capacitance is held per segment.
140
140
  - **Noise-like DC currents** in impedance records, and **external analog inputs** with no sibling column, usually get ABSTAIN.
141
141
  - **Two quantities with the same definition in a file** cannot be told apart from the data. In galvanostatic cycling, for example, `dQ` and `Q charge or discharge` reset at the same points. A prior over how common each name is for the technique decides between them, and the alternative is shown.
142
- - **Supported formats:** EC-Lab data-module versions 2, 3, 10 and 11. Very old files (version 0) are not supported yet.
142
+ - **Supported formats:** EC-Lab data-module versions 0, 2, 3, 10 and 11. Version 0 (older EC-Lab) is read with galvani's offsets, because yadg does not read it. All 59 public version-0 files decode byte for byte like galvani 0.5.0, and the two with an EC-Lab text export match it. The file technique is not read from version-0 files, so the namer works without it.
143
143
 
144
144
  ## Reproduce the benchmark
145
145
 
@@ -45,6 +45,46 @@ def test_reader_matches_reference_decoder():
45
45
  assert a == b, name
46
46
 
47
47
 
48
+ def _mpt(path: Path) -> dict[str, np.ndarray]:
49
+ lines = path.read_bytes().decode("latin-1").splitlines()
50
+ n_header = int(lines[1].split(":")[1])
51
+ head = lines[n_header - 1].rstrip("\t").split("\t")
52
+ rows = [line.rstrip("\t").split("\t") for line in lines[n_header:] if line.strip()]
53
+ return {h: np.array([float(r[i].replace(",", ".")) for r in rows]) for i, h in enumerate(head)}
54
+
55
+
56
+ def test_version_0_files_match_the_vendor_export():
57
+ """Data module version 0 (older EC-Lab): yadg cannot read it; the values must match EC-Lab's own .mpt."""
58
+ m = mprfile.read(EXT / "bio_logic1.mpr", reference="galvani")
59
+ mpt = _mpt(EXT / "bio_logic1.mpt")
60
+ cols = {c.name: c.values.astype("f8") for c in m.columns}
61
+ assert m.n_points == len(mpt["time/s"]) == 3119 and not m.unknown
62
+ for name, ref in (("time", "time/s"), ("Ewe", "Ewe/V"), ("dq", "dq/mA.h"), ("Pwe", "P/W")):
63
+ np.testing.assert_allclose(cols[name], mpt[ref], rtol=1e-6, atol=1e-9, err_msg=name)
64
+ assert any("yadg 7.0.1 cannot read" in n for n in mprfile.read(EXT / "bio_logic1.mpr", reference="yadg").notes)
65
+ assert not any("yadg" in n for n in m.notes)
66
+
67
+
68
+ def test_empty_file_is_reported_plainly():
69
+ with pytest.raises(ValueError, match="no data points"):
70
+ mprfile.read(EXT / "wait.mpr")
71
+
72
+
73
+ def test_power_magnitude_in_version_0_file_is_named(tmp_path):
74
+ """Version-0 files store |Ewe*I| as the power; hidden, it must come back as Pwe (it was named Ece)."""
75
+ raw = bytearray((EXT / "bio_logic1.mpr").read_bytes())
76
+ mdata = next(md for name, _v, md in mprfile._modules(bytes(raw)) if name == "VMP data")
77
+ start = bytes(raw).index(mdata)
78
+ ids = list(raw[start + 5: start + 5 + raw[start + 4]])
79
+ raw[start + 5 + ids.index(70)] = 255 # one-byte column IDs in version 0
80
+ f = tmp_path / "old_drift.mpr"
81
+ f.write_bytes(bytes(raw))
82
+ m = mprfile.read(f, reference="galvani")
83
+ assert [c.raw_id for c in m.unknown] == [255]
84
+ res = cli.name_unknown(m)
85
+ assert res[0]["verdict"] == "NAME" and res[0]["name"] == "Pwe", res[0]
86
+
87
+
48
88
  def test_real_drift_duplicates_are_reported():
49
89
  m = mprfile.read(EXT / "gcpl.issue_230.mpr", reference="galvani")
50
90
  assert [c.raw_id for c in m.unknown] == [883, 884]
File without changes
File without changes