Getting started with PyProBE#
[1]:
import pyprobe
Convert data to standard format#
Create the cell object and load some data. If this is the first time that the data has been loaded, it must first be converted into the standard format for PyProBE.
[2]:
# Describe the cell. Required fields are 'Name'.
info_dictionary = {'Name': 'Sample cell',
'Chemistry': 'NMC622',
'Nominal Capacity [Ah]': 0.04,
'Cycler number': 1,
'Channel number': 1,}
# Create a cell object
cell = pyprobe.Cell(info=info_dictionary)
data_directory = '../../../tests/sample_data/neware'
# Uncomment if running locally
# cell.process_cycler_file(cycler='neware',
# folder_path=data_directory,
# input_filename='sample_data_neware.xlsx',
# output_filename='sample_data_neware.parquet')
If a parquet file exists alongside the original data file, you can add it as a Procedure object to the procedure dictionary of the cell. The key of the dictionary is the procedure name that you provide.
[3]:
cell.add_procedure(procedure_name='Sample',
folder_path = data_directory,
filename = 'sample_data_neware.parquet')
print(cell.procedure)
{'Sample': Procedure(base_dataframe=<LazyFrame at 0x7F71206C4250>, info={'Name': 'Sample cell', 'Chemistry': 'NMC622', 'Nominal Capacity [Ah]': 0.04, 'Cycler number': 1, 'Channel number': 1, 'color': '#ff00ff'}, column_definitions={'Date': 'The timestamp of the data point. Type: datetime.', 'Time [s]': 'The time passed from the start of the procedure.', 'Step': 'The step number.', 'Cycle': 'The cycle number.', 'Event': 'The event number. Counts the changes in cycles and steps.', 'Current [A]': 'The current through the cell.', 'Voltage [V]': 'The terminal voltage.', 'Capacity [Ah]': 'The net charge passed since the start of the procedure.', 'Temperature [C]': 'The temperature of the cell.', 'Procedure Time [s]': 'Time elapsed since beginning of procedure.', 'Procedure Capacity [Ah]': 'The net charge passed since beginning of procedure.'}, step_descriptions={'Step': [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12], 'Description': ['Rest for 4 hours', 'Charge at 4mA until 4.2 V, Hold at 4.2 V until 0.04 A', 'Rest for 2 hours', 'Discharge at 4 mA until 3 V', 'Rest for 2 hours', 'Charge at 4 mA until 4.2 V, Hold at 4.2 V until 0.04 A', 'Rest for 2 hours', 'Rest for 10 seconds', 'Discharge at 20 mA for 0.2 hours or until 3 V', 'Rest for 30 minutes', 'Rest for 1.5 hours']}, readme_dict={'Initial Charge': {'Steps': [1, 2, 3], 'Step Descriptions': ['Rest for 4 hours', 'Charge at 4mA until 4.2 V, Hold at 4.2 V until 0.04 A', 'Rest for 2 hours'], 'Cycles': []}, 'Break-in Cycles': {'Steps': [4, 5, 6, 7], 'Step Descriptions': ['Discharge at 4 mA until 3 V', 'Rest for 2 hours', 'Charge at 4 mA until 4.2 V, Hold at 4.2 V until 0.04 A', 'Rest for 2 hours'], 'Cycles': [(4, 7, 5)]}, 'Discharge Pulses': {'Steps': [9, 10, 11, 12], 'Step Descriptions': ['Rest for 10 seconds', 'Discharge at 20 mA for 0.2 hours or until 3 V', 'Rest for 30 minutes', 'Rest for 1.5 hours'], 'Cycles': [(9, 12, 10)]}}, cycle_info=[])}
The dashboard can be launched immediately (uncomment to run when outside docs environment):
[4]:
# pyprobe.launch_dashboard([cell])
The raw data is accessible as a dataframe with the data property:
[5]:
print(cell.procedure['Sample'].data)
shape: (789_589, 9)
┌────────────────┬────────────┬──────┬───────┬───┬─────────┬──────────┬────────────────┬───────────┐
│ Date ┆ Time [s] ┆ Step ┆ Event ┆ … ┆ Voltage ┆ Capacity ┆ Procedure Time ┆ Procedure │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ [V] ┆ [Ah] ┆ [s] ┆ Capacity │
│ datetime[μs] ┆ f64 ┆ i64 ┆ i64 ┆ ┆ --- ┆ --- ┆ --- ┆ [Ah] │
│ ┆ ┆ ┆ ┆ ┆ f64 ┆ f64 ┆ f64 ┆ --- │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ f64 │
╞════════════════╪════════════╪══════╪═══════╪═══╪═════════╪══════════╪════════════════╪═══════════╡
│ 2024-02-29 ┆ 0.0 ┆ 2 ┆ 0 ┆ … ┆ 3.7172 ┆ 0.04139 ┆ 0.0 ┆ 0.0 │
│ 09:20:29.094 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2024-02-29 ┆ 1.0 ┆ 2 ┆ 0 ┆ … ┆ 3.7197 ┆ 0.041391 ┆ 1.0 ┆ 0.000001 │
│ 09:20:30.094 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2024-02-29 ┆ 2.0 ┆ 2 ┆ 0 ┆ … ┆ 3.7201 ┆ 0.041392 ┆ 2.0 ┆ 0.000002 │
│ 09:20:31.094 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2024-02-29 ┆ 3.0 ┆ 2 ┆ 0 ┆ … ┆ 3.7205 ┆ 0.041393 ┆ 3.0 ┆ 0.000003 │
│ 09:20:32.094 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2024-02-29 ┆ 4.0 ┆ 2 ┆ 0 ┆ … ┆ 3.7208 ┆ 0.041395 ┆ 4.0 ┆ 0.000005 │
│ 09:20:33.094 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
│ 2024-03-06 ┆ 562745.497 ┆ 12 ┆ 61 ┆ … ┆ 3.4513 ┆ 0.022805 ┆ 562745.497 ┆ -0.018585 │
│ 21:39:34.591 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2024-03-06 ┆ 562746.497 ┆ 12 ┆ 61 ┆ … ┆ 3.4513 ┆ 0.022805 ┆ 562746.497 ┆ -0.018585 │
│ 21:39:35.591 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2024-03-06 ┆ 562747.497 ┆ 12 ┆ 61 ┆ … ┆ 3.4513 ┆ 0.022805 ┆ 562747.497 ┆ -0.018585 │
│ 21:39:36.591 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2024-03-06 ┆ 562748.497 ┆ 12 ┆ 61 ┆ … ┆ 3.4513 ┆ 0.022805 ┆ 562748.497 ┆ -0.018585 │
│ 21:39:37.591 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2024-03-06 ┆ 562749.497 ┆ 12 ┆ 61 ┆ … ┆ 3.4513 ┆ 0.022805 ┆ 562749.497 ┆ -0.018585 │
│ 21:39:38.591 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
└────────────────┴────────────┴──────┴───────┴───┴─────────┴──────────┴────────────────┴───────────┘
Individual columns can be returned as 1D numpy arrays with the get()
method:
[6]:
current = cell.procedure['Sample'].experiment('Break-in Cycles').charge(0).get('Current [A]')
print(type(current), current)
<class 'numpy.ndarray'> [0.00399931 0.00400001 0.00400004 ... 0.00040614 0.0004023 0.0004 ]
Multiple columns can be returned at once:
[7]:
current, voltage = cell.procedure['Sample'].experiment('Break-in Cycles').charge(0).get('Current [A]', 'Voltage [V]')
print("Current = ", current)
print("Voltage = ", voltage)
Current = [0.00399931 0.00400001 0.00400004 ... 0.00040614 0.0004023 0.0004 ]
Voltage = [3.2895 3.2962 3.2979 ... 4.2001 4.2001 4.2001]
And different unit can be returned on command:
[8]:
current_mA = cell.procedure['Sample'].experiment('Break-in Cycles').charge(0).get('Current [mA]')
print("Current [mA] = ", current_mA)
Current [mA] = [3.99931 4.00001 4.00004 ... 0.40614 0.4023 0.4 ]
Any part of the procedure can be plotted quickly using the add_line
method:
[9]:
figure = pyprobe.Plot()
figure.add_line(cell.procedure['Sample'].experiment('Break-in Cycles'), 'Experiment Time [s]', 'Voltage [V]')
figure.show_image()
# figure.show() # This will show the plot interactively, it is commented out for the sake of the documentation
We can use the analysis
to further analyse the data. For the 'Break-in Cycles'
we will use the cycling
analysis module and the functions within. These functions return Result
objects, so they can be interacted with in the same ways as raw data:
[10]:
from pyprobe.analysis import cycling
cycling_summary = cycling.summary(input_data = cell.procedure['Sample'].experiment('Break-in Cycles'))
print(type(cycling_summary))
print(cycling_summary.data)
<class 'pyprobe.result.Result'>
shape: (5, 8)
┌───────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┐
│ Cycle ┆ Capacity ┆ Time [s] ┆ Charge ┆ Discharge ┆ SOH Charge ┆ SOH ┆ Coulombic │
│ --- ┆ Throughput ┆ --- ┆ Capacity ┆ Capacity ┆ [%] ┆ Discharge ┆ Efficiency │
│ i64 ┆ [Ah] ┆ f64 ┆ [Ah] ┆ [Ah] ┆ --- ┆ [%] ┆ --- │
│ ┆ --- ┆ ┆ --- ┆ --- ┆ f64 ┆ --- ┆ f64 │
│ ┆ f64 ┆ ┆ f64 ┆ f64 ┆ ┆ f64 ┆ │
╞═══════╪════════════╪════════════╪════════════╪════════════╪════════════╪════════════╪════════════╡
│ 0 ┆ 0.0 ┆ 28448.2 ┆ 0.041086 ┆ 0.040937 ┆ 100.0 ┆ 100.0 ┆ null │
│ 1 ┆ 0.082022 ┆ 119273.495 ┆ 0.041247 ┆ 0.041138 ┆ 100.393179 ┆ 100.490732 ┆ 1.001267 │
│ 2 ┆ 0.164407 ┆ 210305.197 ┆ 0.04132 ┆ 0.041241 ┆ 100.5711 ┆ 100.743512 ┆ 0.999854 │
│ 3 ┆ 0.246969 ┆ 301386.6 ┆ 0.041362 ┆ 0.041296 ┆ 100.672765 ┆ 100.876766 ┆ 0.999406 │
│ 4 ┆ 0.329627 ┆ 392471.001 ┆ 0.04139 ┆ 0.041329 ┆ 100.740624 ┆ 100.959186 ┆ 0.999212 │
└───────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────┘
And it can be plotted as normal too:
[11]:
figure = pyprobe.Plot()
figure.add_line(cycling_summary,
x = 'Capacity Throughput [Ah]',
y = 'Discharge Capacity [Ah]')
figure.show_image()
# figure.show() # This will show the plot interactively, it is commented out for the sake of the documentation