I noticed a big difference in the time it takes to run two similar simulations in PyBaMM, and I don’t understand why. Here’s what I mean:
Case 1:
This code runs in 12.6 seconds:
import pybamm
options = {"thermal": "lumped"}
model = pybamm.lithium_ion.DFN(options=options)
parameter_values = pybamm.ParameterValues("Chen2020")
experiment = pybamm.Experiment(["Charge at 1 A for 1 second"], period="1 second")
sim = pybamm.Simulation(model, parameter_values=parameter_values, experiment=experiment)
sol = sim.solve()
voltage_entries = sol["Terminal voltage [V]"].entries
voltage = float(voltage_entries[-1])
print(voltage)
def battery(current):
global sol
new_model = model.set_initial_conditions_from(sol, inplace=False)
sim = pybamm.Simulation(new_model, parameter_values=parameter_values, experiment=experiment)
sol = sim.solve()
voltage_entries = sol["Terminal voltage [V]"].entries
voltage = float(voltage_entries[-1])
return voltage
for i in range(10):
current = 1
voltage = battery(current)
print(voltage)
Case 2:
This code, which does the same total charge duration (11 seconds), runs in 1.2 seconds:
import pybamm
options = {"thermal": "lumped"}
model = pybamm.lithium_ion.DFN(options=options)
parameter_values = pybamm.ParameterValues("Chen2020")
experiment = pybamm.Experiment(["Charge at 1 A for 11 seconds"], period="1 second")
sim = pybamm.Simulation(model, parameter_values=parameter_values, experiment=experiment)
sol = sim.solve()
voltage_entries = sol["Terminal voltage [V]"].entries
print(voltage_entries)
Question:
Both codes are charging the battery for 11 seconds. The only difference is that in the first case, I run the simulation in a loop for 1 second each time and update the initial conditions.
Why is the first case much slower? Is there a way to make the loop faster?
it creates an entirely new simulation whose equations must be regenerated from scratch.
A faster alternative is to provide a starting_solution keyword argument to solve:
import pybamm
options = {"thermal": "lumped"}
model = pybamm.lithium_ion.DFN(options=options)
parameter_values = pybamm.ParameterValues("Chen2020")
experiment = pybamm.Experiment(["Charge at 1 A for 1 second"], period="1 second")
sim = pybamm.Simulation(model, parameter_values=parameter_values, experiment=experiment)
def battery2(sim, starting_solution):
sol = sim.solve(starting_solution=starting_solution)
return sol
sol = None
for i in range(11):
sol = battery2(sim, sol)
voltage = sol["Voltage [V]"](sol.t[-1])
print(voltage)
While faster than Case 1, this will still not be as fast as Case 2 because the system of DAEs must be reinitialized from the previous starting point and there is some overhead in calling solve().
Thank you so much for your answer and the time you spent helping me. I really appreciate it!
I realized I missed an important line in my first code example:
experiment = pybamm.Experiment([f"Discharge at {current} A for 1 seconds"], period="1 second")
The main problem is that the current (current) can change during each iteration. Here’s the corrected version of my Case 1 code, where I now define current values explicitly and use them in the loop:
import pybamm
options = {"thermal": "lumped"}
model = pybamm.lithium_ion.DFN(options=options)
parameter_values = pybamm.ParameterValues("Chen2020")
experiment = pybamm.Experiment(["Discharge at 1 A for 1 seconds"], period="1 second")
sim = pybamm.Simulation(model, parameter_values=parameter_values, experiment=experiment)
sol = sim.solve()
voltage_entries = sol["Terminal voltage [V]"].entries
voltage = float(voltage_entries[-1])
print(voltage)
def battery(current):
global sol
new_model = model.set_initial_conditions_from(sol, inplace=False)
experiment = pybamm.Experiment([f"Discharge at {current} A for 1 seconds"], period="1 second")
sim = pybamm.Simulation(new_model, parameter_values=parameter_values, experiment=experiment)
sol = sim.solve()
voltage_entries = sol["Terminal voltage [V]"].entries
voltage = float(voltage_entries[-1])
return voltage
# Explicitly define the currents
currents = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
for i in range(10):
voltage = battery(currents[i])
print(voltage)
In this version, the currents are explicitly defined as a list, and the loop iterates over the first 10 values.
I apologize for not explaining this properly the first time. Your solution really helps a lot! But in this case, is there a way to make the loop faster when the current changes dynamically?
Thank you so much for taking the time to answer my question and write the code for me. Your help was really useful, and I truly appreciate the effort you put into it. Thanks again!
Thank you for your help.
I have noticed some issues while working with sim.step during the battery charge and discharge simulation process:
If the process reaches the lower cut-off voltage or upper cut-off voltage during the simulation, unlike other modes where an out-of-range voltage warning is displayed and the simulation stops, in sim.step, the system seems to freeze. It continues to provide output without any error, but the outputs remain locked at a constant value.
When sim.step is called many times (as in long simulations with small dt), RAM usage increases significantly, even though the sol file size remains relatively small. From my initial analysis, this issue appears to be related to the data recorded by the solver, which, surprisingly, is not cleared by Python’s garbage collection process. This becomes more critical as it gradually slows down the simulation over time.
Each step of the simulation stores the adaptive time stepping points for high accuracy post-processing. To only store the first and final step of each simulation, you can specify the interpolation save points t_interp as
This will help limit your memory usage. We are also working on additional memory bug fixes for the garbage collection issue which are coming soon.
Once you hit the voltage limit, the simulation tries to take a step using the following current but cannot because the voltage limit is actively being violated. You can disable the lower/upper voltage limits by setting them to -np.inf and np.inf. respectively, and/or monitor the voltage within your while loop.
Thank you for the explanation! It really clarified how to limit memory usage with t_interp and was very helpful. By the way, if possible, could you also share an example of how to disable the lower/upper voltage limits by setting them to -np.inf and np.inf?