Skip to content

GITT analysis

In this notebook we will use cellpy to extract the open circuit voltages (OCV) from a GITT measurement. The extracted OCVs will be plotted, and the results saved in .csv format.

import pathlib

import pandas as pd
import plotly.graph_objects as go

import cellpy
from cellpy.utils import plotutils

Set filepath and load the datafile:

filedir = pathlib.Path("data")  # foldername within the same directory
candidates = [
    filedir / "20210210_FC.h5",
    filedir / "out" / "20210210_FC.h5",
]
cellpy_path = next((p for p in candidates if p.exists()), None)
if cellpy_path is None:
    raise FileNotFoundError(
        "Could not find 20210210_FC.h5 in examples/data/ or examples/data/out/. "
        "Run notebook 01, or place the file in examples/data/."
    )
c = cellpy.get(cellpy_path)

Produce an overview plot to identify cycle numbers for the GITT experiment (for an interactive version of this plot, you have to have plotly installed):

cycles = [n for n in c.get_cycle_numbers() if 2 <= n <= 6]
plotutils.cycle_info_plot(c, cycle=cycles)

png

From the overview plot above, we can identify the GITT cycles to be cycle number 4 and 5. In the following, we will focus on cycle 5 only.

For further analysis, we create the step table, called steps, a dataframe that contains a lot of information on all the cycle steps for the cell.

In the following, we apply several filters to steps, to eventually extract OCV voltages and corresponding capacities:

  1. steps_cycle: Extract the rows specifically for the selected GITT cycle (here: cycle Nr 5).

NB: For simplicity, steps_cycle only contains columns relevant for further analysis, i.e. cycle_num, step_num, charge_capacity_last, discharge_capacity_last, potential_first, potential_last, step_type.

GITT_cycle = 5
c.make_step_table(all_steps=True)
steps = c.data.steps
steps_cycle = steps.loc[
    steps.cycle_num == GITT_cycle,
    [
        "cycle_num",
        "step_num",
        "charge_capacity_last",
        "discharge_capacity_last",
        "potential_first",
        "potential_last",
        "step_type",
    ],
]

Taking a closer look at the created steps_cycle dataframe:

  • steps_cycle.head(10) to view the first 10 rows
  • steps_cycle.tail(10) to view the last 10 rows
steps_cycle.tail(10)
cycle_num step_num charge_capacity_last discharge_capacity_last potential_first potential_last step_type
755 5 8 0.003358 0.003258 3.212396 3.343531 ocvrlx_up
756 5 7 0.003358 0.003294 3.330632 3.139919 discharge
757 5 8 0.003358 0.003294 3.162645 3.314970 ocvrlx_up
758 5 7 0.003358 0.003330 3.302993 3.080647 discharge
759 5 8 0.003358 0.003330 3.102759 3.283338 ocvrlx_up
760 5 7 0.003358 0.003366 3.272282 3.008170 discharge
761 5 8 0.003358 0.003366 3.029361 3.246485 ocvrlx_up
762 5 7 0.003358 0.003392 3.233587 2.999878 discharge
763 5 10 0.003358 0.003392 3.010627 3.010627 ir
764 5 11 0.003358 0.003392 3.037038 3.228980 ocvrlx_up
  1. To extract the OCV voltages, we then filter the steps_cycle dataframe for
    • the OCV relaxation steps on charge, steps_ocv_cha, of type rest, corresponding to step_num == 3, and
    • the OCV relaxation steps on discharge, steps_ocv_dch, of type rest, corresponding to step_num == 8. Thereby we obtain two new dataframes
steps_ocv_cha = steps_cycle.loc[steps_cycle.step_num == 3]
steps_ocv_dch = steps_cycle.loc[steps_cycle.step_num == 8]
steps_ocv_cha.head(5)
cycle_num step_num charge_capacity_last discharge_capacity_last potential_first potential_last step_type
390 5 3 0.000036 0.0 3.512440 3.487564 rest
392 5 3 0.000072 0.0 3.518582 3.494320 rest
394 5 3 0.000109 0.0 3.524724 3.499848 rest
396 5 3 0.000145 0.0 3.530559 3.505991 rest
398 5 3 0.000181 0.0 3.537315 3.513054 rest

The voltages at the end of these steps (potential_last) contain the (pseudo-) OCV voltages:

V_cha = steps_ocv_cha.potential_last.reset_index(drop=True)
V_dch = steps_ocv_dch.potential_last.reset_index(drop=True)
cap_cha = (
    steps_ocv_cha.charge_capacity_last.reset_index(drop=True) * 1000
)  # *1000 to convert to mAh
cap_dch = (
    steps_ocv_dch.discharge_capacity_last.reset_index(drop=True) * 1000
)  # *1000 to convert to mAh

To plot our results, we additionally get the entire voltage vs capacity curves for the selected GITT cycle, employing the .get_ccap and .get_dcap methods. The cell mass is used to convert from gravimetric capacity (mAh/g) to capacity (mAh).

c.make_step_table(all_steps=False)
ccap = c.get_ccap(cycle=GITT_cycle)
dcap = c.get_dcap(cycle=GITT_cycle)
mass = c.get_mass()  # in mg
fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=ccap["cumulative_charge_capacity"] * mass / 1000,
        y=ccap["potential"],
        mode="lines",
        name="charge",
        line=dict(color="royalblue"),
    )
)
fig.add_trace(
    go.Scatter(
        x=cap_cha,
        y=V_cha,
        mode="markers",
        name="OCV charge",
        marker=dict(color="royalblue", size=9),
    )
)
fig.add_trace(
    go.Scatter(
        x=dcap["cumulative_discharge_capacity"] * mass / 1000,
        y=dcap["potential"],
        mode="lines",
        name="discharge",
        line=dict(color="seagreen"),
    )
)
fig.add_trace(
    go.Scatter(
        x=cap_dch,
        y=V_dch,
        mode="markers",
        name="OCV discharge",
        marker=dict(color="seagreen", size=9),
    )
)
fig.update_layout(
    title="GITT OCV curve",
    xaxis_title="Capacity [mAh]",
    yaxis_title="Voltage [V]",
    width=1000,
    height=700,
    template="plotly_white",
    legend=dict(font=dict(size=14)),
)
fig.show()

png

Saving the data

Concatenate the OCV voltages and capacities into a dataframe, and save as a .csv file.

OCV_cha = pd.concat([cap_cha, V_cha], axis=1, keys=["Charge_cap_mAh", "OCV_V"])
OCV_dch = pd.concat([cap_dch, V_dch], axis=1, keys=["Discharge_cap_mAh", "OCV_V"])
# OCV_cha.to_csv('GITT_OCV_cycle'+str(GITT_cycle)+'_cha.csv', index=False)
# OCV_dch.to_csv('GITT_OCV_cycle'+str(GITT_cycle)+'_dch.csv', index=False)