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)

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:
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 rowssteps_cycle.tail(10)to view the last 10 rows
| 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 |
- To extract the OCV voltages, we then filter the
steps_cycledataframe for- the OCV relaxation steps on charge,
steps_ocv_cha, of type rest, corresponding tostep_num == 3, and - the OCV relaxation steps on discharge,
steps_ocv_dch, of type rest, corresponding tostep_num == 8. Thereby we obtain two new dataframes
- the OCV relaxation steps on charge,
steps_ocv_cha = steps_cycle.loc[steps_cycle.step_num == 3]
steps_ocv_dch = steps_cycle.loc[steps_cycle.step_num == 8]
| 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()

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