cellpycore 0.1.1__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.
- cellpycore/__init__.py +0 -0
- cellpycore/_helpers.py +199 -0
- cellpycore/cell_core.py +696 -0
- cellpycore/config.py +674 -0
- cellpycore/extractors.py +156 -0
- cellpycore/header_mapping.py +267 -0
- cellpycore/legacy.py +336 -0
- cellpycore/metadata/__init__.py +59 -0
- cellpycore/metadata/io.py +192 -0
- cellpycore/metadata/models.py +238 -0
- cellpycore/selectors.py +470 -0
- cellpycore/settings_base.py +98 -0
- cellpycore/summarizers.py +903 -0
- cellpycore/timestamps.py +128 -0
- cellpycore/units.py +257 -0
- cellpycore-0.1.1.dist-info/METADATA +90 -0
- cellpycore-0.1.1.dist-info/RECORD +19 -0
- cellpycore-0.1.1.dist-info/WHEEL +4 -0
- cellpycore-0.1.1.dist-info/licenses/LICENSE +21 -0
cellpycore/config.py
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
# Definitions of headers and other constants
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
This module contains definitions of headers and other constants.
|
|
5
|
+
|
|
6
|
+
Planned features:
|
|
7
|
+
- It should also contain a custom dict-like object that can be used to store
|
|
8
|
+
the settings. It should allow for both "dot notation" and "bracket notation" to access the keys.
|
|
9
|
+
- It should work well with both Polars and Pandas.
|
|
10
|
+
- It should be extendable in case we want to add new features in the future.
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from enum import StrEnum
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestMode(StrEnum):
|
|
19
|
+
"""Test mode of an electrochemical experiment (cycling configuration).
|
|
20
|
+
|
|
21
|
+
Describes whether a cell test is run in the ordinary configuration
|
|
22
|
+
(``NORMAL``) or in the inverted "anode mode" configuration (``INVERTED``).
|
|
23
|
+
This determines the charge/discharge sign conventions the summary / step
|
|
24
|
+
engine must apply when it processes the data.
|
|
25
|
+
|
|
26
|
+
The enum is deliberately binary: for sign-convention purposes only "anode
|
|
27
|
+
half-cell vs everything else" matters. cellpy's legacy ``cycle_mode`` string
|
|
28
|
+
carries a wider vocabulary (``"anode"``, ``"cathode"``,
|
|
29
|
+
``"full_cell"``/``"fullcell"``, ``"standard"``); all non-anode values
|
|
30
|
+
collapse to ``NORMAL`` here. If a finer distinction is ever needed, add new
|
|
31
|
+
members rather than reintroducing free-form strings.
|
|
32
|
+
|
|
33
|
+
This is the typed replacement for cellpy's loose ``cycle_mode`` string
|
|
34
|
+
parameter (``cellpy.parameters.prms.Reader.cycle_mode``); it is defined for
|
|
35
|
+
that future use and not yet wired up in this package. When cellpy is
|
|
36
|
+
migrated onto cellpy-core it should pass a ``TestMode`` here instead of a
|
|
37
|
+
bare string, so the mode is validated and the sign conventions are derived
|
|
38
|
+
from it.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
NORMAL (``"normal"``): The ordinary case (full cells, cathode
|
|
42
|
+
half-cells). The first step uses the standard (non-inverted)
|
|
43
|
+
charge/discharge convention. This is the baseline / default mode.
|
|
44
|
+
INVERTED (``"inverted"``): Anode half-cell ("anode mode"). The electrode
|
|
45
|
+
under test is the anode, cycled against lithium, so the convention
|
|
46
|
+
is inverted relative to ``NORMAL`` (the first step is typically a
|
|
47
|
+
lithiation). This is the case cellpy historically selected with
|
|
48
|
+
``cycle_mode="anode"``.
|
|
49
|
+
|
|
50
|
+
Note:
|
|
51
|
+
Name and members intentionally mirror batbase's metadata enum
|
|
52
|
+
``ElectroChemicalExperiment.TestMode`` (repo ``ife-bat/batbase``,
|
|
53
|
+
``src/runs/models.py``). batbase is the metadata/persistence layer; this
|
|
54
|
+
enum is the processing-layer counterpart. They describe the same
|
|
55
|
+
physical reality and must be kept in sync via this mapping::
|
|
56
|
+
|
|
57
|
+
TestMode (here) batbase (stored code) cellpy cycle_mode
|
|
58
|
+
INVERTED INVERTED ("i") "anode"
|
|
59
|
+
NORMAL NORMAL ("n") "full_cell" / "standard" / "cathode"
|
|
60
|
+
|
|
61
|
+
Two traps to remember:
|
|
62
|
+
|
|
63
|
+
- Storage values differ by layer on purpose: this StrEnum uses the
|
|
64
|
+
self-describing values ``"normal"`` / ``"inverted"``, while batbase
|
|
65
|
+
persists the short codes ``"n"`` / ``"i"`` (its ``TextChoices`` member
|
|
66
|
+
names still read ``NORMAL`` / ``INVERTED``). Translate explicitly at
|
|
67
|
+
the boundary; never compare raw stored values across the two layers.
|
|
68
|
+
- Default-polarity trap: batbase defaults to ``NORMAL``, but cellpy
|
|
69
|
+
historically defaulted ``cycle_mode="anode"`` (i.e. ``INVERTED``).
|
|
70
|
+
When bridging metadata into the engine, map the mode explicitly and
|
|
71
|
+
do not rely on either side's implicit default.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
NORMAL = "normal"
|
|
75
|
+
INVERTED = "inverted"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ResetGranularity(StrEnum):
|
|
79
|
+
"""Reset granularity of a cumulative raw capacity / energy column.
|
|
80
|
+
|
|
81
|
+
Describes where a cycler's cumulative capacity / energy counter resets to
|
|
82
|
+
zero. The harmonized raw format mandates ``CYCLE`` (see
|
|
83
|
+
``docs/data_format_specifications/harmonized_raw.md`` §Capacity convention);
|
|
84
|
+
:func:`cellpycore.summarizers.normalize_capacity_granularity` uses this enum
|
|
85
|
+
to normalize ``STEP`` / ``TEST`` cumulative raw from other cyclers to the
|
|
86
|
+
mandated ``CYCLE`` convention before aggregation.
|
|
87
|
+
|
|
88
|
+
Attributes:
|
|
89
|
+
CYCLE (``"cycle"``): Counter resets at each cycle boundary (accumulates
|
|
90
|
+
across a cycle's steps for its direction). The mandated harmonized
|
|
91
|
+
convention and the default; normalizing a ``CYCLE`` input is a no-op.
|
|
92
|
+
STEP (``"step"``): Counter resets at each step boundary (accumulates only
|
|
93
|
+
within a step).
|
|
94
|
+
TEST (``"test"``): Counter never resets (accumulates across the whole
|
|
95
|
+
test for its direction).
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
CYCLE = "cycle"
|
|
99
|
+
STEP = "step"
|
|
100
|
+
TEST = "test"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class StepType(StrEnum):
|
|
104
|
+
"""Canonical step-type labels for the ``step_type`` column of the step table.
|
|
105
|
+
|
|
106
|
+
This is the single source of truth for the step-type vocabulary (the
|
|
107
|
+
``STEP_TYPES`` list below is derived from it). It mirrors old cellpy's
|
|
108
|
+
``CellpyCell.list_of_step_types`` so cross-repo step-type parity is
|
|
109
|
+
preserved.
|
|
110
|
+
|
|
111
|
+
Like the other enums in this module it is a *reference* vocabulary: the step
|
|
112
|
+
table stores plain strings and the engine does not validate against this
|
|
113
|
+
enum, so unknown values are still allowed. Extend by adding members rather
|
|
114
|
+
than introducing free-form strings elsewhere.
|
|
115
|
+
|
|
116
|
+
Note:
|
|
117
|
+
Not every member is emitted by the built-in classifier. ``_classify_steps``
|
|
118
|
+
in ``summarizers.py`` currently emits only ``charge``, ``discharge``,
|
|
119
|
+
``cv_charge``, ``cv_discharge``, ``ocvrlx_up``, ``ocvrlx_down``, ``ir``
|
|
120
|
+
and ``rest``. The remaining members (``taper_charge``,
|
|
121
|
+
``taper_discharge``, ``charge_cv``, ``discharge_cv``, ``not_known``)
|
|
122
|
+
come from explicit step specifications, overrides, or legacy data.
|
|
123
|
+
|
|
124
|
+
Known discrepancy (not reconciled here to preserve golden-test parity):
|
|
125
|
+
the classifier labels uncategorized steps with the empty string ``""``,
|
|
126
|
+
not ``not_known``. Unifying ``""`` and ``NOT_KNOWN`` is a follow-up.
|
|
127
|
+
|
|
128
|
+
Attributes:
|
|
129
|
+
CHARGE (``"charge"``): Constant-current (or general) charge step.
|
|
130
|
+
DISCHARGE (``"discharge"``): Constant-current (or general) discharge step.
|
|
131
|
+
CV_CHARGE (``"cv_charge"``): Constant-voltage portion of a charge step.
|
|
132
|
+
CV_DISCHARGE (``"cv_discharge"``): Constant-voltage portion of a discharge step.
|
|
133
|
+
TAPER_CHARGE (``"taper_charge"``): Tapering (CV tail) charge step.
|
|
134
|
+
TAPER_DISCHARGE (``"taper_discharge"``): Tapering (CV tail) discharge step.
|
|
135
|
+
CHARGE_CV (``"charge_cv"``): Charge step that includes a CV phase.
|
|
136
|
+
DISCHARGE_CV (``"discharge_cv"``): Discharge step that includes a CV phase.
|
|
137
|
+
OCVRLX_UP (``"ocvrlx_up"``): Open-circuit voltage relaxation, rising potential.
|
|
138
|
+
OCVRLX_DOWN (``"ocvrlx_down"``): Open-circuit voltage relaxation, falling potential.
|
|
139
|
+
IR (``"ir"``): Internal-resistance (instantaneous) step.
|
|
140
|
+
REST (``"rest"``): Rest / pause step (no current, stable potential).
|
|
141
|
+
NOT_KNOWN (``"not_known"``): Uncategorized step (see Note about ``""``).
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
CHARGE = "charge"
|
|
145
|
+
DISCHARGE = "discharge"
|
|
146
|
+
CV_CHARGE = "cv_charge"
|
|
147
|
+
CV_DISCHARGE = "cv_discharge"
|
|
148
|
+
TAPER_CHARGE = "taper_charge"
|
|
149
|
+
TAPER_DISCHARGE = "taper_discharge"
|
|
150
|
+
CHARGE_CV = "charge_cv"
|
|
151
|
+
DISCHARGE_CV = "discharge_cv"
|
|
152
|
+
OCVRLX_UP = "ocvrlx_up"
|
|
153
|
+
OCVRLX_DOWN = "ocvrlx_down"
|
|
154
|
+
IR = "ir"
|
|
155
|
+
REST = "rest"
|
|
156
|
+
NOT_KNOWN = "not_known"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Canonical list of step-type labels, derived from ``StepType`` so there is a
|
|
160
|
+
# single source of truth. Kept as a plain list (and named ``STEP_TYPES``) for
|
|
161
|
+
# backwards compatibility with existing importers.
|
|
162
|
+
STEP_TYPES = [member.value for member in StepType]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class StepMode(StrEnum):
|
|
166
|
+
"""Control mode of a step for the ``step_mode`` column of the raw table.
|
|
167
|
+
|
|
168
|
+
Describes how the cycler regulated the step (constant current, constant
|
|
169
|
+
voltage, constant power). Like the other enums here it is a *reference*
|
|
170
|
+
vocabulary: the raw table stores plain strings and unknown values are
|
|
171
|
+
allowed; extend by adding members.
|
|
172
|
+
|
|
173
|
+
Not produced by the engine yet (only the mock-data helper sets a value).
|
|
174
|
+
|
|
175
|
+
Note:
|
|
176
|
+
Absence / "no specific mode" is represented by a null value in the
|
|
177
|
+
table, not by the literal string ``"None"``. The spec table in
|
|
178
|
+
``docs/data_format_specifications/harmonized_raw.md`` lists ``"None"``
|
|
179
|
+
as a sample value; that is documentation shorthand for "missing" and is
|
|
180
|
+
intentionally not a member here.
|
|
181
|
+
|
|
182
|
+
Attributes:
|
|
183
|
+
CC (``"CC"``): Constant current.
|
|
184
|
+
CV (``"CV"``): Constant voltage.
|
|
185
|
+
CP (``"CP"``): Constant power.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
CC = "CC"
|
|
189
|
+
CV = "CV"
|
|
190
|
+
CP = "CP"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class CycleType(StrEnum):
|
|
194
|
+
"""Cycle classification for the ``cycle_type`` column of the raw table.
|
|
195
|
+
|
|
196
|
+
A *reference* vocabulary (plain strings stored in the table, unknown values
|
|
197
|
+
allowed, extend by adding members). Values keep the capitalization used in
|
|
198
|
+
the spec table in ``docs/data_format_specifications/harmonized_raw.md``.
|
|
199
|
+
|
|
200
|
+
Not used by the engine yet.
|
|
201
|
+
|
|
202
|
+
Note:
|
|
203
|
+
This may migrate into per-test metadata as ``test_type`` rather than
|
|
204
|
+
staying a per-row raw column (see
|
|
205
|
+
``.issueflows/04-designs-and-guides/test-metadata-and-merging.md``).
|
|
206
|
+
``GITT`` also appears as a ``test_type`` example, so ``cycle_type`` and
|
|
207
|
+
``test_type`` may later be unified into one vocabulary.
|
|
208
|
+
|
|
209
|
+
Attributes:
|
|
210
|
+
STANDARD (``"Standard"``): Ordinary cycling.
|
|
211
|
+
GITT (``"GITT"``): Galvanostatic Intermittent Titration Technique.
|
|
212
|
+
ICI (``"ICI"``): Intermittent Current Interruption.
|
|
213
|
+
CHARACTERIZATION (``"Characterization"``): Characterization cycle.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
STANDARD = "Standard"
|
|
217
|
+
GITT = "GITT"
|
|
218
|
+
ICI = "ICI"
|
|
219
|
+
CHARACTERIZATION = "Characterization"
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass
|
|
223
|
+
class BaseCols:
|
|
224
|
+
"""Shared base for all column-header objects.
|
|
225
|
+
|
|
226
|
+
Provides the ``__version__`` field and bracket-notation access
|
|
227
|
+
(``cols["name"]``) on top of attribute access (``cols.name``), so concrete
|
|
228
|
+
header classes can be used interchangeably with both styles.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
__version__: str = "0.1.0"
|
|
232
|
+
|
|
233
|
+
def __getitem__(self, key: str) -> str:
|
|
234
|
+
"""Allow for bracket notation"""
|
|
235
|
+
return getattr(self, key)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class FlexibleCols(BaseCols):
|
|
239
|
+
"""Opt-in base that allows per-attribute name modification.
|
|
240
|
+
|
|
241
|
+
Behaves like ``BaseCols`` but routes every attribute access through
|
|
242
|
+
``__getattribute__``, so subclasses can transform header names on the fly
|
|
243
|
+
(e.g. add a prefix/suffix). This flexibility costs some performance, so use
|
|
244
|
+
it only when dynamic header names are actually needed.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
def __getattribute__(self, key: str) -> str:
|
|
248
|
+
"""Modification of the attribute can be done here.
|
|
249
|
+
|
|
250
|
+
When implemented in the actual column classes (e.g. CycleCols), you have to add
|
|
251
|
+
a comment in this docstring to show the version of the implementation.
|
|
252
|
+
|
|
253
|
+
version 0.1.0:
|
|
254
|
+
- uses the default getattr method, implemented only for showing future us
|
|
255
|
+
where to put code.
|
|
256
|
+
|
|
257
|
+
Example: adding a suffix to the attribute name.
|
|
258
|
+
|
|
259
|
+
>> original_item = super().__getattribute__(key)
|
|
260
|
+
>> # Since __getattribute__ is used for all attributes (not only the ones we defined in the class)
|
|
261
|
+
>> # we need to check the type of the attribute before we can modify it.
|
|
262
|
+
>> if isinstance(original_item, str):
|
|
263
|
+
>> modified_item = original_item + "_modified"
|
|
264
|
+
>> else:
|
|
265
|
+
>> modified_item = original_item
|
|
266
|
+
>> return modified_item
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
return super().__getattribute__(key)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class Cols(BaseCols):
|
|
274
|
+
"""Standard base for the concrete column-header classes.
|
|
275
|
+
|
|
276
|
+
``CycleCols``, ``StepCols`` and ``RawCols`` inherit from this. Add common
|
|
277
|
+
functionality shared across all header classes here (e.g. a ``to_json``
|
|
278
|
+
method). To enable dynamic header names, swap the inheritance to
|
|
279
|
+
``FlexibleCols`` (which incurs some performance loss).
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@dataclass(frozen=True)
|
|
286
|
+
class Schema:
|
|
287
|
+
"""Bundle of the column-header objects for one cell.
|
|
288
|
+
|
|
289
|
+
Holds the raw, cycle (summary) and step header definitions so the summary /
|
|
290
|
+
step engine can read its column names from an injected object instead of
|
|
291
|
+
module-level globals. This is what makes the engine schema-agnostic and
|
|
292
|
+
thread-safe: each cell carries its own schema.
|
|
293
|
+
|
|
294
|
+
Units are handled by value (the engine multiplies by precomputed conversion
|
|
295
|
+
factors supplied by the caller), so units are deliberately not part of the
|
|
296
|
+
schema.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
raw: BaseCols
|
|
300
|
+
cycle: BaseCols
|
|
301
|
+
step: BaseCols
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class CycleCols(Cols):
|
|
305
|
+
"""Column-header definitions for the per-cycle summary table.
|
|
306
|
+
|
|
307
|
+
Each attribute maps a logical quantity to the column name used in the
|
|
308
|
+
per-cycle summary produced by the summary engine (capacities, efficiencies,
|
|
309
|
+
durations, per-direction current/potential/power statistics, etc.).
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
# Compact per-test key (mirrors ``RawCols.test_id``); the leading component of
|
|
313
|
+
# the composite ``(test_id, cycle_num, …)`` group key so merged objects
|
|
314
|
+
# holding many tests never mix cycles across tests. Defaults to ``0`` for a
|
|
315
|
+
# single unmerged test.
|
|
316
|
+
test_id: str = "test_id"
|
|
317
|
+
cycle_num: str = "cycle_num"
|
|
318
|
+
mask: str = "mask"
|
|
319
|
+
datapoint_num_first: str = "datapoint_num_first"
|
|
320
|
+
datapoint_num_last: str = "datapoint_num_last"
|
|
321
|
+
# Absolute timestamps: int64 nanoseconds since the Unix epoch, UTC (see
|
|
322
|
+
# cellpycore.timestamps for ns <-> seconds / datetime conversion helpers).
|
|
323
|
+
first_epoch_time_utc: str = "first_epoch_time_utc"
|
|
324
|
+
last_epoch_time_utc: str = "last_epoch_time_utc"
|
|
325
|
+
first_test_time: str = "first_test_time"
|
|
326
|
+
last_test_time: str = "last_test_time"
|
|
327
|
+
cycle_duration: str = "cycle_duration"
|
|
328
|
+
charge_duration: str = "charge_duration"
|
|
329
|
+
discharge_duration: str = "discharge_duration"
|
|
330
|
+
rest_duration: str = "rest_duration"
|
|
331
|
+
charge_capacity: str = "charge_capacity"
|
|
332
|
+
discharge_capacity: str = "discharge_capacity"
|
|
333
|
+
charge_capacity_loss: str = "charge_capacity_loss"
|
|
334
|
+
discharge_capacity_loss: str = "discharge_capacity_loss"
|
|
335
|
+
coulombic_difference: str = "coulombic_difference"
|
|
336
|
+
coulombic_efficiency: str = "coulombic_efficiency"
|
|
337
|
+
test_cumulated_charge_capacity: str = "test_cumulated_charge_capacity"
|
|
338
|
+
test_cumulated_discharge_capacity: str = "test_cumulated_discharge_capacity"
|
|
339
|
+
test_cumulated_coulombic_difference: str = "test_cumulated_coulombic_difference"
|
|
340
|
+
test_cumulated_charge_capacity_loss: str = "test_cumulated_charge_capacity_loss"
|
|
341
|
+
test_cumulated_discharge_capacity_loss: str = "test_cumulated_discharge_capacity_loss"
|
|
342
|
+
test_net_capacity: str = "test_net_capacity"
|
|
343
|
+
charge_energy: str = "charge_energy"
|
|
344
|
+
discharge_energy: str = "discharge_energy"
|
|
345
|
+
cycle_net_energy: str = "cycle_net_energy"
|
|
346
|
+
energy_efficiency: str = "energy_efficiency"
|
|
347
|
+
test_cumulated_charge_energy: str = "test_cumulated_charge_energy"
|
|
348
|
+
test_cumulated_discharge_energy: str = "test_cumulated_discharge_energy"
|
|
349
|
+
test_net_energy: str = "test_net_energy"
|
|
350
|
+
current_charge_mean: str = "current_charge_mean"
|
|
351
|
+
current_charge_mean_tw: str = "current_charge_mean_tw"
|
|
352
|
+
current_charge_mean_cw: str = "current_charge_mean_cw"
|
|
353
|
+
current_charge_max: str = "current_charge_max"
|
|
354
|
+
current_charge_min: str = "current_charge_min"
|
|
355
|
+
current_discharge_mean: str = "current_discharge_mean"
|
|
356
|
+
current_discharge_mean_tw: str = "current_discharge_mean_tw"
|
|
357
|
+
current_discharge_mean_cw: str = "current_discharge_mean_cw"
|
|
358
|
+
current_discharge_max: str = "current_discharge_max"
|
|
359
|
+
current_discharge_min: str = "current_discharge_min"
|
|
360
|
+
potential_charge_mean: str = "potential_charge_mean"
|
|
361
|
+
potential_charge_mean_tw: str = "potential_charge_mean_tw"
|
|
362
|
+
potential_charge_mean_cw: str = "potential_charge_mean_cw"
|
|
363
|
+
potential_charge_max: str = "potential_charge_max"
|
|
364
|
+
potential_charge_min: str = "potential_charge_min"
|
|
365
|
+
potential_discharge_mean: str = "potential_discharge_mean"
|
|
366
|
+
potential_discharge_mean_tw: str = "potential_discharge_mean_tw"
|
|
367
|
+
potential_discharge_mean_cw: str = "potential_discharge_mean_cw"
|
|
368
|
+
potential_discharge_max: str = "potential_discharge_max"
|
|
369
|
+
potential_discharge_min: str = "potential_discharge_min"
|
|
370
|
+
potential_start_charge: str = "potential_start_charge"
|
|
371
|
+
potential_end_charge: str = "potential_end_charge"
|
|
372
|
+
potential_start_discharge: str = "potential_start_discharge"
|
|
373
|
+
potential_end_discharge: str = "potential_end_discharge"
|
|
374
|
+
voltage_efficiency: str = "voltage_efficiency"
|
|
375
|
+
power_charge_mean: str = "power_charge_mean"
|
|
376
|
+
power_charge_mean_tw: str = "power_charge_mean_tw"
|
|
377
|
+
power_charge_mean_cw: str = "power_charge_mean_cw"
|
|
378
|
+
power_charge_max: str = "power_charge_max"
|
|
379
|
+
power_charge_min: str = "power_charge_min"
|
|
380
|
+
power_discharge_mean: str = "power_discharge_mean"
|
|
381
|
+
power_discharge_mean_tw: str = "power_discharge_mean_tw"
|
|
382
|
+
power_discharge_mean_cw: str = "power_discharge_mean_cw"
|
|
383
|
+
power_discharge_max: str = "power_discharge_max"
|
|
384
|
+
power_discharge_min: str = "power_discharge_min"
|
|
385
|
+
ir_start_charge: str = "ir_start_charge"
|
|
386
|
+
ir_end_charge: str = "ir_end_charge"
|
|
387
|
+
ir_start_discharge: str = "ir_start_discharge"
|
|
388
|
+
ir_end_discharge: str = "ir_end_discharge"
|
|
389
|
+
relaxation_potential_charge: str = "relaxation_potential_charge"
|
|
390
|
+
relaxation_potential_discharge: str = "relaxation_potential_discharge"
|
|
391
|
+
open_circuit_potential_charge: str = "open_circuit_potential_charge"
|
|
392
|
+
open_circuit_potential_discharge: str = "open_circuit_potential_discharge"
|
|
393
|
+
cv_share: str = "cv_share"
|
|
394
|
+
cv_charge_capacity: str = "cv_charge_capacity"
|
|
395
|
+
cv_charge_energy: str = "cv_charge_energy"
|
|
396
|
+
cv_charge_time: str = "cv_charge_time"
|
|
397
|
+
cc_charge_capacity: str = "cc_charge_capacity"
|
|
398
|
+
cc_charge_energy: str = "cc_charge_energy"
|
|
399
|
+
cc_charge_time: str = "cc_charge_time"
|
|
400
|
+
# Per-cycle cell-temperature statistics, aggregated from the raw
|
|
401
|
+
# ``aux_temperature_cell`` signal. Declared here (like the per-direction
|
|
402
|
+
# current/potential/power stats above) ahead of engine support; the summary
|
|
403
|
+
# engine does not populate them yet. ``_mean`` / ``_last`` map to the legacy
|
|
404
|
+
# ``temperature_mean`` / ``temperature_last`` summary columns.
|
|
405
|
+
temperature_cell_mean: str = "temperature_cell_mean"
|
|
406
|
+
temperature_cell_max: str = "temperature_cell_max"
|
|
407
|
+
temperature_cell_min: str = "temperature_cell_min"
|
|
408
|
+
temperature_cell_last: str = "temperature_cell_last"
|
|
409
|
+
# Derived/scaled columns produced by the standalone native summary path
|
|
410
|
+
# (``add_scaled_summary_columns`` + the C-rate / IR helpers). These were
|
|
411
|
+
# previously legacy-only "bridge extras" (see step-table-polars-migration.md,
|
|
412
|
+
# Phase 3b); issue #21 brings them onto the native schema so the native path
|
|
413
|
+
# is self-sufficient. ``ir_charge`` / ``ir_discharge`` are the behaviour-
|
|
414
|
+
# preserving single-value IR targets (the four ``ir_start/end_*`` columns
|
|
415
|
+
# above remain reserved for the richer future IR model).
|
|
416
|
+
normalized_cycle_index: str = "normalized_cycle_index"
|
|
417
|
+
charge_c_rate: str = "charge_c_rate"
|
|
418
|
+
discharge_c_rate: str = "discharge_c_rate"
|
|
419
|
+
ir_charge: str = "ir_charge"
|
|
420
|
+
ir_discharge: str = "ir_discharge"
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
def specific_columns(self) -> list:
|
|
424
|
+
"""Summary columns that get specific (per mass / area / volume) variants.
|
|
425
|
+
|
|
426
|
+
Returns the capacity-like columns that ``generate_specific_summary_columns``
|
|
427
|
+
scales into ``{col}_gravimetric`` / ``{col}_areal`` / ``{col}_absolute``
|
|
428
|
+
variants. Mirrors the legacy ``HeadersSummary.specific_columns`` list using
|
|
429
|
+
the native column names (the native schema has no ``shifted_*`` columns, so
|
|
430
|
+
those legacy entries are dropped).
|
|
431
|
+
"""
|
|
432
|
+
return [
|
|
433
|
+
self.discharge_capacity,
|
|
434
|
+
self.charge_capacity,
|
|
435
|
+
self.test_cumulated_charge_capacity,
|
|
436
|
+
self.test_cumulated_discharge_capacity,
|
|
437
|
+
self.coulombic_difference,
|
|
438
|
+
self.test_cumulated_coulombic_difference,
|
|
439
|
+
self.discharge_capacity_loss,
|
|
440
|
+
self.charge_capacity_loss,
|
|
441
|
+
self.test_cumulated_discharge_capacity_loss,
|
|
442
|
+
self.test_cumulated_charge_capacity_loss,
|
|
443
|
+
]
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class StepCols(Cols):
|
|
447
|
+
"""Column-header definitions for the per-step summary table.
|
|
448
|
+
|
|
449
|
+
Each attribute maps a logical quantity to the column name used in the
|
|
450
|
+
per-step summary (per-step statistics such as mean/std/min/max/first/last/
|
|
451
|
+
delta for time, current, potential, capacity, energy, power and internal
|
|
452
|
+
resistance, plus the per-step C-rate estimate).
|
|
453
|
+
"""
|
|
454
|
+
|
|
455
|
+
# Compact per-test key (mirrors ``RawCols.test_id``); the leading component of
|
|
456
|
+
# the composite ``(test_id, cycle_num, step_num, …)`` group key so merged
|
|
457
|
+
# objects holding many tests never mix cycles/steps across tests. Defaults to
|
|
458
|
+
# ``0`` for a single unmerged test.
|
|
459
|
+
test_id: str = "test_id"
|
|
460
|
+
cycle_num: str = "cycle_num"
|
|
461
|
+
step_num: str = "step_num"
|
|
462
|
+
sub_step_num: str = "sub_step_num"
|
|
463
|
+
# ``step_type`` values draw from the ``StepType`` vocabulary.
|
|
464
|
+
step_type: str = "step_type"
|
|
465
|
+
# ``sub_step_type`` is currently reserved and left unpopulated (the step
|
|
466
|
+
# engine writes null). When used it is expected to draw from the same
|
|
467
|
+
# ``StepType`` vocabulary; its exact semantics are still TBD.
|
|
468
|
+
sub_step_type: str = "sub_step_type"
|
|
469
|
+
mask: str = "mask"
|
|
470
|
+
datapoint_num_first: str = "datapoint_num_first"
|
|
471
|
+
datapoint_num_last: str = "datapoint_num_last"
|
|
472
|
+
test_time_first: str = "test_time_first"
|
|
473
|
+
test_time_last: str = "test_time_last"
|
|
474
|
+
step_time_mean: str = "step_time_mean"
|
|
475
|
+
step_time_std: str = "step_time_std"
|
|
476
|
+
step_time_min: str = "step_time_min"
|
|
477
|
+
step_time_max: str = "step_time_max"
|
|
478
|
+
step_time_first: str = "step_time_first"
|
|
479
|
+
step_time_last: str = "step_time_last"
|
|
480
|
+
step_time_delta: str = "step_time_delta"
|
|
481
|
+
current_mean: str = "current_mean"
|
|
482
|
+
current_std: str = "current_std"
|
|
483
|
+
current_min: str = "current_min"
|
|
484
|
+
current_max: str = "current_max"
|
|
485
|
+
current_first: str = "current_first"
|
|
486
|
+
current_last: str = "current_last"
|
|
487
|
+
current_delta: str = "current_delta"
|
|
488
|
+
potential_mean: str = "potential_mean"
|
|
489
|
+
potential_std: str = "potential_std"
|
|
490
|
+
potential_min: str = "potential_min"
|
|
491
|
+
potential_max: str = "potential_max"
|
|
492
|
+
potential_first: str = "potential_first"
|
|
493
|
+
potential_last: str = "potential_last"
|
|
494
|
+
potential_delta: str = "potential_delta"
|
|
495
|
+
charge_capacity_mean: str = "charge_capacity_mean"
|
|
496
|
+
charge_capacity_std: str = "charge_capacity_std"
|
|
497
|
+
charge_capacity_min: str = "charge_capacity_min"
|
|
498
|
+
charge_capacity_max: str = "charge_capacity_max"
|
|
499
|
+
charge_capacity_first: str = "charge_capacity_first"
|
|
500
|
+
charge_capacity_last: str = "charge_capacity_last"
|
|
501
|
+
charge_capacity_delta: str = "charge_capacity_delta"
|
|
502
|
+
discharge_capacity_mean: str = "discharge_capacity_mean"
|
|
503
|
+
discharge_capacity_std: str = "discharge_capacity_std"
|
|
504
|
+
discharge_capacity_min: str = "discharge_capacity_min"
|
|
505
|
+
discharge_capacity_max: str = "discharge_capacity_max"
|
|
506
|
+
discharge_capacity_first: str = "discharge_capacity_first"
|
|
507
|
+
discharge_capacity_last: str = "discharge_capacity_last"
|
|
508
|
+
discharge_capacity_delta: str = "discharge_capacity_delta"
|
|
509
|
+
power_mean: str = "power_mean"
|
|
510
|
+
power_std: str = "power_std"
|
|
511
|
+
power_min: str = "power_min"
|
|
512
|
+
power_max: str = "power_max"
|
|
513
|
+
power_first: str = "power_first"
|
|
514
|
+
power_last: str = "power_last"
|
|
515
|
+
power_delta: str = "power_delta"
|
|
516
|
+
charge_energy_mean: str = "charge_energy_mean"
|
|
517
|
+
charge_energy_std: str = "charge_energy_std"
|
|
518
|
+
charge_energy_min: str = "charge_energy_min"
|
|
519
|
+
charge_energy_max: str = "charge_energy_max"
|
|
520
|
+
charge_energy_first: str = "charge_energy_first"
|
|
521
|
+
charge_energy_last: str = "charge_energy_last"
|
|
522
|
+
charge_energy_delta: str = "charge_energy_delta"
|
|
523
|
+
discharge_energy_mean: str = "discharge_energy_mean"
|
|
524
|
+
discharge_energy_std: str = "discharge_energy_std"
|
|
525
|
+
discharge_energy_min: str = "discharge_energy_min"
|
|
526
|
+
discharge_energy_max: str = "discharge_energy_max"
|
|
527
|
+
discharge_energy_first: str = "discharge_energy_first"
|
|
528
|
+
discharge_energy_last: str = "discharge_energy_last"
|
|
529
|
+
discharge_energy_delta: str = "discharge_energy_delta"
|
|
530
|
+
internal_resistance_mean: str = "internal_resistance_mean"
|
|
531
|
+
internal_resistance_std: str = "internal_resistance_std"
|
|
532
|
+
internal_resistance_min: str = "internal_resistance_min"
|
|
533
|
+
internal_resistance_max: str = "internal_resistance_max"
|
|
534
|
+
internal_resistance_first: str = "internal_resistance_first"
|
|
535
|
+
internal_resistance_last: str = "internal_resistance_last"
|
|
536
|
+
internal_resistance_delta: str = "internal_resistance_delta"
|
|
537
|
+
ref_potential_mean: str = "ref_potential_mean"
|
|
538
|
+
ref_potential_std: str = "ref_potential_std"
|
|
539
|
+
ref_potential_min: str = "ref_potential_min"
|
|
540
|
+
ref_potential_max: str = "ref_potential_max"
|
|
541
|
+
ref_potential_first: str = "ref_potential_first"
|
|
542
|
+
ref_potential_last: str = "ref_potential_last"
|
|
543
|
+
ref_potential_delta: str = "ref_potential_delta"
|
|
544
|
+
# Per-step C-rate estimate (legacy ``rate_avr``).
|
|
545
|
+
c_rate: str = "c_rate"
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class RawCols(Cols):
|
|
549
|
+
"""Column-header definitions for the harmonized raw data table.
|
|
550
|
+
|
|
551
|
+
Each attribute maps a logical quantity to the column name used in the
|
|
552
|
+
harmonized raw format that cellpy-core consumes. The authoritative spec is
|
|
553
|
+
``docs/data_format_specifications/harmonized_raw.md``; the column order here
|
|
554
|
+
mirrors that spec table.
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
# Follows docs/data_format_specifications/harmonized_raw.md (authoritative,
|
|
558
|
+
# 2025-09-17). Column order mirrors the spec table.
|
|
559
|
+
datapoint_num: str = "datapoint_num"
|
|
560
|
+
source_datapoint_num: str = "source_datapoint_num"
|
|
561
|
+
mask: str = "mask"
|
|
562
|
+
# int64 nanoseconds since the Unix epoch, UTC (canonical absolute-timestamp
|
|
563
|
+
# dtype; see cellpycore.timestamps). ``test_time`` / ``step_time`` below are
|
|
564
|
+
# relative elapsed *seconds* (float), not absolute timestamps.
|
|
565
|
+
epoch_time_utc: str = "epoch_time_utc"
|
|
566
|
+
test_time: str = "test_time"
|
|
567
|
+
step_time: str = "step_time"
|
|
568
|
+
source_type: str = "source_type"
|
|
569
|
+
source_uuid: str = "source_uuid"
|
|
570
|
+
test_id: str = "test_id"
|
|
571
|
+
step_num: str = "step_num"
|
|
572
|
+
source_step_num: str = "source_step_num"
|
|
573
|
+
step_type: str = "step_type"
|
|
574
|
+
step_type_detail: str = "step_type_detail"
|
|
575
|
+
step_mode: str = "step_mode"
|
|
576
|
+
cycle_num: str = "cycle_num"
|
|
577
|
+
cycle_type: str = "cycle_type"
|
|
578
|
+
potential: str = "potential"
|
|
579
|
+
current: str = "current"
|
|
580
|
+
# Capacity / energy are cumulative per cycle, per direction (reset each cycle).
|
|
581
|
+
# See docs/data_format_specifications/harmonized_raw.md ("Capacity convention").
|
|
582
|
+
cumulative_charge_capacity: str = "cumulative_charge_capacity"
|
|
583
|
+
cumulative_discharge_capacity: str = "cumulative_discharge_capacity"
|
|
584
|
+
cumulative_charge_energy: str = "cumulative_charge_energy"
|
|
585
|
+
cumulative_discharge_energy: str = "cumulative_discharge_energy"
|
|
586
|
+
step_charge_power: str = "step_charge_power"
|
|
587
|
+
step_discharge_power: str = "step_discharge_power"
|
|
588
|
+
internal_resistance: str = "internal_resistance"
|
|
589
|
+
# Reference-electrode potential (3-electrode setups); optional value.
|
|
590
|
+
ref_potential: str = "ref_potential"
|
|
591
|
+
# Auxiliary columns (aux_<quantity>_<name> scheme). Defaults below cover the
|
|
592
|
+
# cell/chamber temperatures and cell pressure named in the spec.
|
|
593
|
+
aux_temperature_cell: str = "aux_temperature_cell"
|
|
594
|
+
aux_temperature_chamber: str = "aux_temperature_chamber"
|
|
595
|
+
aux_pressure_cell: str = "aux_pressure_cell"
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def default_schema() -> Schema:
|
|
599
|
+
"""Return a Schema using the native cellpy-core column definitions.
|
|
600
|
+
|
|
601
|
+
Used as a standalone fallback when no schema is injected; the legacy bridge
|
|
602
|
+
(OldCellpyCellCore) always injects its own legacy-named schema.
|
|
603
|
+
"""
|
|
604
|
+
return Schema(raw=RawCols(), cycle=CycleCols(), step=StepCols())
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def cols_check():
|
|
608
|
+
import pandas as pd
|
|
609
|
+
import polars as pl
|
|
610
|
+
|
|
611
|
+
print(80 * "-")
|
|
612
|
+
print("CHECKING CycleCols")
|
|
613
|
+
print(f"CycleCols.__version__: {CycleCols.__version__}")
|
|
614
|
+
print(80 * "-")
|
|
615
|
+
|
|
616
|
+
test_data = {
|
|
617
|
+
"cycle_num": [1, 2, 3],
|
|
618
|
+
"step_num": [4, 5, 6],
|
|
619
|
+
"charge_capacity": [7, 8, 9],
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
cycle_cols = CycleCols()
|
|
623
|
+
df = pl.DataFrame(test_data)
|
|
624
|
+
df_pandas = pd.DataFrame(test_data)
|
|
625
|
+
|
|
626
|
+
print("pandas:")
|
|
627
|
+
print(df_pandas)
|
|
628
|
+
print(df_pandas.columns)
|
|
629
|
+
print(df_pandas.dtypes)
|
|
630
|
+
|
|
631
|
+
print("polars:")
|
|
632
|
+
print(df)
|
|
633
|
+
print(df.schema)
|
|
634
|
+
|
|
635
|
+
print(80 * "-")
|
|
636
|
+
print(cycle_cols)
|
|
637
|
+
print(cycle_cols.cycle_num)
|
|
638
|
+
print(cycle_cols.step_num)
|
|
639
|
+
print(cycle_cols.charge_capacity)
|
|
640
|
+
print(80 * "-")
|
|
641
|
+
print(f"{cycle_cols.cycle_num=}")
|
|
642
|
+
print(f"{cycle_cols.step_num=}")
|
|
643
|
+
print(f"{cycle_cols.charge_capacity=}")
|
|
644
|
+
print(80 * "-")
|
|
645
|
+
print(f"{cycle_cols['cycle_num']=}")
|
|
646
|
+
print(f"{cycle_cols['step_num']=}")
|
|
647
|
+
print(f"{cycle_cols['charge_capacity']=}")
|
|
648
|
+
print(80 * "-")
|
|
649
|
+
|
|
650
|
+
print(80 * "=")
|
|
651
|
+
print("using Cols for polars")
|
|
652
|
+
print(80 * "=")
|
|
653
|
+
print(df.select(pl.col(cycle_cols.cycle_num)))
|
|
654
|
+
print(df.select(pl.col(cycle_cols.step_num)))
|
|
655
|
+
print(df.select(pl.col(cycle_cols.charge_capacity)))
|
|
656
|
+
print(80 * "-")
|
|
657
|
+
print(df.select(pl.col(cycle_cols["cycle_num"])))
|
|
658
|
+
print(df.select(pl.col(cycle_cols["step_num"])))
|
|
659
|
+
print(df.select(pl.col(cycle_cols["charge_capacity"])))
|
|
660
|
+
|
|
661
|
+
print(80 * "=")
|
|
662
|
+
print("using Cols for pandas")
|
|
663
|
+
print(80 * "=")
|
|
664
|
+
print(df_pandas.loc[:, cycle_cols.cycle_num])
|
|
665
|
+
print(df_pandas.loc[:, cycle_cols.step_num])
|
|
666
|
+
print(df_pandas.loc[:, cycle_cols.charge_capacity])
|
|
667
|
+
print(80 * "-")
|
|
668
|
+
print(df_pandas.loc[:, cycle_cols["cycle_num"]])
|
|
669
|
+
print(df_pandas.loc[:, cycle_cols["step_num"]])
|
|
670
|
+
print(df_pandas.loc[:, cycle_cols["charge_capacity"]])
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
if __name__ == "__main__":
|
|
674
|
+
cols_check()
|