General Idea
I would like to call PyBaMM from MATLAB and advance the simulation by one time step in each function call using the step()
method (sim.step()
). During this process, I want to adapt certain input values such as current, temperature, and time step, and return some values such as voltage in each time step to MATLAB. The overall purpose is to use PyBaMM for a control application in an existing MATLAB/Simulink environment.
To ensure that the polarization remains intact between function calls, I also want to return the entire solution object to MATLAB and then pass it back to PyBaMM in the next function call. This will allow me to use the solution object for re-initialization of the model, and the solution for the next time step will be appended to the existing solution object.
Issues
I have attempted this setup in two different versions (Version 1 and Version 2), as shown in the attached example, but I face two overall issues:
Issue 1: Handling of intervals from stepping the solver resulting in double entries for all time steps
-
Version 1: Call Python function for PyBaMM step from MATLAB sub-function
-
Time steps:
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]
-
Version 2: Call Python function for PyBaMM step from Python file
-
Time steps:
[0, 1, 2, 3, 4, 5]
When calling the same functions (from MATLAB or PyBaMM), appending the interval from the last time step results in double entries for Version 1 and single entries (as desired) for Version 2. How can I avoid this for Version 1 when calling the PyBaMM function from MATLAB? Why is the handling of appending the new values from one step to the solution object different between MATLAB and Python when looping through the same function?
Issue 2: Access Solution Variables
The second and more important issue is accessing some variables from the solution object after a single overall time step or after all time steps have been performed.
It seems that when returning the solution object to MATLAB and passing it back to Python, the functionality to create output variables just in time, such as solution["Voltage [V].entries"]
, is compromised.
In my example, I export the final solution object to a pickle file and read it in another Python file to allow me to access it in Python and debug it (Step2_reload_solution.py). This is just a workaround to access the solution object.
My question is: What happens to the solution object when it’s passed between MATLAB and Python, and how does this functionality work in general? When debugging the solution object, I get a traceback error for the relevant variables in the solution object.
Structure of the Example
To recreate the problem, run Step1_main_sim_pybamm.m, which calls the MATLAB function run_pybamm_step_V1.m for Version 1 and run_pybamm_step_V2.py for Version 2. Both versions call the run_single_time_step_simulation function in pybamm_model.py. After looping through a few steps, the solution objects are saved to pickle files, which are then imported in Step2_reload_solution.py for post-processing.
Alternative Approaches
Besides fixing the issues in the examples, I am wondering about alternative approaches to the problem. One obvious choice would be to create all relevant variables within the PyBaMM function and return them directly to MATLAB, then appending the solution array for each variable in each time step. While I might do that in some way, I still would like to understand how I can properly serialize the solution object for any further use.
Do you have any other ideas, such as keeping the PyBaMM simulation running while adapting the input variables or using closures in python? Any help on the example above, as well as alternative approaches, would be much appreciated!
%Step1_main_sim_pybamm.m
close all
clear
clc
%% Version 1: call python function for pybamm step from matlab subfunction
% Define the step size and total number of steps for the simulation
t_step = 1.0;
total_steps = 5;
% Initialize the previous_solution to empty since we're starting a new simulation
previous_solution = [];
% Run the simulation loop
for step = 1:total_steps
% Run a single simulation step
updated_solution = run_pybamm_step_V1(previous_solution,t_step);
previous_solution=updated_solution;
end
% Use the PyBaMM solution object's save method to save it as a pickle file
pybamm = py.importlib.import_module('pybamm');
previous_solution.save('solution_1.pkl');
%% Version 2: call python function for pybamm step from pythonfile
py_filename = "run_pybamm_step_V2.py";
solution = pyrunfile(py_filename,"solution"); % retrun "solution" from python function
% Use the PyBaMM solution object's save method to save it as a pickle file
solution.save('solution_2.pkl');
disp('script completed')
%run_pybamm_step_V1.m
function solution = run_pybamm_step_V1(previous_solution, current_step_size)
% Import the Python simulation function
pybamm_model = py.importlib.import_module('pybamm_model');
py.importlib.reload(pybamm_model);
% If starting from scratch, create the initial solution
if isempty(previous_solution)
% Cast None to a Python type
previous_solution = py.None;
end
% Convert current_step_size to Python float
step_size_py = py.float(current_step_size);
% Call the Python function to perform a single time step simulation
solution= pybamm_model.run_single_time_step_simulation(previous_solution, step_size_py);
end
#run_pybamm_step_V2.py
import pybamm
from pybamm_model import run_single_time_step_simulation
# Set up the initial conditions
solution = None
# Define the time step
time_step = 1 # time step in seconds
total_steps = 5;
for i in range(total_steps):
# Perform a single time step simulation
updated_solution = run_single_time_step_simulation(solution, time_step)
solution=updated_solution #solution is passed to matlab Step1_main_sim_pybamm
# pybamm_model.py
import pybamm
def run_single_time_step_simulation(previous_solution, dt):
model = pybamm.lithium_ion.DFN()
# Initialize new simulation
if previous_solution is None:
sim = pybamm.Simulation(model)
sim.step(dt=dt)
# Initialize the simulation with the previous solution, if provided
else:
model = model.set_initial_conditions_from(previous_solution, inplace=False)
sim = pybamm.Simulation(model)
# Step the simulation by one time step
sim.step(dt=dt,save=True,starting_solution=previous_solution)
solution=sim.solution
return solution
# Step2_reload_solution.py
import os
import pybamm
# Change the current working directory to the script's directory
script_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(script_dir)
#reload the pickle files to check
# Version 1
solution_1_rl = pybamm.load('solution_1.pkl')
# Version 2
solution_2_rl = pybamm.load('solution_2.pkl')
#ISSUE 1: Handling of interval from stepping the solver with Version 1 and Version 2
print(solution_1_rl.t)
print(solution_2_rl.t)
#ISSUE 2: access Solution Varibales
voltage_1=solution_1_rl['Voltage [V]'].entries
voltage_2=solution_2_rl['Voltage [V]'].entries
print('voltage_1')
print('voltage_2')
print('success')