rosetta-eclab 0.1.2__tar.gz → 0.1.3__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.2 → rosetta_eclab-0.1.3}/PKG-INFO +6 -4
  2. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/README.md +4 -2
  3. rosetta_eclab-0.1.3/namer/galvani_ref.py +67 -0
  4. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/library.py +2 -2
  5. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/mprfile.py +20 -13
  6. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/pyproject.toml +6 -3
  7. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/rosetta_eclab.egg-info/PKG-INFO +6 -4
  8. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/rosetta_eclab.egg-info/SOURCES.txt +3 -0
  9. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/corpus.py +2 -2
  10. rosetta_eclab-0.1.3/spike2/galvani_0.5.0_colmap.json +129 -0
  11. rosetta_eclab-0.1.3/spike2/galvani_snapshot.py +48 -0
  12. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/tests/test_cli.py +37 -1
  13. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/LICENSE +0 -0
  14. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/__init__.py +0 -0
  15. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/cli.py +0 -0
  16. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/data/stats.json +0 -0
  17. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/data/stats_features.npy +0 -0
  18. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/debug.py +0 -0
  19. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/engine.py +0 -0
  20. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/evaluate.py +0 -0
  21. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/fallback.py +0 -0
  22. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/golden.py +0 -0
  23. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/namer/misses.py +0 -0
  24. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/rosetta_eclab.egg-info/dependency_links.txt +0 -0
  25. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/rosetta_eclab.egg-info/entry_points.txt +0 -0
  26. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/rosetta_eclab.egg-info/requires.txt +1 -1
  27. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/rosetta_eclab.egg-info/top_level.txt +0 -0
  28. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/setup.cfg +0 -0
  29. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/__init__.py +0 -0
  30. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/agent.py +0 -0
  31. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/api_check.py +0 -0
  32. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/baselines.py +0 -0
  33. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/dsl.py +0 -0
  34. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/elim.py +0 -0
  35. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/features.py +0 -0
  36. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/lock.py +0 -0
  37. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/run.py +0 -0
  38. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/score.py +0 -0
  39. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/tasks.py +0 -0
  40. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/spike2/vocab.py +0 -0
  41. {rosetta_eclab-0.1.2 → rosetta_eclab-0.1.3}/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.2
3
+ Version: 0.1.3
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
@@ -18,9 +18,9 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: numpy>=2.0
20
20
  Requires-Dist: yadg==7.0.1
21
- Requires-Dist: galvani==0.5.0
22
21
  Provides-Extra: test
23
22
  Requires-Dist: pytest; extra == "test"
23
+ Requires-Dist: galvani==0.5.0; extra == "test"
24
24
  Dynamic: license-file
25
25
 
26
26
  # Rosetta
@@ -52,6 +52,8 @@ Python 3.10 or later:
52
52
  pip install rosetta-eclab
53
53
  ```
54
54
 
55
+ Rosetta does not install galvani. If galvani or datalab-org-galvani is installed, "unknown to galvani" means unknown to the galvani you actually have. Without either, Rosetta uses a frozen copy of galvani 0.5.0's column map and says so. The names Rosetta gives never depend on which galvani is installed.
56
+
55
57
  To run the tests or reproduce the benchmark, clone the repository instead:
56
58
 
57
59
  ```sh
@@ -96,7 +98,7 @@ peis.issue_225.with_ece.mpr
96
98
  every column ID is in galvani's column map; nothing to name
97
99
  ```
98
100
 
99
- The fix was released in datalab-org-galvani 0.6.0 ([galvani-mirror#11](https://github.com/datalab-org/galvani-mirror/issues/11)). IDs that are missing from the map altogether, such as 880 or 883 and 884, are what Rosetta names.
101
+ The fix was released in datalab-org-galvani 0.6.0 ([galvani-mirror#11](https://github.com/datalab-org/galvani-mirror/issues/11)). Rosetta prints the note only when the installed galvani reads the low byte. IDs that are missing from the map altogether, such as 880 or 883 and 884, are what Rosetta names.
100
102
 
101
103
  ## How it works
102
104
 
@@ -155,4 +157,4 @@ pytest tests
155
157
 
156
158
  ## Data and licence
157
159
 
158
- Rosetta is free software under the **GNU General Public License v3.0 or later** (see `LICENSE`). It uses [yadg](https://github.com/dgbowl/yadg) and [galvani](https://github.com/echemdata/galvani), both GPL. Measurement files are downloaded at run time and never committed. Sources, licences and attributions are listed in [`DATA_SOURCES.md`](https://github.com/Nataniel0212/rosetta-eclab/blob/main/DATA_SOURCES.md). Rosetta is not affiliated with BioLogic.
160
+ Rosetta is free software under the **GNU General Public License v3.0 or later** (see `LICENSE`). It uses [yadg](https://github.com/dgbowl/yadg) (GPL), and `spike2/galvani_0.5.0_colmap.json` is a copy of the column map of [galvani](https://github.com/echemdata/galvani) 0.5.0 (GPL-3.0-or-later). Measurement files are downloaded at run time and never committed. Sources, licences and attributions are listed in [`DATA_SOURCES.md`](https://github.com/Nataniel0212/rosetta-eclab/blob/main/DATA_SOURCES.md). Rosetta is not affiliated with BioLogic.
@@ -27,6 +27,8 @@ Python 3.10 or later:
27
27
  pip install rosetta-eclab
28
28
  ```
29
29
 
30
+ Rosetta does not install galvani. If galvani or datalab-org-galvani is installed, "unknown to galvani" means unknown to the galvani you actually have. Without either, Rosetta uses a frozen copy of galvani 0.5.0's column map and says so. The names Rosetta gives never depend on which galvani is installed.
31
+
30
32
  To run the tests or reproduce the benchmark, clone the repository instead:
31
33
 
32
34
  ```sh
@@ -71,7 +73,7 @@ peis.issue_225.with_ece.mpr
71
73
  every column ID is in galvani's column map; nothing to name
72
74
  ```
73
75
 
74
- The fix was released in datalab-org-galvani 0.6.0 ([galvani-mirror#11](https://github.com/datalab-org/galvani-mirror/issues/11)). IDs that are missing from the map altogether, such as 880 or 883 and 884, are what Rosetta names.
76
+ The fix was released in datalab-org-galvani 0.6.0 ([galvani-mirror#11](https://github.com/datalab-org/galvani-mirror/issues/11)). Rosetta prints the note only when the installed galvani reads the low byte. IDs that are missing from the map altogether, such as 880 or 883 and 884, are what Rosetta names.
75
77
 
76
78
  ## How it works
77
79
 
@@ -130,4 +132,4 @@ pytest tests
130
132
 
131
133
  ## Data and licence
132
134
 
133
- Rosetta is free software under the **GNU General Public License v3.0 or later** (see `LICENSE`). It uses [yadg](https://github.com/dgbowl/yadg) and [galvani](https://github.com/echemdata/galvani), both GPL. Measurement files are downloaded at run time and never committed. Sources, licences and attributions are listed in [`DATA_SOURCES.md`](https://github.com/Nataniel0212/rosetta-eclab/blob/main/DATA_SOURCES.md). Rosetta is not affiliated with BioLogic.
135
+ Rosetta is free software under the **GNU General Public License v3.0 or later** (see `LICENSE`). It uses [yadg](https://github.com/dgbowl/yadg) (GPL), and `spike2/galvani_0.5.0_colmap.json` is a copy of the column map of [galvani](https://github.com/echemdata/galvani) 0.5.0 (GPL-3.0-or-later). Measurement files are downloaded at run time and never committed. Sources, licences and attributions are listed in [`DATA_SOURCES.md`](https://github.com/Nataniel0212/rosetta-eclab/blob/main/DATA_SOURCES.md). Rosetta is not affiliated with BioLogic.
@@ -0,0 +1,67 @@
1
+ """galvani as a reference parser, without depending on one particular galvani.
2
+
3
+ Two distributions install the module `galvani`: galvani (echemdata, 0.5.0 on PyPI)
4
+ and datalab-org-galvani (0.6.0 reads EC-Lab 11.50+ column IDs in full). Rosetta
5
+ does not install either, so it never overwrites the one a user already has.
6
+
7
+ reference() returns the installed galvani's own column map, and whether that
8
+ galvani reads only the low byte of each column ID in the 11.50+ layout. Without
9
+ galvani installed, the frozen 0.5.0 map (spike2/galvani_snapshot.py) stands in.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import inspect
15
+ from dataclasses import dataclass
16
+
17
+ from spike2.galvani_snapshot import snapshot_map
18
+
19
+ LOW_BYTE_IDIOM = "column_types[1::2]" # galvani 0.5.0 keeps every second byte of the ID array
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class Reference:
24
+ colmap: dict[int, tuple[str, str]]
25
+ label: str # e.g. "galvani 0.5.0", "datalab-org-galvani 0.6.0"
26
+ low_byte: bool | None # True: reads only the low byte of 11.50+ column IDs; None: cannot tell
27
+ installed: bool
28
+
29
+
30
+ def _label() -> str:
31
+ from importlib.metadata import PackageNotFoundError, packages_distributions, version
32
+
33
+ parts = []
34
+ for d in sorted(set(packages_distributions().get("galvani", []))):
35
+ try:
36
+ parts.append(f"{d} {version(d)}")
37
+ except PackageNotFoundError:
38
+ parts.append(d)
39
+ if not parts:
40
+ return "galvani"
41
+ if len(parts) == 1:
42
+ return parts[0]
43
+ return f"galvani (installed by both {' and '.join(parts)}; its files are from whichever was installed last)"
44
+
45
+
46
+ def _reads_low_byte(biologic) -> bool | None:
47
+ try:
48
+ src = inspect.getsource(biologic.MPRfile.__init__)
49
+ except (OSError, TypeError, AttributeError):
50
+ return None
51
+ return LOW_BYTE_IDIOM in src
52
+
53
+
54
+ def installed() -> Reference | None:
55
+ try:
56
+ from galvani import BioLogic
57
+ except ImportError:
58
+ return None
59
+ colmap = {int(k): (v[0], v[1]) for k, v in BioLogic.VMPdata_colID_dtype_map.items()}
60
+ return Reference(colmap, _label(), _reads_low_byte(BioLogic), installed=True)
61
+
62
+
63
+ def reference() -> Reference:
64
+ ref = installed()
65
+ if ref is not None:
66
+ return ref
67
+ return Reference(snapshot_map(), "galvani 0.5.0", low_byte=True, installed=False)
@@ -451,15 +451,15 @@ def is_code(ctx, lo=0, hi=200, max_distinct=8) -> float:
451
451
  # --- expected storage types (public parser maps: yadg 7.0.1 and galvani 0.5.0) ---------
452
452
 
453
453
  def expected_dtypes() -> dict[str, set[str]]:
454
- from galvani import BioLogic
455
454
  from yadg.extractors.eclab.mpr_columns import data_columns
456
455
 
457
456
  from spike2 import vocab
457
+ from spike2.galvani_snapshot import snapshot_map # frozen 0.5.0 map, whichever galvani is installed
458
458
 
459
459
  out: dict[str, set[str]] = {}
460
460
  for dt, name, _unit in data_columns.values():
461
461
  out.setdefault(vocab.canonical(name), set()).add(dt)
462
- for full, dt in BioLogic.VMPdata_colID_dtype_map.values():
462
+ for full, dt in snapshot_map().values():
463
463
  name = full.split("/")[0] if not full.startswith("Q charge/discharge") else "Q charge/discharge"
464
464
  out.setdefault(vocab.canonical(name), set()).add(dt)
465
465
  return out
@@ -16,6 +16,8 @@ from pathlib import Path
16
16
 
17
17
  import numpy as np
18
18
 
19
+ from . import galvani_ref
20
+
19
21
  FLAG_IDS = (1, 2, 3, 21, 31, 65)
20
22
  CANDIDATES = {1: ["|u1"], 2: ["<u2", "<i2"], 4: ["<f4", "<u4", "<i4"], 8: ["<f8", "<u8", "<i8"]}
21
23
 
@@ -79,25 +81,29 @@ def _plausibility(v: np.ndarray, dtype: str) -> float:
79
81
  return 0.9 if span < 2 ** (8 * width - 4) else 0.2
80
82
 
81
83
 
82
- def _galvani_low_byte_note(full_ids: list[int], gmap: dict) -> list[str]:
83
- """galvani 0.5.0 reads only the low byte of each column ID in the 11.50+ layout.
84
+ def _galvani_notes(full_ids: list[int], ref: galvani_ref.Reference, layout_11_50: bool) -> list[str]:
85
+ """What the reference galvani makes of this file, beyond plain unknown IDs.
84
86
 
87
+ galvani 0.5.0 reads only the low byte of each column ID in the 11.50+ layout.
85
88
  IDs above 255 then become other IDs (471 -> 215), which galvani either does not
86
89
  know (it stops) or knows as a different column. Its map repeats a few entries at
87
90
  the low-byte ID (211 = 467), so only IDs whose low-byte entry differs are listed.
88
91
  """
89
- misread = [x for x in full_ids if x > 255 and gmap.get(x & 0xFF) != gmap.get(x)]
90
- if not misread:
91
- return []
92
- from importlib.metadata import version
93
-
94
- pairs = ", ".join(f"{x} as {x & 0xFF}" for x in misread)
95
- return [f"galvani {version('galvani')} reads only the low byte of each column ID in this file layout, "
96
- f"so it misreads {pairs}"]
92
+ notes = []
93
+ if not ref.installed:
94
+ notes.append("galvani is not installed, so 'known to galvani' means the column map of galvani 0.5.0 "
95
+ "(bundled with Rosetta)")
96
+ if layout_11_50 and ref.low_byte:
97
+ gmap = ref.colmap
98
+ misread = [x for x in full_ids if x > 255 and gmap.get(x & 0xFF) != gmap.get(x)]
99
+ if misread:
100
+ pairs = ", ".join(f"{x} as {x & 0xFF}" for x in misread)
101
+ notes.append(f"{ref.label} reads only the low byte of each column ID in this file layout, "
102
+ f"so it misreads {pairs}")
103
+ return notes
97
104
 
98
105
 
99
106
  def read(path: str | Path, reference: str = "galvani") -> MprData:
100
- from galvani import BioLogic
101
107
  from yadg.extractors.eclab.mpr import parse_columns, process_settings
102
108
 
103
109
  logging.getLogger("yadg").setLevel(logging.CRITICAL)
@@ -133,8 +139,9 @@ def read(path: str | Path, reference: str = "galvani") -> MprData:
133
139
  rec = region // n_points
134
140
 
135
141
  names, dtypes, units, _ = parse_columns(yids, technique) # yadg's map: names for known columns
136
- gmap = {int(k): v for k, v in BioLogic.VMPdata_colID_dtype_map.items()}
137
- notes = _galvani_low_byte_note(full, gmap) if version in (10, 11) and reference == "galvani" else []
142
+ gref = galvani_ref.reference() if reference == "galvani" else None
143
+ gmap = gref.colmap if gref else {}
144
+ notes = _galvani_notes(full, gref, version in (10, 11)) if gref else []
138
145
  layout = [] # record layout in file order: (raw_id or None for the flags byte, ...)
139
146
  for yid, fid in zip(yids, full):
140
147
  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.2"
7
+ version = "0.1.3"
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"
@@ -19,10 +19,12 @@ classifiers = [
19
19
  "Programming Language :: Python :: 3",
20
20
  "Topic :: Scientific/Engineering :: Chemistry",
21
21
  ]
22
+ # galvani is not a dependency: two distributions install the module (galvani and datalab-org-galvani),
23
+ # and requiring one would overwrite the other. Rosetta uses whichever is installed, or a frozen copy
24
+ # of galvani 0.5.0's column map (spike2/galvani_0.5.0_colmap.json).
22
25
  dependencies = [
23
26
  "numpy>=2.0",
24
27
  "yadg==7.0.1",
25
- "galvani==0.5.0",
26
28
  ]
27
29
 
28
30
  [project.urls]
@@ -30,7 +32,7 @@ Repository = "https://github.com/Nataniel0212/rosetta-eclab"
30
32
  Issues = "https://github.com/Nataniel0212/rosetta-eclab/issues"
31
33
 
32
34
  [project.optional-dependencies]
33
- test = ["pytest"]
35
+ test = ["pytest", "galvani==0.5.0"]
34
36
 
35
37
  [project.scripts]
36
38
  rosetta = "rosetta_eclab.cli:main"
@@ -45,3 +47,4 @@ rosetta_eclab = "namer"
45
47
 
46
48
  [tool.setuptools.package-data]
47
49
  rosetta_eclab = ["data/*.json", "data/*.npy"]
50
+ spike2 = ["*.json"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosetta-eclab
3
- Version: 0.1.2
3
+ Version: 0.1.3
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
@@ -18,9 +18,9 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: numpy>=2.0
20
20
  Requires-Dist: yadg==7.0.1
21
- Requires-Dist: galvani==0.5.0
22
21
  Provides-Extra: test
23
22
  Requires-Dist: pytest; extra == "test"
23
+ Requires-Dist: galvani==0.5.0; extra == "test"
24
24
  Dynamic: license-file
25
25
 
26
26
  # Rosetta
@@ -52,6 +52,8 @@ Python 3.10 or later:
52
52
  pip install rosetta-eclab
53
53
  ```
54
54
 
55
+ Rosetta does not install galvani. If galvani or datalab-org-galvani is installed, "unknown to galvani" means unknown to the galvani you actually have. Without either, Rosetta uses a frozen copy of galvani 0.5.0's column map and says so. The names Rosetta gives never depend on which galvani is installed.
56
+
55
57
  To run the tests or reproduce the benchmark, clone the repository instead:
56
58
 
57
59
  ```sh
@@ -96,7 +98,7 @@ peis.issue_225.with_ece.mpr
96
98
  every column ID is in galvani's column map; nothing to name
97
99
  ```
98
100
 
99
- The fix was released in datalab-org-galvani 0.6.0 ([galvani-mirror#11](https://github.com/datalab-org/galvani-mirror/issues/11)). IDs that are missing from the map altogether, such as 880 or 883 and 884, are what Rosetta names.
101
+ The fix was released in datalab-org-galvani 0.6.0 ([galvani-mirror#11](https://github.com/datalab-org/galvani-mirror/issues/11)). Rosetta prints the note only when the installed galvani reads the low byte. IDs that are missing from the map altogether, such as 880 or 883 and 884, are what Rosetta names.
100
102
 
101
103
  ## How it works
102
104
 
@@ -155,4 +157,4 @@ pytest tests
155
157
 
156
158
  ## Data and licence
157
159
 
158
- Rosetta is free software under the **GNU General Public License v3.0 or later** (see `LICENSE`). It uses [yadg](https://github.com/dgbowl/yadg) and [galvani](https://github.com/echemdata/galvani), both GPL. Measurement files are downloaded at run time and never committed. Sources, licences and attributions are listed in [`DATA_SOURCES.md`](https://github.com/Nataniel0212/rosetta-eclab/blob/main/DATA_SOURCES.md). Rosetta is not affiliated with BioLogic.
160
+ Rosetta is free software under the **GNU General Public License v3.0 or later** (see `LICENSE`). It uses [yadg](https://github.com/dgbowl/yadg) (GPL), and `spike2/galvani_0.5.0_colmap.json` is a copy of the column map of [galvani](https://github.com/echemdata/galvani) 0.5.0 (GPL-3.0-or-later). Measurement files are downloaded at run time and never committed. Sources, licences and attributions are listed in [`DATA_SOURCES.md`](https://github.com/Nataniel0212/rosetta-eclab/blob/main/DATA_SOURCES.md). Rosetta is not affiliated with BioLogic.
@@ -7,6 +7,7 @@ namer/debug.py
7
7
  namer/engine.py
8
8
  namer/evaluate.py
9
9
  namer/fallback.py
10
+ namer/galvani_ref.py
10
11
  namer/golden.py
11
12
  namer/library.py
12
13
  namer/misses.py
@@ -27,6 +28,8 @@ spike2/corpus.py
27
28
  spike2/dsl.py
28
29
  spike2/elim.py
29
30
  spike2/features.py
31
+ spike2/galvani_0.5.0_colmap.json
32
+ spike2/galvani_snapshot.py
30
33
  spike2/lock.py
31
34
  spike2/run.py
32
35
  spike2/score.py
@@ -96,9 +96,9 @@ def decode(path: Path) -> FileData:
96
96
  raise NotImplementedError(f"data module version {data_version}")
97
97
  names, dtypes, units, _flags = parse_columns(ids, technique)
98
98
  arr = np.frombuffer(data, offset=offset, dtype=np.dtype(list(zip(names, dtypes))), count=n_points)
99
- from galvani import BioLogic
99
+ from .galvani_snapshot import snapshot_map
100
100
 
101
- km = {int(k): v for k, v in BioLogic.VMPdata_colID_dtype_map.items()} # galvani 0.5.0: full IDs it knows
101
+ km = snapshot_map() # galvani 0.5.0 (frozen copy): full IDs it knows
102
102
  cols, pos = [], 0
103
103
  # yadg's own flag test (on its reading of the IDs) decides which entries are flags
104
104
  data_ids = [(i, f) for i, f in zip(ids, full_ids) if i not in (1, 2, 3, 21, 31, 65)]
@@ -0,0 +1,129 @@
1
+ {
2
+ "source": "galvani 0.5.0 (GPL-3.0), galvani/BioLogic.py, VMPdata_colID_dtype_map: column ID -> [name/unit, numpy dtype]",
3
+ "columns": {
4
+ "4": ["time/s", "<f8"],
5
+ "5": ["control/V/mA", "<f4"],
6
+ "6": ["Ewe/V", "<f4"],
7
+ "7": ["dq/mA.h", "<f8"],
8
+ "8": ["I/mA", "<f4"],
9
+ "9": ["Ece/V", "<f4"],
10
+ "11": ["<I>/mA", "<f8"],
11
+ "13": ["(Q-Qo)/mA.h", "<f8"],
12
+ "16": ["Analog IN 1/V", "<f4"],
13
+ "17": ["Analog IN 2/V", "<f4"],
14
+ "19": ["control/V", "<f4"],
15
+ "20": ["control/mA", "<f4"],
16
+ "23": ["dQ/mA.h", "<f8"],
17
+ "24": ["cycle number", "<f8"],
18
+ "26": ["Rapp/Ohm", "<f4"],
19
+ "27": ["Ewe-Ece/V", "<f4"],
20
+ "32": ["freq/Hz", "<f4"],
21
+ "33": ["|Ewe|/V", "<f4"],
22
+ "34": ["|I|/A", "<f4"],
23
+ "35": ["Phase(Z)/deg", "<f4"],
24
+ "36": ["|Z|/Ohm", "<f4"],
25
+ "37": ["Re(Z)/Ohm", "<f4"],
26
+ "38": ["-Im(Z)/Ohm", "<f4"],
27
+ "39": ["I Range", "<u2"],
28
+ "69": ["R/Ohm", "<f4"],
29
+ "70": ["P/W", "<f4"],
30
+ "74": ["|Energy|/W.h", "<f8"],
31
+ "75": ["Analog OUT/V", "<f4"],
32
+ "76": ["<I>/mA", "<f4"],
33
+ "77": ["<Ewe>/V", "<f4"],
34
+ "78": ["Cs-2/µF-2", "<f4"],
35
+ "96": ["|Ece|/V", "<f4"],
36
+ "98": ["Phase(Zce)/deg", "<f4"],
37
+ "99": ["|Zce|/Ohm", "<f4"],
38
+ "100": ["Re(Zce)/Ohm", "<f4"],
39
+ "101": ["-Im(Zce)/Ohm", "<f4"],
40
+ "123": ["Energy charge/W.h", "<f8"],
41
+ "124": ["Energy discharge/W.h", "<f8"],
42
+ "125": ["Capacitance charge/µF", "<f8"],
43
+ "126": ["Capacitance discharge/µF", "<f8"],
44
+ "131": ["Ns", "<u2"],
45
+ "163": ["|Estack|/V", "<f4"],
46
+ "168": ["Rcmp/Ohm", "<f4"],
47
+ "169": ["Cs/µF", "<f4"],
48
+ "172": ["Cp/µF", "<f4"],
49
+ "173": ["Cp-2/µF-2", "<f4"],
50
+ "174": ["<Ewe>/V", "<f4"],
51
+ "178": ["(Q-Qo)/C", "<f4"],
52
+ "179": ["dQ/C", "<f4"],
53
+ "211": ["Q charge/discharge/mA.h", "<f8"],
54
+ "212": ["half cycle", "<u4"],
55
+ "213": ["z cycle", "<u4"],
56
+ "217": ["THD Ewe/%", "<f4"],
57
+ "218": ["THD I/%", "<f4"],
58
+ "220": ["NSD Ewe/%", "<f4"],
59
+ "221": ["NSD I/%", "<f4"],
60
+ "223": ["NSR Ewe/%", "<f4"],
61
+ "224": ["NSR I/%", "<f4"],
62
+ "230": ["|Ewe h2|/V", "<f4"],
63
+ "231": ["|Ewe h3|/V", "<f4"],
64
+ "232": ["|Ewe h4|/V", "<f4"],
65
+ "233": ["|Ewe h5|/V", "<f4"],
66
+ "234": ["|Ewe h6|/V", "<f4"],
67
+ "235": ["|Ewe h7|/V", "<f4"],
68
+ "236": ["|I h2|/A", "<f4"],
69
+ "237": ["|I h3|/A", "<f4"],
70
+ "238": ["|I h4|/A", "<f4"],
71
+ "239": ["|I h5|/A", "<f4"],
72
+ "240": ["|I h6|/A", "<f4"],
73
+ "241": ["|I h7|/A", "<f4"],
74
+ "242": ["|E2|/V", "<f4"],
75
+ "271": ["Phase(Z1) / deg", "<f4"],
76
+ "272": ["Phase(Z2) / deg", "<f4"],
77
+ "301": ["|Z1|/Ohm", "<f4"],
78
+ "302": ["|Z2|/Ohm", "<f4"],
79
+ "331": ["Re(Z1)/Ohm", "<f4"],
80
+ "332": ["Re(Z2)/Ohm", "<f4"],
81
+ "361": ["-Im(Z1)/Ohm", "<f4"],
82
+ "362": ["-Im(Z2)/Ohm", "<f4"],
83
+ "391": ["<E1>/V", "<f4"],
84
+ "392": ["<E2>/V", "<f4"],
85
+ "422": ["Phase(Zstack)/deg", "<f4"],
86
+ "423": ["|Zstack|/Ohm", "<f4"],
87
+ "424": ["Re(Zstack)/Ohm", "<f4"],
88
+ "425": ["-Im(Zstack)/Ohm", "<f4"],
89
+ "426": ["<Estack>/V", "<f4"],
90
+ "430": ["Phase(Zwe-ce)/deg", "<f4"],
91
+ "431": ["|Zwe-ce|/Ohm", "<f4"],
92
+ "432": ["Re(Zwe-ce)/Ohm", "<f4"],
93
+ "433": ["-Im(Zwe-ce)/Ohm", "<f4"],
94
+ "434": ["(Q-Qo)/C", "<f4"],
95
+ "435": ["dQ/C", "<f4"],
96
+ "438": ["step time/s", "<f8"],
97
+ "441": ["<Ecv>/V", "<f4"],
98
+ "462": ["Temperature/°C", "<f4"],
99
+ "467": ["Q charge/discharge/mA.h", "<f8"],
100
+ "468": ["half cycle", "<u4"],
101
+ "469": ["z cycle", "<u4"],
102
+ "471": ["<Ece>/V", "<f4"],
103
+ "473": ["THD Ewe/%", "<f4"],
104
+ "474": ["THD I/%", "<f4"],
105
+ "476": ["NSD Ewe/%", "<f4"],
106
+ "477": ["NSD I/%", "<f4"],
107
+ "479": ["NSR Ewe/%", "<f4"],
108
+ "480": ["NSR I/%", "<f4"],
109
+ "486": ["|Ewe h2|/V", "<f4"],
110
+ "487": ["|Ewe h3|/V", "<f4"],
111
+ "488": ["|Ewe h4|/V", "<f4"],
112
+ "489": ["|Ewe h5|/V", "<f4"],
113
+ "490": ["|Ewe h6|/V", "<f4"],
114
+ "491": ["|Ewe h7|/V", "<f4"],
115
+ "492": ["|I h2|/A", "<f4"],
116
+ "493": ["|I h3|/A", "<f4"],
117
+ "494": ["|I h4|/A", "<f4"],
118
+ "495": ["|I h5|/A", "<f4"],
119
+ "496": ["|I h6|/A", "<f4"],
120
+ "497": ["|I h7|/A", "<f4"],
121
+ "498": ["Q charge/mA.h", "<f8"],
122
+ "499": ["Q discharge/mA.h", "<f8"],
123
+ "500": ["step time/s", "<f8"],
124
+ "501": ["Efficiency/%", "<f8"],
125
+ "502": ["Capacity/mA.h", "<f8"],
126
+ "505": ["Rdc/Ohm", "<f4"],
127
+ "509": ["Acir/Dcir Control", "<u1"]
128
+ }
129
+ }
@@ -0,0 +1,48 @@
1
+ """galvani 0.5.0's column map, frozen in this repository.
2
+
3
+ The benchmarks and the namer's expected storage types use this copy, so their
4
+ answers do not depend on which galvani (if any) is installed. namer/galvani_ref.py
5
+ uses the installed galvani for "known to galvani" instead.
6
+
7
+ python -m spike2.galvani_snapshot --write # regenerate from an installed galvani 0.5.0
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import sys
14
+ from functools import lru_cache
15
+ from pathlib import Path
16
+
17
+ SNAPSHOT = Path(__file__).resolve().parent / "galvani_0.5.0_colmap.json"
18
+ SOURCE = "galvani 0.5.0 (GPL-3.0), galvani/BioLogic.py, VMPdata_colID_dtype_map: column ID -> [name/unit, numpy dtype]"
19
+
20
+
21
+ @lru_cache(maxsize=1)
22
+ def snapshot_map() -> dict[int, tuple[str, str]]:
23
+ d = json.loads(SNAPSHOT.read_text(encoding="utf-8"))
24
+ return {int(k): (v[0], v[1]) for k, v in d["columns"].items()}
25
+
26
+
27
+ def write() -> int:
28
+ from importlib.metadata import PackageNotFoundError, version
29
+
30
+ try:
31
+ ok = version("galvani") == "0.5.0"
32
+ except PackageNotFoundError:
33
+ ok = False
34
+ if not ok:
35
+ print("needs galvani 0.5.0 installed (pip install galvani==0.5.0)", file=sys.stderr)
36
+ return 1
37
+ from galvani import BioLogic
38
+
39
+ rows = [f" {json.dumps(str(k))}: {json.dumps([v[0], v[1]], ensure_ascii=False)}"
40
+ for k, v in sorted(BioLogic.VMPdata_colID_dtype_map.items())]
41
+ text = '{\n"source": ' + json.dumps(SOURCE) + ',\n"columns": {\n' + ",\n".join(rows) + "\n}\n}\n"
42
+ SNAPSHOT.write_text(text, encoding="utf-8", newline="\n")
43
+ print(f"wrote {SNAPSHOT} ({len(rows)} columns)")
44
+ return 0
45
+
46
+
47
+ if __name__ == "__main__":
48
+ raise SystemExit(write() if "--write" in sys.argv else 0)
@@ -11,8 +11,9 @@ from pathlib import Path
11
11
  import numpy as np
12
12
  import pytest
13
13
 
14
- from namer import cli, mprfile
14
+ from namer import cli, galvani_ref, mprfile
15
15
  from spike2 import corpus
16
+ from spike2.galvani_snapshot import snapshot_map
16
17
 
17
18
  EXT = corpus.EXT
18
19
  pytestmark = pytest.mark.skipif(not (EXT / "gcpl.mpr").exists(), reason="run scripts/fetch_testdata.py first")
@@ -60,6 +61,41 @@ def test_galvani_low_byte_misreads_are_reported():
60
61
  assert not any("low byte" in n for n in mprfile.read(EXT / "peis.issue_225.with_ece.mpr", reference="yadg").notes)
61
62
 
62
63
 
64
+ def _galvani_version() -> str | None:
65
+ from importlib.metadata import PackageNotFoundError, version
66
+
67
+ try:
68
+ return version("galvani")
69
+ except PackageNotFoundError:
70
+ return None
71
+
72
+
73
+ def test_frozen_map_is_galvani_050():
74
+ if _galvani_version() != "0.5.0":
75
+ pytest.skip("needs galvani 0.5.0 (pip install '.[test]')")
76
+ from galvani import BioLogic
77
+
78
+ assert snapshot_map() == {int(k): (v[0], v[1]) for k, v in BioLogic.VMPdata_colID_dtype_map.items()}
79
+ ref = galvani_ref.installed()
80
+ assert ref.label == "galvani 0.5.0" and ref.low_byte is True
81
+
82
+
83
+ def test_full_id_galvani_gets_no_low_byte_note(monkeypatch):
84
+ """A galvani that reads column IDs in full (datalab-org-galvani 0.6.0) misreads nothing."""
85
+ ref = galvani_ref.Reference(snapshot_map(), "datalab-org-galvani 0.6.0", low_byte=False, installed=True)
86
+ monkeypatch.setattr(galvani_ref, "reference", lambda: ref)
87
+ m = mprfile.read(EXT / "peis.issue_225.with_ece.mpr", reference="galvani")
88
+ assert m.unknown == [] and not any("low byte" in n for n in m.notes), m.notes
89
+
90
+
91
+ def test_without_galvani_the_frozen_map_stands_in(monkeypatch):
92
+ monkeypatch.setattr(galvani_ref, "installed", lambda: None)
93
+ m = mprfile.read(EXT / "gcpl.issue_230.mpr", reference="galvani")
94
+ assert [c.raw_id for c in m.unknown] == [883, 884]
95
+ assert any("not installed" in n for n in m.notes), m.notes
96
+ assert any("galvani 0.5.0 reads only the low byte" in n and "438 as 182" in n for n in m.notes), m.notes
97
+
98
+
63
99
  @pytest.mark.parametrize("old_id,expected", [(467, "Q charge or discharge"), (7, "dq"), (4, "time")])
64
100
  def test_simulated_drift_is_named(tmp_path, old_id, expected):
65
101
  f = tmp_path / "drift.mpr"
File without changes
@@ -1,6 +1,6 @@
1
1
  numpy>=2.0
2
2
  yadg==7.0.1
3
- galvani==0.5.0
4
3
 
5
4
  [test]
6
5
  pytest
6
+ galvani==0.5.0
File without changes