Differentiating voltage data#

Differential Voltage Analysis (DVA) and Incremental Capacity Analysis are popular methods of characterising the degradation state of a cell. PyProBE offers multiple methods to the user which will be explored in this example.

First import the package and dataset:

%%capture
%pip install matplotlib
import matplotlib.pyplot as plt

import pyprobe

%matplotlib inline
info_dictionary = {
    "Name": "Sample cell",
    "Chemistry": "NMC622",
    "Nominal Capacity [Ah]": 0.04,
    "Cycler number": 1,
    "Channel number": 1,
}
data_directory = "../../../tests/sample_data/neware"

# Create a cell object
cell = pyprobe.Cell(info=info_dictionary)
cell.import_from_cycler(
    procedure_name="Sample",
    cycler="neware",
    input_data_path=data_directory + "/sample_data_neware.xlsx",
)

The break-in cycles of this dataset are at C/10, so can be analysed as pseudo-OCV curves. We’re going to look at the last cycle:

final_cycle = cell.procedure["Sample"].experiment("Break-in Cycles").cycle(-1)

final_cycle.discharge(0).plot(x="Time [hr]", y="Voltage [V]")
<Axes: xlabel='Time [hr]'>
../_images/8f5b0b195ecb62d1feb25c7a971a9d51bc8888cac6f986f1750d77f7d2a44dfa.png

We’re going to look at using the finite-difference based differentiation method, first on the raw data:

from pyprobe.analysis import differentiation

raw_data_dVdQ = differentiation.gradient(
    final_cycle.discharge(0),
    "Capacity [Ah]",
    "Voltage [V]",
)
print(raw_data_dVdQ.columns)
raw_data_dVdQ.plot(x="Capacity [Ah]", y="d(Voltage [V])/d(Capacity [Ah])")
['Capacity [Ah]', 'Voltage [V]', 'd(Voltage [V])/d(Capacity [Ah])']
<Axes: xlabel='Capacity [Ah]'>
../_images/efd9e78ce0b165fd61ecff04bcc927318b830ea9b6363accab0dd2e8227502bc.png

This gives a clearly poor result. This is due to the noise in the experimental data. We can apply a smoothing function to the voltage prior to differentiating to remove this noise:

from pyprobe.analysis import smoothing

downsampled_data = smoothing.downsample(
    input_data=final_cycle.discharge(0),
    target_column="Voltage [V]",
    sampling_interval=0.002,
)
fig, ax = plt.subplots()
final_cycle.discharge(0).plot(
    x="Capacity [Ah]",
    y="Voltage [V]",
    ax=ax,
    label="Raw data",
)
downsampled_data.plot(
    x="Capacity [Ah]",
    y="Voltage [V]",
    ax=ax,
    style="--",
    label="Downsampled data",
)
<Axes: xlabel='Capacity [Ah]'>
../_images/e93ad673ac0b4b5f1c1acbff3315914883d4d31870fcf515a58637240808c7ca.png

We can now differentiate the smoothed data object:

downsampled_data_dVdQ = differentiation.gradient(
    downsampled_data,
    "Voltage [V]",
    "Capacity [Ah]",
)

fig, ax = plt.subplots()
downsampled_data_dVdQ.plot(
    x="Voltage [V]",
    y="d(Capacity [Ah])/d(Voltage [V])",
    ax=ax,
    label="Downsampled data",
)
ax.set_ylabel("d(Capacity [Ah])/d(Voltage [V])")
Text(0, 0.5, 'd(Capacity [Ah])/d(Voltage [V])')
../_images/b6e3fb9fe2d6b4e229a6ed988d87288e7bb55775f98b010131285ecc6b76eeb2.png

PyProBE has multiple smoothing methods, so you can easily compare their effect on the ICA result:

spline_smoothed_data = smoothing.spline_smoothing(
    input_data=final_cycle.discharge(0),
    x="Capacity [Ah]",
    target_column="Voltage [V]",
    smoothing_lambda=1e-10,
)
spline_smoothed_data_dVdQ = differentiation.gradient(
    spline_smoothed_data,
    "Voltage [V]",
    "Capacity [Ah]",
)

fig, ax = plt.subplots()
downsampled_data_dVdQ.plot(
    x="Voltage [V]",
    y="d(Capacity [Ah])/d(Voltage [V])",
    ax=ax,
    label="Downsampled data",
)
spline_smoothed_data_dVdQ.plot(
    x="Voltage [V]",
    y="d(Capacity [Ah])/d(Voltage [V])",
    ax=ax,
    label="Spline smoothed data",
)
ax.set_ylabel("d(Capacity [Ah])/d(Voltage [V])")
Text(0, 0.5, 'd(Capacity [Ah])/d(Voltage [V])')
../_images/2dc9bd84ab15465b472ee2d0ba3c3bd925d1fac86e76a3b240fa342b0f3d468e.png

We can also compare to an alternative differentiation method, the LEAN method described in Feng X, Merla Y, Weng C, Ouyang M, He X, Liaw BY, et al. A reliable approach of differentiating discrete sampled-data for battery diagnosis. eTransportation. 2020;3: 100051. https://doi.org/10.1016/j.etran.2020.100051.

LEAN_dQdV = differentiation.differentiate_LEAN(
    input_data=final_cycle.discharge(0),
    x="Capacity [Ah]",
    y="Voltage [V]",
    k=10,
    gradient="dxdy",
)

fig, ax = plt.subplots()
downsampled_data_dVdQ.plot(
    x="Voltage [V]",
    y="d(Capacity [Ah])/d(Voltage [V])",
    ax=ax,
    label="Downsampled data",
)
spline_smoothed_data_dVdQ.plot(
    x="Voltage [V]",
    y="d(Capacity [Ah])/d(Voltage [V])",
    ax=ax,
    label="Spline smoothed data",
)
LEAN_dQdV.plot(
    x="Voltage [V]",
    y="d(Capacity [Ah])/d(Voltage [V])",
    ax=ax,
    label="LEAN smoothed data",
)
ax.set_ylabel("d(Capacity [Ah])/d(Voltage [V])")
14:53:33 | WARNING | pyprobe.utils:wrapper:109 - Deprecation Warning: Use the `differentiate_lean` method instead. | Context: {}
Text(0, 0.5, 'd(Capacity [Ah])/d(Voltage [V])')
../_images/aca9703d3043135c514942b78194e3f408091ae9677fcfee16d4e139674c648a.png