In [ ]:
In [1]:
import os
import logging
import bokeh
import holoviews as hv
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import ipywidgets as widgets
from ipywidgets import interact, interact_manual
import cellpy
from cellpy import cellreader
from cellpy.utils import ica
%matplotlib inline
hv.extension('bokeh')
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
print(f"pandas: {pd.__version__}")
print(f"cellpy: {cellpy.__version__}")
print(f"holoviews: {hv.__version__}")
my_data = cellreader.CellpyData()
filename = "../../../testdata/hdf5/20160805_test001_45_cc.h5"
assert os.path.isfile(filename)
my_data.load(filename)
my_data.set_mass(0.1)
In [ ]:
comb_ica = ica.dqdv_frames(my_data, split=False, tidy=False)
comb_ica.head()
In [ ]:
cycle_number = 1
ax = comb_ica[cycle_number].plot(x="voltage")
ax.legend([f"cycle {cycle_number}"])
In [96]:
comb_ica_charge, comb_ica_discharge = ica.dqdv_frames(my_data, split=True, tidy=False, )
In [97]:
comb_ica_charge.head()
Out[97]:
In [ ]:
cycle_number = 1
ax = comb_ica_charge[["voltage", cycle_number]].plot(x=("voltage", "v"))
ax.legend([f"charge cycle {cycle_number}"])
ax = comb_ica_discharge[["voltage", cycle_number]].plot(x=("voltage", "v"))
ax.legend([f"discharge cycle {cycle_number}"])
In [ ]:
tidy_ica_charge, tidy_ica_discharge = ica.dqdv_frames(my_data, split=True, tidy=True)
In [ ]:
cycle_number = 1
ax = tidy_ica_charge.loc[tidy_ica_charge.cycle == cycle_number, ["voltage", "dq"]].plot(x="voltage")
ax.legend([f"charge cycle {cycle_number}"])
For fitting, it is most convinient to have individual datasets for charge and discharge. It is also convinient to have the data in a form where we can easily plot all the data to get an overview. And it should be easy to extract single cycles.
In [32]:
tidy_ica_charge, tidy_ica_discharge = ica.dqdv_frames(my_data, split=True, tidy=True)
cycle_number = 1
dq_charge_1 = tidy_ica_charge.loc[tidy_ica_charge.cycle == cycle_number, ["voltage", "dq"]]
dqdv_frames gives a warning if step is not found - make that optional (warnings=False or True)dqdv_frames with split=True vs split=Falsemake_dqdv_frame or retrieve_dqdv_frame
In [ ]:
pd.options.plotting.backend = "matplotlib"
ax_mplib = dq_charge_1.plot(x="voltage", style="-o")
ax_mplib.legend([f"charge cycle {cycle_number}"])
In [ ]:
pd.options.plotting.backend = "hvplot"
ax_hvplot = dq_charge_1.plot(x="voltage") * dq_charge_1.plot(x="voltage", kind="scatter")
# Unfortunately, the df.plot() method returns a hv object with a copy of the dataframe (potential memory leak)
# We therefore chose to use the hv.Curve() etc. methods directly instead.
ax_hvplot
In [33]:
cycle_label = "Cycle 1 (charge)"
value_dims = [
hv.Dimension("dq", label="dQ", unit="mAh/g/V")
] # the y-axis
key_dims = [
hv.Dimension("voltage", label="Voltage", unit="V vs. Li/Li+")
] # the x-axis
z_dims = key_dims + value_dims
v = hv.Scatter(data=dq_charge_1, kdims=key_dims, vdims=value_dims, label=cycle_label).opts(
width=800,
height=400,
marker="o",
size=8,
tools = ['hover']
)
vv = (v * hv.Curve(v)).opts(title="ICA plot")
vv
Out[33]:
In [34]:
import holoviews as hv
from holoviews import opts, streams
from holoviews.plotting.links import DataLink
In [90]:
dq_df = dq_charge_1
cycle_label = "Cycle 1 (charge)"
num_points = 10
value_dims = [
hv.Dimension("dq", label="dQ", unit="mAh/g/V")
]
key_dims = [
hv.Dimension("voltage", label="Voltage", unit="V vs. Li/Li+"),
hv.Dimension("dq", label="dQ", unit="mAh/g/V")
]
max_dq_idx = dq_df["dq"].idxmax()
v0, dq0 = dq_df.iloc[max_dq_idx].values
peaks = pd.DataFrame({"voltage": [v0], "dq": [dq0]})
points = hv.Points(data=peaks, kdims=key_dims).opts(size=26, color="red", alpha=0.3)
v = hv.Scatter(data=dq_df, kdims=key_dims, vdims=value_dims, label=cycle_label).opts(
width=600,
height=400,
marker="o",
size=8,
tools = ['hover']
)
vv = (v * hv.Curve(v)).opts(title="ICA plot")
point_stream = streams.PointDraw(num_objects=num_points, source=points, empty_value='black')
table = hv.Table(points, key_dims)
DataLink(points, table)
(vv * points + table).opts(
opts.Layout(merge_tools=False),
opts.Points(active_tools=['point_draw'], padding=0.1),
opts.Table(editable=True))
Out[90]:
In [91]:
selected_voltage = point_stream.data["voltage"][0]
selected_icap = point_stream.data["dq"][0]
print(f"You selected ({selected_voltage:0.2}, {selected_icap:0.0f})")
In [115]:
comb_ica_charge, comb_ica_discharge = ica.dqdv_frames(my_data, split=True, tidy=False, voltage_resolution=0.02)
In [108]:
# should add option to dqdv_frames to set voltage resolution for interpolation
In [116]:
comb_ica_charge.columns = comb_ica_charge.columns.droplevel(1)
In [118]:
comb_ica_charge.set_index("voltage", drop=True, inplace=True)
In [119]:
comb_ica_charge.head()
Out[119]:
In [100]:
# x-axis: "voltage"
# y-axis: cycle number
# z-axis (intensity): dq
In [294]:
img = hv.Image(
comb_ica_charge.values[::-1], bounds=(1, 0.1, 17, 1.1)
).opts(
#invert_xaxis=True,
#invert_yaxis=True,
xlabel="cycle",
ylabel="voltage",
ylim=(0.2, 1.0)
)
img
Out[294]:
In [ ]:
In [262]:
fig, ax = plt.subplots()
fig.set_figwidth(4)
fig.set_figheight(4)
ax.imshow(comb_ica_charge.T,
#aspect=0.1,
aspect="auto",
interpolation="nearest",
origin='lower',
extent=(0.1, 1, 1.2, 16),
)
ax.set_xlim((0.2, 0.8))
ax.set_xlabel("Voltage (V)")
ax.set_ylabel("Cycle number")
print()