In [ ]:
%load_ext autoreload
%autoreload 2

In [2]:
import os
from pathlib import Path
from pprint import pprint

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

import cellpy
from cellpy import prms
from cellpy import prmreader
from cellpy import cellreader
from cellpy.utils import ocv_rlx
import holoviews as hv

%matplotlib inline
hv.extension('bokeh')



In [3]:
######################################################################
##                                                                  ##
##                       development                                ##
##                                                                  ##
######################################################################

if os.name == 'nt':
    # Use these when working on my work PC:
    raw_data_path = r"C:\Scripting\MyFiles\development_cellpy\dev_data\gitt"
    out_data_path = r"C:\Scripting\MyFiles\development_cellpy\out"
else:
    # Use these when working on my MacBook:
    raw_data_path = "/Users/jepe/scripting/cellpy/dev_data/gitt"
    out_data_path = "/Users/jepe/scripting/cellpy/dev_data/out"

raw_data_path = Path(raw_data_path)
out_data_path = Path(out_data_path)

print(" SETTING SOME PRMS ".center(80, "="))
prms.Paths["db_filename"] = "cellpy_db.xlsx"
prms.Paths["cellpydatadir"] = out_data_path
prms.Paths["outdatadir"] = out_data_path
prms.Paths["rawdatadir"] = raw_data_path
prms.Paths["db_path"] = out_data_path
prms.Paths["filelogdir"] = out_data_path
pprint(prms.Paths)

pd.set_option('display.max_rows', 30)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)


============================== SETTING SOME PRMS ===============================
{'cellpydatadir': WindowsPath('C:/Scripting/MyFiles/development_cellpy/out'),
 'db_filename': 'cellpy_db.xlsx',
 'db_path': WindowsPath('C:/Scripting/MyFiles/development_cellpy/out'),
 'examplesdir': 'C:\\miniconda\\envs\\cellpy_dev\\Scripts',
 'filelogdir': WindowsPath('C:/Scripting/MyFiles/development_cellpy/out'),
 'notebookdir': 'C:\\Scripting\\MyFiles\\notebooks\\Cellpy',
 'outdatadir': WindowsPath('C:/Scripting/MyFiles/development_cellpy/out'),
 'rawdatadir': WindowsPath('C:/Scripting/MyFiles/development_cellpy/dev_data/gitt')}

In [4]:
fn = "20190403_cen59_04_rateGITT_01"
mass = 0.3 # mg
resn = fn + ".res"
cellpyn = fn + ".h5"
filename = prms.Paths["rawdatadir"] / resn
cellpyname = prms.Paths["cellpydatadir"] / cellpyn

In [5]:
cell = cellreader.get(filename, mass=mass, logging_mode="INFO")
cell.save(cellpyname)
cell = cellreader.get(cellpyname)


(cellpy) - Loading raw-file: C:\Scripting\MyFiles\development_cellpy\dev_data\gitt\20190403_cen59_04_rateGITT_01.res
(cellpy) - Setting mass: 0.3
(cellpy) - Creating step table
(cellpy) - Creating summary data
(cellpy) - Assuming cycling in anode half-cell (discharge before charge) mode
(cellpy) - Using the following nominal capacity: 3579
(cellpy) - Created CellpyData object
(cellpy) - Loading cellpy-file: C:\Scripting\MyFiles\development_cellpy\out\20190403_cen59_04_rateGITT_01.h5
(cellpy) - Created CellpyData object

In [6]:
# print(cell)

Some functions

Custom step-table for GITT

Need to allow for several ocv steps with same step number in the same cycle. This is not calculated by default and we therefore have to (re-) run the make_step_table method selecting all_steps=True.


In [7]:
steptable = cell.make_step_table(all_steps=True).cell.steps

Checking


In [8]:
steptable[steptable.cycle==5].head(4)


Out[8]:
index cycle step sub_step ustep point_avr point_std point_min point_max point_first point_last point_delta test_time_avr test_time_std test_time_min test_time_max test_time_first test_time_last test_time_delta step_time_avr step_time_std step_time_min step_time_max step_time_first step_time_last step_time_delta current_avr current_std current_min current_max current_first current_last current_delta voltage_avr voltage_std voltage_min voltage_max voltage_first voltage_last voltage_delta charge_avr charge_std charge_min charge_max charge_first charge_last charge_delta discharge_avr discharge_std discharge_min discharge_max discharge_first discharge_last discharge_delta ir_avr ir_std ir_min ir_max ir_first ir_last ir_delta rate_avr type sub_type info
25 25 5 16 1 26 5869.5 20.928450 5834 5905 5834 5905 1.217004 386792.057181 796.508493 386083.949044 388963.949349 386083.949044 388963.949349 0.745952 708.108140 796.508493 0.000002 2880.000308 0.000002 2880.000308 1.192655e+11 -0.000209 1.000730e-06 -0.000211 -0.000208 -0.000209 -0.000208 0.506012 0.471002 0.107113 0.310266 0.663898 0.663898 0.310266 -53.266039 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.000041 0.000046 1.400000e-13 0.000167 1.400000e-13 0.000167 1.195027e+11 71.008865 0.0 71.008865 71.008865 71.008865 71.008865 0.0 0.2 discharge None NaN
26 36 5 17 1 27 5932.0 15.443445 5906 5958 5906 5958 0.880461 390299.584918 907.127654 388964.091988 391843.951996 388964.091988 391843.951996 0.740392 1335.633324 907.127654 0.140394 2880.000402 0.140394 2880.000402 2.051272e+06 0.000000 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.364880 0.015251 0.315493 0.377609 0.315493 0.377609 19.688611 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.000167 0.000000 1.673038e-04 0.000167 1.673038e-04 0.000167 0.000000e+00 71.008865 0.0 71.008865 71.008865 71.008865 71.008865 0.0 0.0 ocvrlx_up None NaN
27 26 5 16 1 28 5977.0 10.824355 5959 5995 5959 5995 0.604128 392844.546260 995.042011 391844.000928 394724.001355 391844.000928 394724.001355 0.734986 1000.545334 995.042011 0.000003 2880.000430 0.000003 2880.000430 1.060137e+11 -0.000210 8.750639e-07 -0.000211 -0.000208 -0.000208 -0.000208 0.000000 0.308570 0.026945 0.279823 0.371767 0.371767 0.279823 -24.731713 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.000225 0.000058 1.673038e-04 0.000335 1.673038e-04 0.000335 9.999142e+01 71.008865 0.0 71.008865 71.008865 71.008865 71.008865 0.0 0.2 discharge None NaN
28 37 5 17 1 29 6022.0 15.443445 5996 6048 5996 6048 0.867245 396074.774146 908.744565 394724.689486 397603.987810 394724.689486 397603.987810 0.729445 1350.786463 908.744565 0.701803 2880.000127 0.701803 2880.000127 4.102719e+05 0.000000 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.332673 0.014398 0.285050 0.344399 0.285050 0.344399 20.820434 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.000335 0.000000 3.345933e-04 0.000335 3.345933e-04 0.000335 0.000000e+00 71.008865 0.0 71.008865 71.008865 71.008865 71.008865 0.0 0.0 ocvrlx_up None NaN

Initial exploration

Overview of the full test

Curve with labels


In [9]:
# retrieving the voltage vs time curve
t, v = cell.get_timestamp(), cell.get_voltage()
all_cycs = hv.Curve((t,v), ('t', 'time (sec)'), ('v', 'voltage (v)'), label="voltage-time").opts()

In [10]:
# creating labels
cycle_label_df = steptable.drop_duplicates("cycle")
cycle_labels = hv.Labels((cycle_label_df.test_time_first, cycle_label_df.voltage_first, cycle_label_df.cycle))

Holomap (with selector)


In [11]:
# creating a dictionary of curves (for each cycle)
cycs_dict = dict()
for c in cell.get_cycle_numbers():
    t = cell.get_timestamp(cycle=c)
    t = t - np.amin(t.values)  # setting first point to t=0
    curve = hv.Curve((t, cell.get_voltage(cycle=c)), ("time", "time (seconds)"), ("voltage", "voltage (v vs. Li/Li+)"))
    cycs_dict[c] = curve

In [12]:
# creating a holomap object
hmap = hv.HoloMap(cycs_dict,"cycle")

Curve and HoloMap


In [13]:
%%opts Curve [width=800, xformatter="%6.0f"]
(all_cycs * cycle_labels + hmap).cols(1)


Out[13]:

In [14]:
from bokeh.models import HoverTool
hover = HoverTool(tooltips=[("index", "$index")])

Processing a cycle

As we have seen, only some of the cycles contains the GITT curves. We need to select one of those. Lets start with cycle 5


In [15]:
dfdata = cell.cell.raw

In [16]:
cyc5 = dfdata.loc[dfdata.Cycle_Index == 5, :]
gt5 = steptable.loc[steptable.cycle==5, :]

In [17]:
cyc5curve = hv.Curve(cyc5,  ("Test_Time", "time"), ("Voltage", "voltage")).opts(color="grey", alpha=0.5)
cyc5points = hv.Scatter(cyc5,  ("Test_Time", "time"), ("Voltage", "voltage")).opts(size=5, fill_alpha=0.2)

In [18]:
slabels5 = hv.Labels((gt5.test_time_first, gt5.voltage_first, gt5.ustep))

In [19]:
%%opts Curve [width=1000, height=600, tools=['hover']]
cyc5curve * cyc5points * slabels5


Out[19]:

Some calculations are needed...


In [20]:
gt5.head()


Out[20]:
index cycle step sub_step ustep point_avr point_std point_min point_max point_first point_last point_delta test_time_avr test_time_std test_time_min test_time_max test_time_first test_time_last test_time_delta step_time_avr step_time_std step_time_min step_time_max step_time_first step_time_last step_time_delta current_avr current_std current_min current_max current_first current_last current_delta voltage_avr voltage_std voltage_min voltage_max voltage_first voltage_last voltage_delta charge_avr charge_std charge_min charge_max charge_first charge_last charge_delta discharge_avr discharge_std discharge_min discharge_max discharge_first discharge_last discharge_delta ir_avr ir_std ir_min ir_max ir_first ir_last ir_delta rate_avr type sub_type info
25 25 5 16 1 26 5869.5 20.928450 5834 5905 5834 5905 1.217004 386792.057181 796.508493 386083.949044 388963.949349 386083.949044 388963.949349 0.745952 708.108140 796.508493 0.000002 2880.000308 0.000002 2880.000308 1.192655e+11 -0.000209 1.000730e-06 -0.000211 -0.000208 -0.000209 -0.000208 0.506012 0.471002 0.107113 0.310266 0.663898 0.663898 0.310266 -53.266039 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.000041 0.000046 1.400000e-13 0.000167 1.400000e-13 0.000167 1.195027e+11 71.008865 0.0 71.008865 71.008865 71.008865 71.008865 0.0 0.20 discharge None NaN
26 36 5 17 1 27 5932.0 15.443445 5906 5958 5906 5958 0.880461 390299.584918 907.127654 388964.091988 391843.951996 388964.091988 391843.951996 0.740392 1335.633324 907.127654 0.140394 2880.000402 0.140394 2880.000402 2.051272e+06 0.000000 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.364880 0.015251 0.315493 0.377609 0.315493 0.377609 19.688611 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.000167 0.000000 1.673038e-04 0.000167 1.673038e-04 0.000167 0.000000e+00 71.008865 0.0 71.008865 71.008865 71.008865 71.008865 0.0 0.00 ocvrlx_up None NaN
27 26 5 16 1 28 5977.0 10.824355 5959 5995 5959 5995 0.604128 392844.546260 995.042011 391844.000928 394724.001355 391844.000928 394724.001355 0.734986 1000.545334 995.042011 0.000003 2880.000430 0.000003 2880.000430 1.060137e+11 -0.000210 8.750639e-07 -0.000211 -0.000208 -0.000208 -0.000208 0.000000 0.308570 0.026945 0.279823 0.371767 0.371767 0.279823 -24.731713 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.000225 0.000058 1.673038e-04 0.000335 1.673038e-04 0.000335 9.999142e+01 71.008865 0.0 71.008865 71.008865 71.008865 71.008865 0.0 0.20 discharge None NaN
28 37 5 17 1 29 6022.0 15.443445 5996 6048 5996 6048 0.867245 396074.774146 908.744565 394724.689486 397603.987810 394724.689486 397603.987810 0.729445 1350.786463 908.744565 0.701803 2880.000127 0.701803 2880.000127 4.102719e+05 0.000000 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.332673 0.014398 0.285050 0.344399 0.285050 0.344399 20.820434 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.000335 0.000000 3.345933e-04 0.000335 3.345933e-04 0.000335 0.000000e+00 71.008865 0.0 71.008865 71.008865 71.008865 71.008865 0.0 0.00 ocvrlx_up None NaN
29 27 5 16 1 30 6067.0 10.824355 6049 6085 6049 6085 0.595140 398606.154948 993.796816 397604.535691 400484.021343 397604.535691 400484.021343 0.724208 1002.134033 993.796816 0.514775 2880.000427 0.514775 2880.000427 5.593678e+05 -0.000209 1.044498e-06 -0.000211 -0.000207 -0.000208 -0.000211 -1.186706 0.279349 0.024593 0.254915 0.339171 0.339171 0.254915 -24.841923 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.000393 0.000058 3.346230e-04 0.000502 3.346230e-04 0.000502 5.000027e+01 71.008865 0.0 71.008865 71.008865 71.008865 71.008865 0.0 0.19 discharge None NaN

In [21]:
# approximating electrode-electrolyte contact area

diameter_si = 200.0 * 10e-7  # cm
rho_si = 2.32  # g/cm3
_mass = mass/1000
area = (2*3/diameter_si) * (_mass/rho_si) # CHECK THIS

print(f"Diameter: {diameter_si:8.6f} cm")
print(f"Mass:     {mass:8.6f} mg")
print(f"Calculated contact area (ideal case): {area:8.6f} cm2")

# For reference (v, a, m is pr Si particle)
v = (4/3)*np.pi*((diameter_si/2)**3)
m = rho_si * v
n = mass / m
a = 4 * np.pi * (diameter_si/2)**2


Diameter: 0.000200 cm
Mass:     0.300000 mg
Calculated contact area (ideal case): 3.879310 cm2

In [22]:
# estimate some values
# electrolyte-electrode area (cm2)
radius = 0.75  # cm
roughness = 1.0 # a dimensionless parameter ranging from 1 to a lot.
print("Should probably use BET to find out this?")
area = (1*roughness) * np.pi * (radius**2)
print(f"S: {area:6.2f} cm2")
print("You should compare this number to what was calculated in the cell above (the theoretical surface area for the set of same sized nanoparticles)")
# number of moles
# 1 mol Si weighs 28.0855 g
_mass = mass / 1000 # convert mass from mg to g
number_of_moles = _mass / 28.0855
print(f"n_m: {number_of_moles:8.6f} mol")

# molar volume
# The 2006 CODATA recommended value for the molar volume of silicon is 12.0588349(11)×10−6 m3/mol, with a relative standard uncertainty of 9.1×10−8
molar_volume = 12.06 # cm3/mol
print(f"V_m: {molar_volume:6.2f} cm3/mol")


Should probably use BET to find out this?
S:   1.77 cm2
You should compare this number to what was calculated in the cell above (the theoretical surface area for the set of same sized nanoparticles)
n_m: 0.000011 mol
V_m:  12.06 cm3/mol

In [ ]:


In [ ]:

The following equation to estimate the Diffusion Coefficients appears in a paper:

\begin{equation} D = \frac{4}{\pi\cdot\tau} \cdot ( \frac {m \cdot V_m}{M \cdot S})^2 \cdot ( \frac {\Delta E_s}{ \Delta E_t})^2 \: for \: \tau << L^2 / D \end{equation}

$ \Delta E_s $ = change of steady state voltage at the end of two sequential open-circuit relaxation periods
$ \Delta E_t $ = total change in the cell voltage $ E $ during the current pulse, neglecting the IR drop
$ V_s $ = mole volume of active material (cm^3/mol) (molar mass divided by the materials density)
$ M $ = ?
$ S $ = Surface area of the electrode (cm^2)
$ \tau $ = time of the current pulse
$ L $ = diffustion length

Andrzej P. Nowak et al., Procedia Engineering 98 (2014) 8


In [23]:
# Simple calculations by reading values from the graph
m = mass/1000
molar_mass = 28.0855 # g/mol
density = 2.32  # g/cm3
Vm = molar_mass / density # cm3/mol
S = 1.7 # cm2
t = 2880 # s
M = 1
Es1 = 0.378
Es2 = 0.344
DEs = Es2-Es1

Et11 = 0.378  # starting the current pulse
Et12 = 0.309 # after (possible) IR drop
Et2 = 0.28
DEt1 = Et2 - Et11
DEt2 = Et2 - Et12

p1 = (4 / (np.pi * t)) * (m * Vm / (M * S))**2
p21 = (DEs / DEt1)**2
p22 = (DEs / DEt2)**2

D1 = p1*p21
D2 = p1*p22
print(f"Assuming no IR drop: {D1}\nWith IR drop: {D2}")
print(f"A: {p1}")
print(f"DEt: {DEt1}")
print(f"DEs: {DEs}")


Assuming no IR drop: 2.4286018993722645e-10
With IR drop: 2.773399838474586e-09
A: 2.017672373838337e-09
DEt: -0.09799999999999998
DEs: -0.03400000000000003

Comparing another reference (below) I assume that M is 1/number_of_moles ?


In [ ]:

A function to automatically extract diffusion constant(s) from the steptable

This function is based on the Metrohm Autolab document. It will need some adjustments.


In [24]:
def calc_A(n_m=0.0011, V_m=12.06, S=1.77):
    """
    D = 4 /((pi)(tau)) * (n_m * V_m / S)^2 * (DEs / DEt)^2 = A /tau * (DEs / DEt)^2
    A = 4 /(pi) * (n_m * V_m / S)^2
    
    tau: duration of the current pulse (s)  
    n_m: number of moles (mol)  
    V_m: molar volume of electrode (cm3/mol)  
    S: electrode-electrolyte contact area (cm2)  
    DEs: steady state voltage change due to the current pulse (V)  
    DEt: voltage change during the constant current pulse (eliminating the iR drop) (V)  
    
    Ref.: application note from Metrohm Autolab b.v. pdf (BAT03)
    """
    A = (4 / np.pi) * (n_m * V_m / S)**2
    return A

In [25]:
def auto_calc_D(steptable, cycle_number, A=1.0, tau=None, ustep_first=None, ustep_last=None):
    """Function for extracting diffusion constant(s) and inserting into steptable.
    
    D = 4 /((pi)(tau)) * (n_m * V_m / S)^2 * (DEs / DEt)^2 = A /tau * (DEs / DEt)^2
    A = 4 /(pi) * (n_m * V_m / S)^2
    
    OBS! not corrected for IR drop yet.
    """
    st = None
    if cycle_number is None:
        # This function is intended to only work on a pr. cycle basis
        # to prevent users from "poluting" the steptable for "non_GITT" experiments.
        print("no cycle number given")
        return
    
    st = steptable[steptable.cycle==cycle_number]
    st = st[st.type.isin(["charge", "discharge", "ocvrlx_up", "ocvrlx_down"])]
    if st.empty:
        print("the given cycle is not found")
        return
    
    if ustep_first is not None:
        st = st[st.ustep>=ustep_first]
    if ustep_last is not None:
        st = st[st.ustep<=ustep_last]
    
    # used for finding DE
    n3 = st["voltage_last"].shift(periods=-3)
    n2 = st["voltage_last"].shift(periods=-2)
    n1 = st["voltage_last"].shift(periods=-1)
    n0 = st["voltage_last"]
  #  st["n3"] = n3
  #  st["n2"] = st["voltage_last"].shift(periods=-2)
  #  st["n1"] = n1
  #  st["n0"] = st["voltage_last"].shift(periods=0)
    
    # used for finding tau
    if tau is None:
        tau = st["step_time_last"] - st["step_time_first"]
    
    # used for validating if proper GITT step
    t0 = st["type"]
    t1 = st["type"].shift(periods=-1)
    t2 = st["type"].shift(periods=-2)
    t3 = st["type"].shift(periods=-3)
    
    st["valid_D"] = (t0==t2) & (t1.str.contains("ocv"))
    
    # calculating
    st["DEt"] = st["voltage_last"] - st["voltage_first"] # should remove the IR drop here (maybe calculate it based on the measured IR time the current?)
    st["DEs"] = n3 - n1
    st["D"] = A / tau * (st["DEs"] / st["DEt"])**2
    
    return st

Using the function to extract Diffusion constants vs capacity


In [26]:
# Lets just run it and see what happens
A = calc_A(n_m=0.0011, V_m=12.06, S=1.77)
print(f"A: {A}")
x = auto_calc_D(steptable, 5, A=A, ustep_first=27)
discharge = x.loc[(x.type=="discharge") & (x.valid_D)]
charge = x.loc[(x.type=="charge") & (x.valid_D)]
# discharge.head(25)


A: 7.152264581980216e-05

In [27]:
%%opts Curve [width=600 xformatter="%6.0f" tools=["hover"]]
X = discharge["discharge_avr"]*1_000_000/mass
Y = discharge["D"]
discharge_diffcurve = hv.Scatter((X,Y), ("capacity"), ("diffusion coef. (cm2 s-1)"), label="discharge").opts(size=12) * hv.Curve((X,Y)).opts(alpha=0.4)

X = charge["charge_avr"]*1_000_000/mass
Y = charge["D"]
charge_diffcurve = hv.Scatter((X,Y), ("capacity"), ("diffusion coef. (cm2 s-1)"), label="charge").opts(size=12) * hv.Curve((X,Y)).opts(alpha=0.4)

discharge_diffcurve * charge_diffcurve


Out[27]:

TODO

  • make a plot where we show each point that is used in the calculations
  • tweak the method so that we get realistic results
  • find a paper to compare with

In [ ]:

Picking out titration curves


In [28]:
cycle = 5
titcurve_ustep = 28

# filtering wrt cycle number to get the Data_Points for the step

datapoints = x.loc[(x.type=="discharge") & (x.ustep==titcurve_ustep),  ["point_first", "point_last"]]
datapoints


Out[28]:
point_first point_last
27 5959 5995

In [29]:
# Using the data points to select the data from dfdata
first = datapoints.iloc[0, 0]
last = datapoints.iloc[0, 1]
dftit = dfdata.loc[first:last, :]
dftit.head()


Out[29]:
Test_ID Data_Point Test_Time Step_Time DateTime Step_Index Cycle_Index Is_FC_Data Current Voltage Charge_Capacity Discharge_Capacity Charge_Energy Discharge_Energy dV/dt Internal_Resistance AC_Impedance ACI_Phase_Angle
Data_Point
5959 1 5959 391844.000928 0.000003 2019-04-19 21:33:30.000001 16 5 0 -0.000208 0.371767 0.0 0.000167 0.0 0.000063 0.000000 71.008865 0.0 0.0
5960 1 5960 391845.217576 1.216651 2019-04-19 21:33:31.000000 16 5 0 -0.000209 0.366539 0.0 0.000167 0.0 0.000063 -0.001046 71.008865 0.0 0.0
5961 1 5961 391847.276689 3.275764 2019-04-19 21:33:32.999999 16 5 0 -0.000209 0.361004 0.0 0.000167 0.0 0.000063 -0.002153 71.008865 0.0 0.0
5962 1 5962 391849.663499 5.662574 2019-04-19 21:33:35.000000 16 5 0 -0.000210 0.355777 0.0 0.000168 0.0 0.000063 -0.002579 71.008865 0.0 0.0
5963 1 5963 391852.377747 8.376822 2019-04-19 21:33:38.000000 16 5 0 -0.000211 0.350549 0.0 0.000168 0.0 0.000063 -0.002149 71.008865 0.0 0.0

In [30]:
dfcurve = hv.Curve(dftit,  ("Step_Time", "time"), ("Voltage", "voltage")).opts(color="grey", alpha=0.5).opts(width=1000, xformatter="%6.0f")
min_time = hv.VLine(100.0)
max_time = hv.VLine(1000.0)
a = hv.Arrow(x=500, y = 0.31, text="fit", direction= "v")
dfcurve * min_time * max_time * a


Out[30]:

In [31]:
# plotting dE vs sqrt(t) and doing a linear regression to find the slope
dE = dftit["Voltage"]
t = np.sqrt(dftit["Step_Time"])

In [32]:
decurve = hv.Scatter((t, dE), "sqrt-time", "voltage").opts(width=1000, xformatter="%6.0f")
decurve


Out[32]:

Temporary stuff (will probably be deleted)


In [ ]:

Methodology for finding diff coeffs

Equation

Eq. 1.1.
D = (4 / pi) (i*V_m / (Z_A*F*S)^2 * ((dE/dd)/(dE/d(sqrt(t)))^2  

i: current (A)  
Z_A: charge number  
F: Faraday´s constant (96_458 C/mol)
V_m: molar volume of electrode (cm3/mol)  
S: electrode-electrolyte contact area (cm2)  

(dE/dd): the slope of the coulometric titration curve, found by plotting the
steady state voltages E (V) measured after each titration step δ

steady state voltage change due to the current pulse (V)  
(dE/d(sqrt(t)):  the slope of the linearized plot of the potential E (V) during the current pulse of duration t (s).

step 1

Need to find the slope of E vs δ. This is the same as finding dV/dQ. One way to do this is to fit the "relaxed" voltage curve (the OCV_RLX points) to a polynomial and diffing it.

step 2

Then we also need to find the slope of E vs time during the pulse. For this we can e.g. use the polytfit function from scipy. The first 100 seconds should not be used as they are typically from charge-transfere resistance and the concentration overpotential.

step 3

Now it is time to get all the other prms (where the most difficult one is the electrode-electrolyte contact area). It should ideally be found using BET. But I guess the best is to calculate it using a sensible model, or from some scattering experiments (SANS?).


In [ ]:

Example

Based on application note from Metrohm Autolab b.v. pdf (BAT03)

Eq. 1.1.
D = (4 / pi) (i*V_m / (Z_A*F*S)^2 * ((dE/dd)/(dE/d(sqrt(t)))^2  

i: current (A)  
Z_A: charge number  
F: Faraday´s constant (96_458 C/mol)
V_m: molar volume of electrode (cm3/mol)  
S: electrode-electrolyte contact area (cm2)  

(dE/dd): the slope of the coulometric titration curve, found by plotting the
steady state voltages E (V) measured after each titration step δ

steady state voltage change due to the current pulse (V)  
(dE/d(sqrt(t)):  the slope of the linearized plot of the potential E (V) during the current pulse of duration t (s).
Eq. 1.2.
D = 4 /((pi)(tau)) * (n_m * V_m / S)^2 * (DEs / DEt)^2  

tau: duration of the current pulse (s)  
n_m: number of moles (mol)  
V_m: molar volume of electrode (cm3/mol)  
S: electrode-electrolyte contact area (cm2)  
DEs: steady state voltage change due to the current pulse (V)  
DEt: voltage change during the constant current pulse (eliminating the iR drop) (V)

In [ ]:

Example code:

D = (A / tau) * (DEs / DEt)**2
# during charge
DEt = ustep_52.voltage[-1] - ustep_52.voltage[0]  # charge step
DEs = ustep_55.voltage[-1] - ustep_53.voltage[0]  # ocv steps
# during discharge
DEt = ustep_27.voltage[-1] - ustep_27.voltage[0]  # discharge step
DEs = ustep_30.voltage[-1] - ustep_28.voltage[0]  # ocv steps

Using step-table

DEt = ustep[n].voltage_last - ustep[n].voltage_first  # charge step
DEs = ustep[n+3].voltage_last - ustep[n+1].voltage_first # ocv steps