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/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()