Capacity of simulation and experimental plot not matching

Hii i am trying to simulate capacity and match it with experimental data. But there is huge difference in plot. What change can be done to match both the plot .

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

Part 1: PyBaMM Simulation

model = pybamm.lithium_ion.SPMe({“SEI”: “solvent-diffusion limited”})
param = pybamm.ParameterValues(“OKane2022”)

Update the nominal cell capacity to 3.2 A.h

param.update({“Nominal cell capacity [A.h]”: 3.2})

experiment = pybamm.Experiment(
[
“Discharge at 0.5C until 2.95V”,
“Charge at 0.5C until 4.15V”,
“Hold at 4.15V until 50mA”,
“Rest for 10 minutes”,
] * 1500 # Use fewer cycles for demonstration; increase as needed
)

sim = pybamm.Simulation(model, parameter_values=param, experiment=experiment)

Solve the simulation

solution = sim.solve()

Extract time, current, and discharge capacity data

time = solution[“Time [s]”].data
current = solution[“Current [A]”].data
discharge_capacity = solution[“Discharge capacity [A.h]”].data

Identify the end of each discharge step

discharge_capacities =
cycle_numbers =
for i in range(1, len(time)):
# Check for the end of discharge: current transitions to zero or positive
if current[i] > 0 and current[i - 1] <= 0: # Discharge phase ends when current becomes positive
discharge_capacities.append(discharge_capacity[i - 1]) # Record capacity at the end of discharge
cycle_numbers.append(len(discharge_capacities)) # Cycle number corresponds to the index

Part 2: Excel Data Processing

Full path to your file

file_path = r’C:\Users\RawatShubham\PycharmProjects\pythonProject\VS_LG_MH1_Nilfisk0.5C_data_1.xlsx’ # Update with the correct file name and extension
sheet_name = ‘Tabelle1’ # Replace with the name of your desired sheet

Debug: Check if the file exists

if not os.path.exists(file_path):
print(f"File not found: {file_path}")
exit()

Read the specific sheet

data = pd.read_excel(file_path, sheet_name=sheet_name)

Extract Cycle No. and Capacity for VS18-191

cycle_no = data[‘Cycle No.’]
capacity_vs18_191 = data[‘Capacity Ah VS18-191’]

Part 3: Combined Plot

plt.figure(figsize=(10, 6))

Plot PyBaMM simulation data

plt.plot(cycle_numbers, discharge_capacities, ‘ro-’, label=“PyBaMM Simulation”)

Plot Excel data

plt.plot(cycle_no, capacity_vs18_191, ‘bo-’, label=“VS18-191”)

Add labels, title, and legend

plt.xlabel(‘Cycle Number’)
plt.ylabel(‘Capacity (Ah)’)
plt.title(‘Capacity vs Cycle Number: PyBaMM Simulation and Experimental Data’)
plt.legend()
plt.grid(True)

Show the plot

plt.show()

Hi Shubham,

Your Python code is good, but there are some conventions in PyBaMM that are different to other battery software, and cause confusion sometimes!

Firstly, in PyBaMM, discharge current is defined to be positive, and charge current to be negative, which is the opposite convention to some battery software products. So what you are actually plotting is the end of charge, not end of discharge!

Secondly, updating the parameter "Nominal cell capacity [A.h]" does not change the capacity of the cell. What that parameter is used for is to define the current that constitutes 1C. So by changing the nominal capacity to 3.2 A.h, 1C becomes 3.2 A. To change the capacity without altering any other parameters, multiply the parameter "Electrode width [m]" by 3.2/5:

param.update({
    "Nominal cell capacity [A.h]": 3.2,
    "Electrode width [m]": 1.58 * 3.2 /5,
})

Changing the electrode width changes how much material is in the cell, and therefore the capacity.

Thirdly, the cell capacity is not degrading because you did not enable any degradation mechanisms in the model. This notebook shows you how to do this. The experimental plot is consistent with the "solvent-diffusion limited" SEI model, so start with that. You may need to adjust the parameter "SEI solvent diffusivity [m2.s-1]" to get the correct rate of degradation.

Once you have made these changes, the starting capacities should match and the cell should degrade. Good luck!

Simon

1 Like

Dear @OKane,

Thank you for your reply.

I want to mention i had the same error and it worked when i scale up the widght.

However i have noticed that the voltage is got shifted up after when the number of cycles got increased. i thought it’s because of the aging branch i have added but when i comment it out the issue still there.

could you please give me an explanation to this.

Hii Simon , thankyou for your reply. It is really helpful for me. Now this is the updated plot I am getting.

and here is the code I am using
import pybamm
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import os
model = pybamm.lithium_ion.SPMe(
{
“SEI”: “solvent-diffusion limited”,
“SEI porosity change”: “true”,
“lithium plating”: “partially reversible”,
“lithium plating porosity change”: “true”, # alias for “SEI porosity change”
“particle mechanics”: (“swelling and cracking”, “swelling only”),
“SEI on cracks”: “true”,
“loss of active material”: “stress-driven”,
“calculate discharge energy”: “true”, # for compatibility with older PyBaMM versions3
}
)
param = pybamm.ParameterValues(“OKane2022”)

#this change is done

param.update({
“Nominal cell capacity [A.h]”: 2.87,
“Electrode width [m]”: 1.58 * 2.87 / 4.85,
“Negative electrode LAM constant proportional term [s-1]”: 1e-07,
“Open-circuit voltage at 0% SOC [V]”: 2.95,
“Open-circuit voltage at 100% SOC [V]”: 4.15,
“Lower voltage cut-off [V]”: 2.95,
“Upper voltage cut-off [V]”: 4.15,
“Lithium metal partial molar volume [m3.mol-1]”: 5e-05, # 1.3e-05,

})
param.update({“SEI solvent diffusivity [m2.s-1]”:1e-21 }, check_already_exists=False) #2.5e-22
experiment = pybamm.Experiment(
[
“Discharge at 0.5C until 2.95V”,
“Charge at 0.5C until 4.15V”,
“Hold at 4.15V until 50mA”,
“Rest for 10 minutes”,
] * 10 # Use fewer cycles for demonstration; increase as needed
)

sim = pybamm.Simulation(model, parameter_values=param, experiment=experiment)

solution = sim.solve()

time = solution[“Time [s]”].data
current = solution[“Current [A]”].data
discharge_capacity = solution[“Discharge capacity [A.h]”].data

##this change is done
discharge_capacities =
cycle_numbers =
for i in range(1, len(time)):
# Check for the end of discharge: current transitions to a negative value
if current[i] < 0 and current[i - 1] >= 0: # Discharge phase ends when current becomes negative
discharge_capacities.append(discharge_capacity[i - 1]) # Record capacity at the end of discharge
cycle_numbers.append(len(discharge_capacities)) # Cycle number corresponds to the index

file_path = r’C:\Users\RawatShubham\PycharmProjects\pythonProject\VS_LG_MH1_Nilfisk0.5C_data_1.xlsx’ # Update with the correct file name and extension
sheet_name = ‘Tabelle1’ # Replace with the name of your desired sheet

if not os.path.exists(file_path):
print(f"File not found: {file_path}")
exit()

data = pd.read_excel(file_path, sheet_name=sheet_name)

cycle_no = data[‘Cycle No.’]
capacity_vs18_191 = data[‘Capacity Ah VS18-191’]

plt.figure(figsize=(10, 6))

plt.plot(cycle_numbers, discharge_capacities, ‘ro-’, label=“PyBaMM Simulation”)

plt.plot(cycle_no, capacity_vs18_191, ‘bo-’, label=“VS18-191 Experimantal data”)

plt.xlabel(‘Cycle Number’)
plt.ylabel(‘Capacity (Ah)’)
plt.title(‘Capacity vs Cycle Number: PyBaMM Simulation and Experimental Data’)
plt.legend()
plt.grid(True)
plt.show()

#I am using Okane parameter. and the experimental data is for Lg 18650 Mh1 cell.
Question: What further changes can I do now , to match the simulation and experimental data?

@shubh Did you face this issue?

Sorry ,Actaully i dont have Voltage experimental data. So it will be not possible for me .
Can you give me input about my question (asked above)?

Actually it has nothing to do with the experimental data it’s only about the simulated voltage, if you can see from the attached figure the more cycles got repeated the simulated voltage gets deviated and decreased even without adding aging or thermal behavior, as long as you also did the same can you please check your voltage response and tell me if you faced the same thing…

When comparing experimental data with simulated data, there’s often a mismatch in the time points, which makes direct plotting and comparison difficult. To address this, you need to do interpolation to match the time points, make sure also to define the right initial soc; this is just for comparison. then you need to optimize the model parameters to match your experimental output, and here you can use PyBop.

can you send me your code?

import pybamm
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error

Set logging level

pybamm.set_logging_level(“WARNING”)

Create model

model = pybamm.lithium_ion.DFN(
options={
“cell geometry”: “pouch”,
}
)

Create parameter set

parameter_values = pybamm.ParameterValues(“OKane2022”)

Calculate total width

parameter_values.update({
# Cell geometry

    "Nominal cell capacity [A.h]": 65,
    "Electrode width [m]": 1.58 * 62/5,
    "Current function [A]": 65.0,

})

Import drive cycle

drive_cycle = pd.read_csv(
r"C:\Users\OO000009\Desktop------.csv",
comment=“#”,
header=None
).to_numpy()
drive_cycle[:, 1] = -drive_cycle[:, 1]

Load experimental data

experimental_data = pd.read_csv(r"C:\Users\OO000009\Desktop-----.csv")
exp_time_s = experimental_data[“Total Time (Seconds)”]
exp_voltage_mV = experimental_data[“Voltage (mV)”]

Convert voltage from mV to V

exp_voltage_V = exp_voltage_mV / 1000

Skip the first 3200 rows and reset time to start from 0

exp_time_s = exp_time_s.iloc[3200:].reset_index(drop=True)
exp_voltage_V = exp_voltage_V.iloc[3200:].reset_index(drop=True)
exp_time_s = exp_time_s - exp_time_s.iloc[0] # Reset time to start from 0

Create drive cycle experiment

def create_experiment(num_cycles):
# Create drive cycle step using the current function
drive_cycle_step = pybamm.step.current(drive_cycle)

# Create cycling experiment
cycle_steps = [
    (drive_cycle_step,)  # Add rest between cycles
] * num_cycles

return pybamm.Experiment(cycle_steps)

Use drive cycle experiment

experiment = create_experiment(num_cycles=100)

Solver settings

solver = pybamm.CasadiSolver(
mode=“safe”,
atol=1e-6, # Stricter tolerance
rtol=1e-6,
dt_max=600, # Smaller timestep
)

Create and run simulation

sim = pybamm.Simulation(
model,
parameter_values=parameter_values,
experiment=experiment,
solver=solver
)

print(“Starting simulation…”)
solution = sim.solve(initial_soc=0.3)

Extract data for plotting

voltage = solution[“Terminal voltage [V]”].entries
time = solution[“Time [s]”].entries

Interpolate experimental data to match simulation time points

exp_voltage_interp = np.interp(time, exp_time_s, exp_voltage_V)

Calculate RMSE between simulated and experimental voltage

rmse = np.sqrt(mean_squared_error(exp_voltage_interp, voltage))
print(f"Root Mean Squared Error (RMSE) between simulated and experimental voltage: {rmse:.4f} V")

Plot voltage comparison

plt.figure(figsize=(12, 6))
plt.plot(time, voltage, label=“Simulated Voltage”, color=“royalblue”, linewidth=2.5)
plt.plot(time, exp_voltage_interp, label=“Experimental Voltage”, color=“darkorange”, linewidth=2.5)
plt.xlabel(“Time [s]”)
plt.ylabel(“Voltage [V]”)
plt.title(“Voltage Comparison”)
plt.legend()
plt.grid(True)
plt.show()

Track capacity over cycles

cycle_numbers =
capacities =
capacity_losses =

Check if capacity data exists in solution summary variables

if “Capacity [A.h]” in solution.summary_variables:
initial_capacity = solution.summary_variables[“Capacity [A.h]”][0]
capacities = solution.summary_variables[“Capacity [A.h]”]
capacity_losses = ((initial_capacity - np.array(capacities)) / initial_capacity) * 100

# Print capacity values for each cycle
print("Cycle-wise capacities and capacity losses:")
for cycle_number, (capacity, capacity_loss) in enumerate(zip(capacities, capacity_losses), start=1):
    print(f"Cycle {cycle_number}: Capacity = {capacity:.2f} Ah, Capacity Loss = {capacity_loss:.2f} %")

# Plot results
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10))

# Capacity plot
ax1.plot(range(1, len(capacities) + 1), capacities, 'b-o')
ax1.set_xlabel('Cycle Number')
ax1.set_ylabel('Capacity [Ah]')
ax1.grid(True)
ax1.set_title('Capacity vs Cycle Number')

# Capacity loss plot
ax2.plot(range(1, len(capacities) + 1), capacity_losses, 'r-o')
ax2.set_xlabel('Cycle Number')
ax2.set_ylabel('Capacity Loss [%]')
ax2.grid(True)
ax2.set_title('Capacity Loss vs Cycle Number')

plt.tight_layout()
plt.show()

else:
print(“Capacity data is not available in the solution’s summary variables.”)

# Calculate RMSE between simulated and experimental voltage

rmse = np.sqrt(mean_squared_error(exp_voltage_interp, voltage))
print(f"Root Mean Squared Error (RMSE) between simulated and experimental voltage: {rmse:.4f} V")

Hii, i have plotted Terminal voltage for my code (all degradation mechanism are turned off) and here is the result ,


After zoom in it look something like this

in this plot voltage high and low are same in every cycle.

1 Like

DO you have an idea why this happened in my case?

Sorry . I dont get it why your simulation plot is behaving differently. U can wait for @DrSOKane response.

Hi Shubam, your value `“SEI solvent diffusivity [m2.s-1]”’ is far too low. Try increasing it. It will take a lot of trial and error. Good luck!

Hi @Notslytherin1, I haven’t worked with drive cycles personally. Have you tried changing the voltage limits? Try this:

parameter_values.update({
    "Upper voltage cut-off [V]": 4.1,
    "Lower voltage cut-off [V]": 2.9,
})

Hi @DrSOKane,
I have tried it now but the issue still there. Do you think it’s just related to the use of the drive-cycle itself and not other parameters?
Thank you in adavance,

No, I think it’s the other parameters. The OKane2022 parameters apply to a graphite+SiOx/NCM811 cell, and may not work for other chemistries. What happens if you plot a simulated vs experimental slow discharge curve? Do they match?

Hii @DrSOKane . I have tried to change “SEI solvent diffusivity [m2.s-1]” between 2.5e-5 and 2.5e-50. But the plot remain same.


What further improvement can i do?
Also i want to ask, I am simulating For LG 18650 MH1 cell. Is Okane2022 parameter set is compatible to use for this cell?

Hi Shubham, I don’t have an immediate answer but I can suggest a few ways to save computational time.

Set "calculate discharge energy" to false. It was put in the example notebook during an older version of PyBaMM and is not needed now.

Use only the degradation models that could explain the behaviour in the experiments. The cracking model is not consistent with the experimental data, so you don’t need it.

If you look back at the example notebook, you’ll see I reduced the number of points in the x dimension to 5, to speed up the code. WIthout the cracking model, you can reduce the number of points in the r dimension to 20, which will also help.

At first glance, the data looks like it could be explained with my student Ruihe Li’s SEI + Dry-out model. Have a look at the preprint and the code.

@DrSOKane can you please share link of this example notebook?