Cellpy batch

Author: Jan Petter Mæhlen, IFE

Date: 2018-10-24

  1. session example
  2. analyser example

Setting up the project

Imports


In [1]:
%load_ext autoreload
%autoreload 2

In [24]:
%matplotlib widget
# import seaborn as sbn # can be used for getting nice colormaps and good settings of the matplotlib rc

In [25]:
import holoviews as hv
hv.extension('bokeh')



In [4]:
from cellpy.utils import batch
from cellpy import prms

Need to load some data to play with


In [5]:
import os

In [6]:
current_file_path = os.getcwd()
relative_test_data_dir = "../testdata"
test_data_dir = os.path.abspath(os.path.join(current_file_path, relative_test_data_dir))
test_data_dir_raw = os.path.join(test_data_dir, "data")
test_res_file = "20160805_test001_45_cc_01.res"
test_res_file_full = os.path.join(test_data_dir_raw,test_res_file)
test_data_dir_out = os.path.join(test_data_dir, "out")
test_data_dir_cellpy = os.path.join(test_data_dir, "hdf5")
test_cellpy_file = "20160805_test001_45_cc.h5"
test_cellpy_file_tmp = "tmpfile.h5"
test_cellpy_file_full = os.path.join(test_data_dir_cellpy,test_cellpy_file)
test_cellpy_file_tmp_full = os.path.join(test_data_dir_cellpy,test_cellpy_file_tmp)
test_run_name = "20160805_test001_45_cc"

In [ ]:

Checking if we can do any lookup in the cellpyfiles


In [ ]:


In [23]:
import pandas as pd
import matplotlib.pyplot as plt
infoname = '/CellpyData/info'
dataname = '/CellpyData/dfdata'
summaryname = '/CellpyData/dfsummary'
fidname = '/CellpyData/fidtable'
stepname = '/CellpyData/step_table'
v_hdr = "Voltage"
c_hdr = "Charge_Capacity"
d_hdr = "Discharge_Capacity"
i_hdr = "Current"

Defining some functions


In [9]:
def _query_dfdata(store, q):
    return store.select('/CellpyData/dfdata', q)

In [10]:
def _convert_to_specific(s, mass):
    return  s*1000000.0 / mass

In [11]:
def _make_selectors(t):
    if not t.lower() in [
        "galvanostatic_charge", "galvanostatic_discharge",
        "cycle", "ocv",
        "discharge", "cv_discharge", "charge", "cv_charge", "ocvrlx_up", "ocvrlx_down",
    ]:
        # error-message here
        return
    selectors = []
    if t.lower() == "galvanostatic_charge":
        selectors.append("charge")
    elif t.lower() == "galvanostatic_discharge": 
        selectors.append("discharge")
    elif t.lower() == "charge": 
        selectors.extend(["charge", "cv_charge"])
    elif t.lower() == "discharge": 
        selectors.extend(["discharge", "cv_discharge"])
    elif t.lower() == "cycle": 
        selectors.extend(["charge", "discharge"])
    elif t.lower() == "ocv": 
        selectors.extend(["ocvrlx_down", "ocvrlx_up"])
    else:
        selectors.append(t.lower())
                
    return(selectors)

In [12]:
def _get_step_table(file_name):
    step_name = '/CellpyData/step_table'
    with pd.HDFStore(file_name) as store:
        step_table = store.select(step_name)
    return step_table

In [13]:
def _get_info_name(file_name):
    info_name = '/CellpyData/info'
    with pd.HDFStore(file_name) as store:
        info_table = store.select(info_name)
    return info_table

In [14]:
def _filter_step_table(selectors, step_table, cycle_numbers):
    steps = step_table.type.isin(selectors) & step_table.cycle.isin(cycle_numbers)
    return step_table.loc[steps]

In [39]:
def _generate_cycle_data_query(a, headers=None):
    """create cycle query from a step_table DataFrame"""
    a = a[["point_first", "point_last"]]
    if headers is None:
        headers = [
            "Step_Time",
            "Voltage",
            "Charge_Capacity",
            "Discharge_Capacity",
            "Current",
            "Cycle_Index"
        ]
        
    sections = []
    for points in a.iterrows():
        p1, p2 = points[-1].values
        sections.append(f"(index>={p1} & index<={p2})")
    q = " | ".join(sections)
    
    hdr_txts = []
    for h in headers:
        hdr_txts.append(f"'{h}'")
    hdr_txt = ", ".join(hdr_txts)
    q += f" & columns = [{hdr_txt}]"
    return q

Testing if the functions work


In [16]:
cellpyfile = test_cellpy_file_full
step_table = _get_step_table(cellpyfile)

In [17]:
info_table = _get_info_name(cellpyfile)

In [18]:
mass = float(info_table.mass)

In [19]:
cycle_numbers = [3]

In [40]:
with pd.HDFStore(cellpyfile) as store:
    selectors = _make_selectors("cycle")
    a = _filter_step_table(selectors, step_table, cycle_numbers=cycle_numbers)
    q = _generate_cycle_data_query(a)
    c = _query_dfdata(store, q)
    c["Charge_Capacity"] = _convert_to_specific(c["Charge_Capacity"], mass)
    c["Discharge_Capacity"] = _convert_to_specific(c["Discharge_Capacity"], mass)

In [41]:
c.describe()


Out[41]:
Step_Time Cycle_Index Current Voltage Charge_Capacity Discharge_Capacity
count 768.000000 768.0 768.000000 768.000000 768.000000 768.000000
mean 18395.104785 3.0 -0.000002 0.369468 456.146432 1106.333485
std 12877.350641 0.0 0.000153 0.225929 599.924719 598.334572
min 0.016000 3.0 -0.000153 0.049894 0.000000 0.002021
25% 6562.532781 3.0 -0.000152 0.210547 0.000000 586.389208
50% 18075.800431 3.0 -0.000152 0.305000 0.000000 1559.847765
75% 29589.076835 3.0 0.000154 0.501987 920.236324 1585.720948
max 40588.536438 3.0 0.000154 1.000113 1731.507588 1585.720948

In [42]:
c.head()


Out[42]:
Step_Time Cycle_Index Current Voltage Charge_Capacity Discharge_Capacity
Data_Point
2284 0.047976 3 -0.000152 0.844310 0.0 0.002021
2285 0.927995 3 -0.000152 0.839075 0.0 0.039225
2286 2.650064 3 -0.000152 0.833840 0.0 0.112076
2287 4.732038 3 -0.000153 0.828606 0.0 0.200163
2288 7.123093 3 -0.000152 0.823371 0.0 0.301311

Plotting


In [26]:
c.plot.scatter(x="Charge_Capacity", y="Voltage", )


Out[26]:
<matplotlib.axes._subplots.AxesSubplot at 0xd1d802c18>

In [27]:
with pd.HDFStore(cellpyfile) as store:
    selectors = _make_selectors("galvanostatic_charge")
    a = _filter_step_table(selectors, step_table, cycle_numbers=cycle_numbers)
    q = _generate_cycle_data_query(a)
    gc = _query_dfdata(store, q)
    gc["Charge_Capacity"] = _convert_to_specific(gc["Charge_Capacity"], mass)

In [28]:
gc.head()


Out[28]:
Step_Time Current Voltage Charge_Capacity Discharge_Capacity
Data_Point
2709 0.016000 0.000154 0.107166 0.000684 0.001586
2710 3.010030 0.000154 0.112400 0.128401 0.001586
2711 18.349153 0.000154 0.117635 0.782761 0.001586
2712 46.428552 0.000154 0.122869 1.980588 0.001586
2713 85.142930 0.000154 0.128412 3.632003 0.001586

In [29]:
def get_charge_cycle(step_table, cycle_number, mass):
    headers = [
            "Step_Time",
            "Voltage",
            "Charge_Capacity",
            #"Discharge_Capacity",
            #"Current"
        ]
    with pd.HDFStore(cellpyfile) as store:
        selectors = _make_selectors("galvanostatic_charge")
        a = _filter_step_table(selectors, step_table, cycle_numbers=[cycle_number])
        q = _generate_cycle_data_query(a)
        gc = _query_dfdata(store, q)
        gc["Charge_Capacity"] = _convert_to_specific(gc["Charge_Capacity"], mass)
    return gc

In [30]:
cycles = list(step_table.cycle.unique())
number_of_cycles = len(cycles)

In [37]:
%%time
charge_cycles = []
for n in cycles[:]:
    try:
        cycle = get_charge_cycle(step_table, n, mass)
    except SyntaxError as e:
        print(f"could not extract cycle {n}")
        print(e)
        
    charge_cycles.append(cycle)


could not extract cycle 18
Python keyword not valid identifier in numexpr query (<unknown>, line 1)
CPU times: user 359 ms, sys: 22.4 ms, total: 382 ms
Wall time: 385 ms

In [32]:
from cellpy.utils import ica

In [33]:
import matplotlib.style
import matplotlib as mpl
import numpy as np
mpl.style.use('seaborn')

In [34]:
colormap = "viridis"

Possible colormaps

Accent, Accent_r, Blues, Blues_r, BrBG, BrBG_r, BuGn, BuGn_r, BuPu, BuPu_r, CMRmap, CMRmap_r, Dark2, Dark2_r, GnBu, GnBu_r, Greens, Greens_r, Greys, Greys_r, OrRd, OrRd_r, Oranges, Oranges_r, PRGn, PRGn_r, Paired, Paired_r, Pastel1, Pastel1_r, Pastel2, Pastel2_r, PiYG, PiYG_r, PuBu, PuBuGn, PuBuGn_r, PuBu_r, PuOr, PuOr_r, PuRd, PuRd_r, Purples, Purples_r, RdBu, RdBu_r, RdGy, RdGy_r, RdPu, RdPu_r, RdYlBu, RdYlBu_r, RdYlGn, RdYlGn_r, Reds, Reds_r, Set1, Set1_r, Set2, Set2_r, Set3, Set3_r, Spectral, Spectral_r, Wistia, Wistia_r, YlGn, YlGnBu, YlGnBu_r, YlGn_r, YlOrBr, YlOrBr_r, YlOrRd, YlOrRd_r, afmhot, afmhot_r, autumn, autumn_r, binary, binary_r, bone, bone_r, brg, brg_r, bwr, bwr_r, cividis, cividis_r, cool, cool_r, coolwarm, coolwarm_r, copper, copper_r, cubehelix, cubehelix_r, flag, flag_r, gist_earth, gist_earth_r, gist_gray, gist_gray_r, gist_heat, gist_heat_r, gist_ncar, gist_ncar_r, gist_rainbow, gist_rainbow_r, gist_stern, gist_stern_r, gist_yarg, gist_yarg_r, gnuplot, gnuplot2, gnuplot2_r, gnuplot_r, gray, gray_r, hot, hot_r, hsv, hsv_r, inferno, inferno_r, jet, jet_r, magma, magma_r, nipy_spectral, nipy_spectral_r, ocean, ocean_r, pink, pink_r, plasma, plasma_r, prism, prism_r, rainbow, rainbow_r, seismic, seismic_r, spring, spring_r, summer, summer_r, tab10, tab10_r, tab20, tab20_r, tab20b, tab20b_r, tab20c, tab20c_r, terrain, terrain_r, viridis, viridis_r, winter, winter_r


In [35]:
cmap = plt.cm.get_cmap(colormap)(np.linspace(0, 1, number_of_cycles+10))

In [36]:
fig, (ax1, ax2) = plt.subplots(2,1, sharex=True)
for i, frame in enumerate(charge_cycles):
    ax1.plot(frame.Voltage, frame.Charge_Capacity, label=str(i+1), color=cmap[i])
    v, dq = ica.dqdv(frame.Voltage, frame.Charge_Capacity)
    ax2.plot(v, dq, color=cmap[i])
ax1.legend(ncol=3, loc=4, frameon=True)
ax1.set_ylabel("Charge capacity (mAh/g)")
ax2.set_xlabel("Voltage (V vs Li+/Li)")


WARNING:py.warnings:/Users/jepe/miniconda3/envs/cellpy/lib/python3.6/site-packages/scipy/signal/_arraytools.py:45: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
  b = a[a_slice]

Out[36]:
Text(0.5,0,'Voltage (V vs Li+/Li)')

In [ ]:

Trying out HoloViews


In [ ]:


In [43]:
def get_charge_cycles1(step_table, cycle_numbers, mass):
    headers = [
            "Cycle_Index",
            "Step_Time",
            "Voltage",
            "Charge_Capacity",
            #"Discharge_Capacity",
            #"Current"
        ]
    with pd.HDFStore(cellpyfile) as store:
        selectors = _make_selectors("galvanostatic_charge")
        a = _filter_step_table(selectors, step_table, cycle_numbers=cycle_numbers)
        q = _generate_cycle_data_query(a)
        gc = _query_dfdata(store, q)
        gc["Charge_Capacity"] = _convert_to_specific(gc["Charge_Capacity"], mass)
    return gc

In [60]:
def get_charge_cycles2(step_table, cycle_numbers, mass):
    headers = [
            "Cycle_Index",
            "Step_Time",
            "Voltage",
            "Charge_Capacity",
            #"Discharge_Capacity",
            #"Current"
        ]
    gcs = []
    with pd.HDFStore(cellpyfile) as store:
        for cycle_number in cycle_numbers:
            selectors = _make_selectors("galvanostatic_charge")
            a = _filter_step_table(selectors, step_table, cycle_numbers=[cycle_number])
            q = _generate_cycle_data_query(a)
            gc = _query_dfdata(store, q)
            gc["Charge_Capacity"] = _convert_to_specific(gc["Charge_Capacity"], mass)
            gcs.append(gc)
    gcs = pd.concat(gcs)
    return gcs

In [61]:
cycs = list(range(1,18))

In [66]:
%%timeit
c1 = get_charge_cycles1(step_table, cycs, mass)


10 loops, best of 3: 50.9 ms per loop

In [67]:
%%timeit
c2 = get_charge_cycles2(step_table, cycs, mass)


10 loops, best of 3: 140 ms per loop

In [68]:
c1.columns


Out[68]:
Index(['Step_Time', 'Cycle_Index', 'Current', 'Voltage', 'Charge_Capacity',
       'Discharge_Capacity'],
      dtype='object')

In [87]:
# defining key dimensions
cycles = hv.Dataset(c1, ['Cycle_Index', 'Charge_Capacity'])

In [134]:
dqdv = []
cycle_indexes = c1.Cycle_Index.unique()
for c in cycle_indexes:
    frame = c1.loc[c1.Cycle_Index==c]
    v, dq = ica.dqdv(frame.Voltage, frame.Charge_Capacity)
    df = pd.DataFrame(
        dict(
            v=v,
            dq=dq,
        )
    )
    df["Cycle_Index"] = c
    dqdv.append(df)


WARNING:py.warnings:/Users/jepe/miniconda3/envs/cellpy/lib/python3.6/site-packages/scipy/signal/_arraytools.py:45: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
  b = a[a_slice]


In [135]:
dqdv_frame = pd.concat(dqdv)

In [136]:
dqdv_frame.head()


Out[136]:
v dq Cycle_Index
0 0.114219 251.995032 1
1 0.116696 251.995032 1
2 0.119174 251.995032 1
3 0.121652 251.995032 1
4 0.124130 243.500184 1

In [138]:
# defining key dimensions
dqdv = hv.Dataset(dqdv_frame, ['Cycle_Index', 'v'])

In [88]:
print(cycles.groupby('Cycle_Index'))


:HoloMap   [Cycle_Index]
   :Dataset   [Charge_Capacity]   (Step_Time,Current,Voltage,Discharge_Capacity)

In [92]:
%%opts Curve [width=500]
curves = cycles.to(hv.Curve, 'Charge_Capacity', 'Voltage', groupby='Cycle_Index')
curves


Out[92]:

In [105]:
%%opts Scatter [width=500 height=400,] (alpha=0.1)
%%opts Curve (line_width=5)
%%opts NdOverlay [legend_position='left']

ndoverlay = cycles.to(hv.Scatter, 'Charge_Capacity', 'Voltage', groupby='Cycle_Index').overlay()
curves = cycles.to(hv.Curve, 'Charge_Capacity', 'Voltage', groupby='Cycle_Index')
curves * ndoverlay


Out[105]:

In [146]:
%%opts Curve.all [width=500 height=400,] (alpha=0.3)
%%opts Curve.focus (line_width=5)
%%opts NdOverlay [legend_position='left']

dndoverlay = dqdv.to(hv.Curve, 'v', 'dq',  groupby='Cycle_Index', group="all").overlay()
dcurves = dqdv.to(hv.Curve, 'v', 'dq',  groupby='Cycle_Index', group="focus")
dcurves * dndoverlay


Out[146]:

In [ ]: