Incremental capacity analysis (dQ/dV)#

In this notebook we illustrate, how to use cellpy to extract dQ/dV data for selected cycles.

The respective methods are collected in the ica utilities (cellpy.utils.ica):

  • ``ica.dqdv``: This is the main and recommended method, taking data contained within the CellpyCell object as input.

  • Additional methods allow for the calculation of dQ/dV data based on voltage and capacity data provided in different, cellpy-agnostic formats:

    • ica.dqdv_cycle and ica.dqdv_cycles takes Pandas DataFrames containing capacity vs voltage data for one or multiple cycles as input

    • ica.dqdv_np uses simple arrays or Pandas DataSeries of capacity and voltage as input.

For more details on these, have a look at the source code.

[1]:
import cellpy
from cellpy.utils import example_data, ica

Tip: The plots in this notebook are based on plotly. If you have not installed plotly, you can do so by running pip install plotly. Alternatively, you can of course also use standard plotting tools such as matplotlib to plot the data from the obtained pandas DataFrames.

[2]:
import plotly.express as px

Load an example datafile:

[3]:
c = example_data.cellpy_file()

Extracting dQ/dV data using ica.dqdv#

This example shows how to get dQ/dV data directly and easily from the CellpyCell object (obtained by loading the data using cellpy.get()).

The dQ/dV data is provided as a Pandas DataFrame.

Without specifying any further options, the dQ/dV for all cycles contained within the CellpyCell is calculated:

[4]:
ica_df=ica.dqdv(c)
ica_df.head()
[4]:
cycle voltage dq
0 1 0.051583 -6388.687162
1 1 0.054792 -6828.975477
2 1 0.058001 -7514.790533
3 1 0.061210 -8284.857174
4 1 0.064420 -9098.355196
[5]:
px.line(ica_df,x="voltage",y="dq",color="cycle")

If cycle number(s) are specified using the cycle keyword (as an integer or list of integers), the dQ/dV will be calculated for those cycles only. If split=True, two separate Pandas Dataframes will be obtained, one containing charge, and one containing discharge data:

[6]:
cycles=[2,3,4]
charge_ica,discharge_ica=ica.dqdv(c,split=True,cycle=cycles)
[7]:
charge_ica.head(3)
[7]:
voltage cycle dq
0 0.094257 2 NaN
1 0.103407 2 365.946074
2 0.112558 2 598.189449
[8]:
discharge_ica.head(3)
[8]:
voltage cycle dq
0 0.049979 2 NaN
1 0.058489 2 -6538.113356
2 0.066999 2 -7797.598247
[9]:
px.line(charge_ica,x="voltage",y="dq",color="cycle")

Tweaking the algorithm#

[10]:
ica_df_1 = ica.dqdv(c, cycle=3, voltage_resolution=0.03)
ica_df_2 = ica.dqdv(c, cycle=3, voltage_resolution=0.01)
ica_df_3 = ica.dqdv(c, cycle=3, voltage_resolution=0.005)
[11]:
import pandas as pd
ica_both = pd.concat([ica_df_1, ica_df_2, ica_df_3], keys=["0.03", "0.01", "0.005"], names=["v_res", "index"]).reset_index()
px.line(ica_both, x="voltage", y="dq", color="v_res", range_x=[0.1, 0.4], range_y=[0, 6000], symbol="v_res")

More details on dqdv#

A lot of different options with respect to smoothing, interpolation etc. are available when calculating the dQ/dV. For more details, have a look at the source code:

def dqdv(cell, split=False, tidy=True, label_direction=False, **kwargs):
    """Calculates dq-dv data for all cycles contained in
    the given CellpyCell object, returns data as pandas.DataFrame(s)

    Args:
        cell (CellpyCell-object).
        split (bool): return one frame for charge and one for
            discharge if True (defaults to False).
        tidy (bool): returns the split frames in wide format (defaults
            to True. Remark that this option is currently not available
            for non-split frames).

    Returns:
        one or two ``pandas.DataFrame`` with the following columns:
        cycle: cycle number (if split is set to True).
        voltage: voltage
        dq: the incremental capacity


    Additional key-word arguments are sent to Converter:

    Keyword Args:
        cycle (int or list of ints (cycle numbers)): will process all (or up to max_cycle_number)
            if not given or equal to None.
        points_pr_split (int): only used when investigating data
            using splits, defaults to 10.
        max_points: None
        voltage_resolution (float): used for interpolating voltage
            data (e.g. 0.005)
        capacity_resolution: used for interpolating capacity data
        minimum_splits (int): defaults to 3.
        interpolation_method: scipy interpolation method
        increment_method (str): defaults to "diff"
        pre_smoothing (bool): set to True for pre-smoothing (window)
        smoothing (bool): set to True for smoothing during
            differentiation (window)
        post_smoothing (bool): set to True for post-smoothing
            (gaussian)
        normalize (bool): set to True for normalizing to capacity
        normalizing_factor (float):
        normalizing_roof  (float):
        savgol_filter_window_divisor_default (int): used for window
            smoothing, defaults to 50
        savgol_filter_window_order: used for window smoothing
        voltage_fwhm (float): used for setting the post-processing
            gaussian sigma, defaults to 0.01
        gaussian_order (int): defaults to 0
        gaussian_mode (str): defaults to "reflect"
        gaussian_cval (float): defaults to 0.0
        gaussian_truncate (float): defaults to 4.0

    Example:
        >>> from cellpy.utils import ica
        >>> charge_df, dcharge_df = ica.dqdv(my_cell, split=True)
        >>> charge_df.plot(x="voltage",y="dq")

Using the cellpy-agnostic methods#

  • ica.dqdv_cycle and ica.dqdv_cycles takes Pandas DataFrames containing capacity vs voltage data for one or multiple cycles as input

  • ica.dqdv_np uses simple arrays or Pandas DataSeries of capacity and voltage as input.

To use the cellpy-agnostic methods mentioned above, capacity vs voltage data is needed as input. This has to be extracted first and can be done, e.g., by using the get_cap method.

Specify cycle number(s):

[12]:
cycle=2
cycles=[2,3,4]

Get capacities (note here that the dqdv methods require categorical_column and label_cycle_number to be set to True):

[13]:
vcap = c.get_cap(cycle=cycle, categorical_column=True, method="forth-and-forth",insert_nan=False,label_cycle_number=True)
vcap.head(2)
[13]:
cycle voltage capacity direction
1525 2 0.892503 0.041180 -1
1526 2 0.887276 0.176045 -1

dqdv_cycle then outputs a tuple containing voltage and incremental capacity:

[14]:
voltage, capacity = ica.dqdv_cycle(vcap)
print(f"voltage:\n{voltage[:10]}\n\ncapacity:\n{capacity[:10]}")
voltage:
[0.05087869 0.05267896 0.05447923 0.05627949 0.05807976 0.05988003
 0.06168029 0.06348056 0.06528083 0.06708109]

capacity:
[-5707.83447516 -5816.23674218 -6004.10155608 -6234.12949797
 -6480.74747702 -6733.15734537 -6987.70817472 -7244.6875613
 -7512.44888318 -7811.17678808]

while dqdv_cycles returns a pandas DataFrame:

[15]:
ica_cycles=ica.dqdv_cycles(vcap)
ica_cycles.head()
[15]:
cycle voltage dq
0 2 0.050879 -5707.834475
1 2 0.052679 -5816.236742
2 2 0.054479 -6004.101556
3 2 0.056279 -6234.129498
4 2 0.058080 -6480.747477

Doing the same for multiple cycle numbers:

[16]:
vcaps = c.get_cap(cycle=cycles, categorical_column=True, method="forth-and-forth",insert_nan=False,label_cycle_number=True)
ica_curves=ica.dqdv_cycles(vcaps)
[17]:
ica_curves.head(2)
[17]:
cycle voltage dq
0 2 0.050879 -5707.834475
1 2 0.052679 -5816.236742