Analysing GITT data#

PyProBE includes built-in analysis methods for pulsing experiments, which this example will demonstrate.

First import the required libraries and data:

[1]:
import pyprobe

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')
print(cell.procedure['Sample'].experiment_names)


['Initial Charge', 'Break-in Cycles', 'Discharge Pulses']

We will plot the Break-in Cycles and Discharge Pulses:

[2]:
fig = pyprobe.Plot()
fig.add_line(cell.procedure['Sample'].experiment('Break-in Cycles'), 'Time [hr]', 'Voltage [V]', label = 'Break-in Cycles', color = 'blue')
fig.add_line(cell.procedure['Sample'].experiment('Discharge Pulses'), 'Time [hr]', 'Voltage [V]', label = 'Discharge Pulses', color = 'red')
fig.show_image()
2024-12-23 16:54:23,965 - pyprobe.units - ERROR - Name Date does not match pattern.
2024-12-23 16:54:23,965 - pyprobe.units - ERROR - Name Step does not match pattern.
2024-12-23 16:54:23,966 - pyprobe.units - ERROR - Name Event does not match pattern.
2024-12-23 16:54:24,002 - pyprobe.units - ERROR - Name Date does not match pattern.
2024-12-23 16:54:24,002 - pyprobe.units - ERROR - Name Step does not match pattern.
2024-12-23 16:54:24,003 - pyprobe.units - ERROR - Name Event does not match pattern.
../_images/examples_analysing-GITT-data_3_1.png

State-of-charge is a useful metric when working with battery data, however it must be carefully defined. PyProBE doesn’t automatically calculate a value for cell SOC until instructed to by the user for this reason.

To add an SOC column to the data, we call set_SOC() on the procedure. We are going to provide an argument to reference_charge. This will be the final charge of the break-in cycles. This argument instructs PyProBE that the final data point of this charge is our 100% SOC reference.

[3]:
reference_charge = cell.procedure['Sample'].experiment('Break-in Cycles').charge(-1)
cell.procedure['Sample'].set_SOC(reference_charge=reference_charge)

fig = pyprobe.Plot()
fig.add_line(cell.procedure['Sample'].experiment('Break-in Cycles'), 'Time [hr]', 'SOC', label = 'Break-in Cycles', color = 'blue')
fig.add_line(cell.procedure['Sample'].experiment('Discharge Pulses'), 'Time [hr]', 'SOC', label = 'Discharge Pulses', color = 'red')
fig.show_image()
2024-12-23 16:54:27,647 - pyprobe.units - ERROR - Name Date does not match pattern.
2024-12-23 16:54:27,647 - pyprobe.units - ERROR - Name Step does not match pattern.
2024-12-23 16:54:27,648 - pyprobe.units - ERROR - Name Event does not match pattern.
2024-12-23 16:54:27,648 - pyprobe.units - ERROR - Name Full charge reference capacity does not match pattern.
2024-12-23 16:54:27,649 - pyprobe.units - ERROR - Name SOC does not match pattern.
2024-12-23 16:54:27,776 - pyprobe.units - ERROR - Name Date does not match pattern.
2024-12-23 16:54:27,776 - pyprobe.units - ERROR - Name Step does not match pattern.
2024-12-23 16:54:27,777 - pyprobe.units - ERROR - Name Event does not match pattern.
2024-12-23 16:54:27,778 - pyprobe.units - ERROR - Name Full charge reference capacity does not match pattern.
2024-12-23 16:54:27,778 - pyprobe.units - ERROR - Name SOC does not match pattern.
../_images/examples_analysing-GITT-data_5_1.png

Then we’ll filter to only the pulsing experiment:

[4]:
pulsing_experiment = cell.procedure['Sample'].experiment('Discharge Pulses')

fig = pyprobe.Plot()
fig.add_line(pulsing_experiment, 'Experiment Time [hr]', 'Voltage [V]')
fig.show_image()
2024-12-23 16:54:31,589 - pyprobe.units - ERROR - Name Date does not match pattern.
2024-12-23 16:54:31,590 - pyprobe.units - ERROR - Name Step does not match pattern.
2024-12-23 16:54:31,590 - pyprobe.units - ERROR - Name Event does not match pattern.
2024-12-23 16:54:31,591 - pyprobe.units - ERROR - Name Full charge reference capacity does not match pattern.
2024-12-23 16:54:31,591 - pyprobe.units - ERROR - Name SOC does not match pattern.
../_images/examples_analysing-GITT-data_7_1.png

And then create our pulsing analysis object.

[5]:
from pyprobe.analysis import pulsing
pulse_object = pulsing.Pulsing(input_data=pulsing_experiment)

With the pulsing object we can separate out individual pulses:

[6]:
fig = pyprobe.Plot()
fig.add_line(pulse_object.input_data, 'Experiment Time [hr]', 'Voltage [V]', color='blue', label='Full Experiment')
fig.add_line(pulse_object.pulse(4), "Experiment Time [hr]", "Voltage [V]", label = 'Pulse 5', color = 'red')
fig.show_image()
../_images/examples_analysing-GITT-data_11_0.png

We can also extract key parameters from the pulsing experiment, with the get_resistances() method.

[7]:
pulse_resistances = pulsing.get_resistances(input_data=pulsing_experiment)
print(pulse_resistances.data)
shape: (10, 5)
┌──────────────┬───────────────┬──────────┬─────────┬───────────┐
│ Pulse Number ┆ Capacity [Ah] ┆ SOC      ┆ OCV [V] ┆ R0 [Ohms] │
│ ---          ┆ ---           ┆ ---      ┆ ---     ┆ ---       │
│ u32          ┆ f64           ┆ f64      ┆ f64     ┆ f64       │
╞══════════════╪═══════════════╪══════════╪═════════╪═══════════╡
│ 1            ┆ 0.062214      ┆ 1.0      ┆ 4.1919  ┆ 1.805578  │
│ 2            ┆ 0.058214      ┆ 0.903497 ┆ 4.0949  ┆ 1.835632  │
│ 3            ┆ 0.054213      ┆ 0.806994 ┆ 3.9934  ┆ 1.775612  │
│ 4            ┆ 0.050213      ┆ 0.710493 ┆ 3.8987  ┆ 1.750596  │
│ 5            ┆ 0.046213      ┆ 0.613991 ┆ 3.8022  ┆ 1.725532  │
│ 6            ┆ 0.042212      ┆ 0.517489 ┆ 3.7114  ┆ 1.705558  │
│ 7            ┆ 0.038212      ┆ 0.420988 ┆ 3.665   ┆ 1.705622  │
│ 8            ┆ 0.034212      ┆ 0.324487 ┆ 3.6334  ┆ 1.735555  │
│ 9            ┆ 0.030212      ┆ 0.227986 ┆ 3.5866  ┆ 1.795638  │
│ 10           ┆ 0.026211      ┆ 0.131485 ┆ 3.5164  ┆ 1.900663  │
└──────────────┴───────────────┴──────────┴─────────┴───────────┘

The get_resistances() method can take an argument of a list of times at which to evaluate the resistance after the pulse, for instance at 10s after the pulse:

[8]:
pulse_resistances = pulsing.get_resistances(input_data=pulsing_experiment, r_times=[10])
print(pulse_resistances.data)
shape: (10, 6)
┌──────────────┬───────────────┬──────────┬─────────┬───────────┬──────────────┐
│ Pulse Number ┆ Capacity [Ah] ┆ SOC      ┆ OCV [V] ┆ R0 [Ohms] ┆ R_10s [Ohms] │
│ ---          ┆ ---           ┆ ---      ┆ ---     ┆ ---       ┆ ---          │
│ u32          ┆ f64           ┆ f64      ┆ f64     ┆ f64       ┆ f64          │
╞══════════════╪═══════════════╪══════════╪═════════╪═══════════╪══════════════╡
│ 1            ┆ 0.062214      ┆ 1.0      ┆ 4.1919  ┆ 1.805578  ┆ 2.910931     │
│ 2            ┆ 0.058214      ┆ 0.903497 ┆ 4.0949  ┆ 1.835632  ┆ 2.805967     │
│ 3            ┆ 0.054213      ┆ 0.806994 ┆ 3.9934  ┆ 1.775612  ┆ 2.735943     │
│ 4            ┆ 0.050213      ┆ 0.710493 ┆ 3.8987  ┆ 1.750596  ┆ 2.685915     │
│ 5            ┆ 0.046213      ┆ 0.613991 ┆ 3.8022  ┆ 1.725532  ┆ 2.640815     │
│ 6            ┆ 0.042212      ┆ 0.517489 ┆ 3.7114  ┆ 1.705558  ┆ 2.400785     │
│ 7            ┆ 0.038212      ┆ 0.420988 ┆ 3.665   ┆ 1.705622  ┆ 2.345855     │
│ 8            ┆ 0.034212      ┆ 0.324487 ┆ 3.6334  ┆ 1.735555  ┆ 2.390765     │
│ 9            ┆ 0.030212      ┆ 0.227986 ┆ 3.5866  ┆ 1.795638  ┆ 2.565912     │
│ 10           ┆ 0.026211      ┆ 0.131485 ┆ 3.5164  ┆ 1.900663  ┆ 3.026056     │
└──────────────┴───────────────┴──────────┴─────────┴───────────┴──────────────┘

As a result object, the pulse summary can also be plotted:

[9]:
fig = pyprobe.Plot()
fig.add_line(pulse_resistances, 'SOC', 'R0 [Ohms]', color='blue', label='R0')
fig.add_line(pulse_resistances, 'SOC', 'R_10s [Ohms]', color='red', label='R_10s')
fig.yaxis_title = 'Resistance [Ohms]'
fig.show_image()
../_images/examples_analysing-GITT-data_17_0.png