ICA and DVA¶
Incremental capacity analysis (dQ/dV) and differential voltage analysis (dV/dQ).
from cellpy import ica
frame = ica.dqdv(c) # cycle, direction, voltage, capacity, dqdv
frame = ica.dvdq(c, direction="charge") # cycle, direction, capacity, voltage, dvdq
Both verbs accept the same three kinds of source — a CellpyCell, a curve
frame from get_cap, or a bare (voltage, capacity) pair — and the same
IcaOptions recipe.
ica ¶
Incremental capacity analysis (dQ/dV) and differential voltage analysis (dV/dQ).
Two public verbs over one pure core:
from cellpy import ica
frame = ica.dqdv(c) # cycle, direction, voltage, capacity, dqdv
frame = ica.dvdq(c, direction="charge") # cycle, direction, capacity, voltage, dvdq
The recipe is unchanged from cellpy 1.x — interpolate V(q), optionally smooth,
invert to q(V), differentiate, optionally smooth again, optionally normalize —
but it now lives in stateless functions ([transform_half_cycle][]) configured
by a single frozen [IcaOptions][] object rather than ~20 keyword arguments
tunnelled through four entry points.
What changed in 2.0 (see DEPRECATIONS.md and the migration guide):
- The output frame is specced: always long format, always the same columns,
with
directionspelled"charge"/"discharge"instead of the old ±1 code whose meaning depended oncycle_mode. - The incremental-capacity column is named
dqdv. The old namedqis kept as a duplicate column for one release. dvdq()is new. cellpy could not compute differential voltage analysis at all before, even though the pipeline already built the smoothed V(q) representation it needs.- Half-cycles that fail are reported, not silently replaced by empty arrays.
Converter,dqdv_cycle,dqdv_cycles,dqdv_np,dqdv(split=True)anddqdv(tidy=False)are deprecated shims over the new core. They reproduce the 1.x numbers bit-for-bit (tests/data/goldens/ica_dqdv_*) and go away in 2.1.
scipy stays on this side of the cellpy/cellpycore boundary: cellpycore is scipy-free, and the ICA math is interpolation and filtering, not frame algebra.
Converter ¶
Converter(capacity=None, voltage=None, points_pr_split=10, max_points=None, voltage_resolution=None, capacity_resolution=None, minimum_splits=3, interpolation_method='linear', increment_method='diff', pre_smoothing=False, smoothing=False, post_smoothing=True, normalize=True, normalizing_factor=None, normalizing_roof=None, savgol_filter_window_divisor_default=50, savgol_filter_window_order=3, voltage_fwhm=0.01, gaussian_order=0, gaussian_mode='reflect', gaussian_cval=0.0, gaussian_truncate=4.0)
Deprecated staged dQ/dV converter.
Use transform_half_cycle instead: it
takes the same recipe as an IcaOptions and
returns its derived quantities rather than writing them back onto itself.
The five stages (set → inspect → pre-process → increment → post-process) are kept because they are how the 1.x characterization tests drive the pipeline, but each now delegates to the pure functions above.
Hidden state
inspect_data overwrites normalizing_factor from the data, so a
converter reused across half-cycles carries the previous one's
normalization into the next. That behaviour is preserved here for
compatibility; the new core returns the factor instead.
GaussianOptions
dataclass
¶
Parameters passed straight through to scipy.ndimage.gaussian_filter1d.
HalfCycleResult
dataclass
¶
HalfCycleResult(x: ndarray, y: ndarray, partner: ndarray, derivative: str, normalizing_factor: float, post_smoothing_applied: bool, notes: tuple[str, ...] = ())
Output of one half-cycle transform.
Attributes:
-
x(ndarray) –The abscissa — voltage for
dqdv, capacity fordvdq. -
y(ndarray) –The derivative itself.
-
partner(ndarray) –The other coordinate at the same points, so ICA and DVA curves can be cross-plotted. Capacity for
dqdv, voltage fordvdq. -
derivative(str) –"dqdv"or"dvdq". -
normalizing_factor(float) –The factor actually used. In 1.x this was written back onto the
Converter, so a reused converter silently carried one cycle's normalization into the next; it is returned here instead. -
post_smoothing_applied(bool) –Whether gaussian post-smoothing survived — it is dropped when it raises (see
notes). -
notes(tuple[str, ...]) –Human-readable record of anything the transform had to work around.
IcaCols
dataclass
¶
IcaCols(cycle: str = 'cycle', direction: str = 'direction', voltage: str = 'voltage', capacity: str = 'capacity', dqdv: str = 'dqdv', dvdq: str = 'dvdq', legacy_dqdv: str = 'dq')
Column names of the specced ICA/DVA output frames.
This is a data contract: users index the returned frame by these names, so it is versioned with the package rather than left to each entry point.
ordered_names ¶
Column order for the given derivative.
IcaOptions
dataclass
¶
IcaOptions(voltage_resolution: float | None = None, capacity_resolution: float | None = None, max_points: int | None = None, interpolation_method: str = 'linear', pre_smoothing: bool = False, diff_smoothing: bool = False, post_smoothing: bool = True, savgol_window_divisor: int = 50, savgol_order: int = 3, voltage_fwhm: float = 0.01, capacity_fwhm: float | None = None, gaussian: GaussianOptions = GaussianOptions(), normalize: Literal['area'] | bool = 'area', normalizing_factor: float | None = None, normalizing_roof: float | None = None, increment_method: Literal['diff'] = 'diff')
The complete recipe for one dQ/dV or dV/dQ transform.
One definition, one docstring. In 1.x these ~20 parameters could arrive as
Converter constructor arguments, as **kwargs tunnelled through any
of four entry points, or as dqdv_np's 17 explicit parameters — with the
same option spelled smoothing in one place and diff_smoothing in
another. Now there is one object.
Attributes:
-
voltage_resolution(float | None) –Voltage step for the q(V) interpolation, e.g.
0.005.Nonekeeps the point count of the input. -
capacity_resolution(float | None) –Capacity step for the V(q) interpolation. Ignored when
max_pointsis set. -
max_points(int | None) –Cap on the V(q) interpolation grid. Takes precedence over
capacity_resolution. -
interpolation_method(str) –Any
kindaccepted byscipy.interpolate.interp1d. -
pre_smoothing(bool) –Savitzky-Golay smoothing of V(q) before inversion.
-
diff_smoothing(bool) –Savitzky-Golay smoothing of q(V) before differentiating. (1.x called this
smoothingonConverteranddiff_smoothingondqdv_np.) -
post_smoothing(bool) –Gaussian smoothing of the finished derivative.
-
savgol_window_divisor(int) –Window length divisor for both Savitzky-Golay passes; the window is clamped to at least 3 points and forced odd.
-
savgol_order(int) –Polynomial order for both Savitzky-Golay passes.
-
voltage_fwhm(float) –Full width at half maximum, in volts, of the gaussian post-smoothing — used when differentiating along voltage (dQ/dV).
-
capacity_fwhm(float | None) –The dV/dQ analogue, in capacity units.
Nonederives it as 1% of the capacity span of the half-cycle, which is the same fraction the defaultvoltage_fwhmof 0.01 V represents on a typical ~1 V window. -
gaussian(GaussianOptions) –Remaining
gaussian_filter1dparameters. -
normalize(Literal['area'] | bool) –"area"scales the curve so its integral equals the normalizing factor;Falseleaves it in physical units. 1.x spelled"area"asTrue, which is still accepted. -
normalizing_factor(float | None) –Target for
normalize="area".Noneuses the half-cycle's own end capacity. -
normalizing_roof(float | None) –Rescales the normalizing factor by
end_capacity / normalizing_roof— the hook for normalizing a whole series to one nominal capacity. -
increment_method(Literal['diff']) –Only
"diff". The half-finished"hist"binning method from 1.x is reachable through the deprecatedConverteronly; see cellpy#566.
replace ¶
Return a copy with overrides applied (validated).
dqdv ¶
dqdv(source, cycles=None, direction: str = BOTH, options: IcaOptions | None = None, *, strict: bool = False, cycle_mode: str | None = None, number_of_points: int | None = None, **overrides) -> pd.DataFrame
Incremental capacity analysis: dQ/dV against voltage.
Parameters:
-
source–A
CellpyCell, a curve frame fromget_cap(categorical_column=True, label_cycle_number=True), or a(voltage, capacity)pair of arrays. -
cycles–Cycle number or list of cycle numbers.
Noneprocesses all. -
direction(str, default:BOTH) –"charge","discharge"or"both". Replaces 1.x'ssplit=True, which returned two frames of a different shape. -
options(IcaOptions | None, default:None) –An
IcaOptions. Defaults toIcaOptions(). -
strict(bool, default:False) –Raise instead of warning when a half-cycle fails.
-
cycle_mode(str | None, default:None) –Overrides the cell's own
cycle_mode, which decides whether the first half-cycle of each cycle is a charge or a discharge. -
number_of_points(int | None, default:None) –Passed to the curve extraction.
-
**overrides–Individual
IcaOptionsfields, for the common case of changing one thing.
Returns:
-
DataFrame–A long frame with columns
cycle,direction,voltage, -
DataFrame–capacity,dqdv- plus a deprecated duplicatedqcolumn that -
DataFrame–goes away in 2.1.
frame.attrscarries the options used, the -
DataFrame–resolved cycle mode, and any per-half-cycle failures.
Example
frame = dqdv(c, cycles=[1, 2], voltage_resolution=0.005) charge = frame[frame.direction == "charge"]
dqdv_cycle ¶
Deprecated. Use dqdv with a curve frame.
Returns a tuple of numpy arrays rather than a frame, and reports failure by substituting empty arrays.
Parameters:
-
cycle_df–One cycle ('potential', 'capacity', 'direction' as ±1).
-
splitter–Insert a NaN row between the two half-cycles.
-
label_direction–Also return the ±1 direction array.
dqdv_cycles ¶
Deprecated. Use dqdv with a curve frame.
Parameters:
-
cycles_df–Curve frame with a cycle-number column.
-
not_merged–Return
(cycle_numbers, frames)instead of one frame. -
label_direction–Include the ±1
directioncolumn.
dqdv_np ¶
dqdv_np(voltage, capacity, voltage_resolution=None, capacity_resolution=None, voltage_fwhm=0.01, pre_smoothing=True, diff_smoothing=False, post_smoothing=True, post_normalization=True, interpolation_method=None, gaussian_order=None, gaussian_mode=None, gaussian_cval=None, gaussian_truncate=None, points_pr_split=None, savgol_filter_window_divisor_default=None, savgol_filter_window_order=None, max_points=None, **kwargs)
dvdq ¶
dvdq(source, cycles=None, direction: str = BOTH, options: IcaOptions | None = None, *, strict: bool = False, cycle_mode: str | None = None, number_of_points: int | None = None, **overrides) -> pd.DataFrame
Differential voltage analysis (DVA): dV/dQ against capacity.
New in cellpy 2.0. DVA is the standard technique for electrode balancing
and degradation-mode analysis, and cellpy could not compute it - some
loaders ingested a dv_dq column when the cycler happened to export
one, but there was no way to derive it from the curves.
It rides the same pipeline as dqdv and is in fact the
simpler of the two: dQ/dV needs the V(q) curve inverted to q(V) before
differentiating, while dV/dQ differentiates the smoothed V(q) curve the
pipeline has already built.
Args and Returns as dqdv, except that the frame's
columns are cycle, direction, capacity, voltage, dvdq,
and that normalization defaults to False: DVA is read from the peak
positions on the capacity axis, so rescaling the ordinate would only
obscure the comparison between cycles.
Example
frame = dvdq(c, cycles=1, direction="charge") frame.plot(x="capacity", y="dvdq")
to_wide ¶
Convert a specced long frame to the wide, cycle-per-column layout.
Wide format is an explicit conversion, not an entry-point mode: in 1.x
whether you got long or wide depended on tidy= and split=, whose
defaults disagreed between functions.
Parameters:
-
frame(DataFrame) –
Returns:
-
DataFrame–A frame whose columns are a
(cycle, value)MultiIndex. When the -
DataFrame–input holds both directions the top level is
"<cycle> <direction>", -
DataFrame–so the two do not collide.
transform_half_cycle ¶
transform_half_cycle(voltage, capacity, options: IcaOptions | None = None, *, derivative: str = 'dqdv') -> HalfCycleResult
Transform one half-cycle into dQ/dV or dV/dQ.
This is the pure core: same inputs, same outputs, no shared state. It is
public because it is the honest replacement for 1.x's dqdv_np — the
"I already have two arrays" case.
Parameters:
-
voltage–Voltage samples.
-
capacity–Capacity samples, monotonic and the same length as voltage.
-
options(IcaOptions | None, default:None) –The recipe. Defaults to
IcaOptionsfordqdvand toDVA_DEFAULTS(no normalization) fordvdq. -
derivative(str, default:'dqdv') –"dqdv"or"dvdq".
Returns:
Raises:
-
NullData–If either array is missing, or has one point or fewer.
-
ValueError–If derivative is not a known mode.
Example
capacity, voltage = c.get_ccap(5, as_frame=False) result = transform_half_cycle(voltage, capacity) result.x, result.y # voltage, dQ/dV