Skip to content

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

Both are thin wrappers over stateless transform functions (transform_half_cycle) configured by a single frozen IcaOptions object: interpolate V(q), optionally smooth, invert to q(V), differentiate, optionally smooth again, optionally normalize.

  • The output frame is specced: always long format, always the same columns, with direction spelled "charge"/"discharge".
  • The incremental-capacity column is named dqdv.
  • Half-cycles that fail are reported, not silently replaced by empty arrays.
  • ica.Converter and the other pre-2.1 entry points are removed; use dqdv(cell, cycles=, direction=) + to_wide() (see the migration guide and DEPRECATIONS.md).

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.

GaussianOptions dataclass

GaussianOptions(order: int = 0, mode: str = 'reflect', cval: float = 0.0, truncate: float = 4.0)

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 for dvdq.

  • 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 for dvdq.

  • derivative (str) –

    "dqdv" or "dvdq".

  • normalizing_factor (float) –

    The factor actually used, returned rather than mutated onto shared state, so processing one half-cycle can never leak its normalization into the next.

  • 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')

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

ordered_names(derivative: str = 'dqdv') -> list[str]

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.

All options for a transform live on one frozen object, passed to transform_half_cycle or built implicitly from the **overrides on dqdv / dvdq.

Attributes:

  • voltage_resolution (float | None) –

    Voltage step for the q(V) interpolation, e.g. 0.005. None keeps the point count of the input.

  • capacity_resolution (float | None) –

    Capacity step for the V(q) interpolation. Ignored when max_points is set.

  • max_points (int | None) –

    Cap on the V(q) interpolation grid. Takes precedence over capacity_resolution.

  • interpolation_method (str) –

    Any kind accepted by scipy.interpolate.interp1d.

  • pre_smoothing (bool) –

    Savitzky-Golay smoothing of V(q) before inversion.

  • diff_smoothing (bool) –

    Savitzky-Golay smoothing of q(V) before differentiating.

  • 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. None derives it as 1% of the capacity span of the half-cycle, which is the same fraction the default voltage_fwhm of 0.01 V represents on a typical ~1 V window.

  • gaussian (GaussianOptions) –

    Remaining gaussian_filter1d parameters.

  • normalize (Literal['area'] | bool) –

    "area" scales the curve so its integral equals the normalizing factor; False leaves it in physical units. The legacy True spelling of "area" is also accepted.

  • normalizing_factor (float | None) –

    Target for normalize="area". None uses 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" is implemented on this path; the unfinished "hist" binning method lived on the 1.x Converter class, which was removed in 2.1.

replace

replace(**overrides: Any) -> 'IcaOptions'

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 from get_cap(categorical_column=True, label_cycle_number=True), or a (voltage, capacity) pair of arrays.

  • cycles

    Cycle number or list of cycle numbers. None processes all.

  • direction (str, default: BOTH ) –

    "charge", "discharge" or "both".

  • options (IcaOptions | None, default: None ) –

    An IcaOptions. Defaults to IcaOptions().

  • 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 IcaOptions fields, for the common case of changing one thing.

Returns:

  • DataFrame

    A long frame with columns cycle, direction, voltage,

  • DataFrame

    capacity, dqdv. frame.attrs carries 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"]

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.

DVA is the standard technique for electrode balancing and degradation-mode analysis.

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")

index_bounds

index_bounds(x) -> tuple[float, float]

Return (first, last) item of x.

to_wide

to_wide(frame: DataFrame) -> pd.DataFrame

Convert a specced long frame to the wide, cycle-per-column layout.

Wide format is always an explicit conversion, never an implicit mode of dqdv / dvdq.

Parameters:

  • frame (DataFrame) –

    A frame from dqdv or dvdq.

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 for the "I already have two arrays" case, where extracting a half-cycle from a CellpyCell first would be pure overhead.

Parameters:

  • voltage

    Voltage samples.

  • capacity

    Capacity samples, monotonic and the same length as voltage.

  • options (IcaOptions | None, default: None ) –

    The recipe. Defaults to IcaOptions for dqdv and to DVA_DEFAULTS (no normalization) for dvdq.

  • 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

value_bounds

value_bounds(x) -> tuple[float, float]

Return (min, max) of x.