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:

[1]:
%%capture
%pip install matplotlib
[2]:
import pyprobe
import matplotlib.pyplot as plt

%matplotlib inline
[3]:
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.add_procedure(
    procedure_name="Sample",
    folder_path=data_directory,
    filename="sample_data_neware.parquet",
)

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:

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

final_cycle.discharge(0).plot(x="Time [hr]", y="Voltage [V]")
[4]:
<Axes: xlabel='Time [hr]'>
../_images/examples_differentiating-voltage-data_6_1.png

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

[5]:
from pyprobe.analysis import differentiation

raw_data_dVdQ = differentiation.gradient(
    final_cycle.discharge(0), "Capacity [Ah]", "Voltage [V]"
)
print(raw_data_dVdQ.column_list)
raw_data_dVdQ.plot(x="Capacity [Ah]", y="d(Voltage [V])/d(Capacity [Ah])")
['Capacity [Ah]', 'Voltage [V]', 'd(Voltage [V])/d(Capacity [Ah])']
[5]:
<Axes: xlabel='Capacity [Ah]'>
../_images/examples_differentiating-voltage-data_8_2.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:

[6]:
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"
)
[6]:
<Axes: xlabel='Capacity [Ah]'>
../_images/examples_differentiating-voltage-data_10_1.png

We can now differentiate the smoothed data object:

[7]:
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])")
[7]:
Text(0, 0.5, 'd(Capacity [Ah])/d(Voltage [V])')
../_images/examples_differentiating-voltage-data_12_1.png

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

[8]:
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])")
[8]:
Text(0, 0.5, 'd(Capacity [Ah])/d(Voltage [V])')
../_images/examples_differentiating-voltage-data_14_1.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.

[9]:
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])")
[9]:
Text(0, 0.5, 'd(Capacity [Ah])/d(Voltage [V])')
../_images/examples_differentiating-voltage-data_16_1.png