pybamm-team / PyBaMM

Fast and flexible physics-based battery models in Python
https://www.pybamm.org/
BSD 3-Clause "New" or "Revised" License
1.05k stars 524 forks source link

Something a bit funny about ParameterValues Class #792

Closed TomTranter closed 2 years ago

TomTranter commented 4 years ago

Describe the bug I am parallel processing a bunch of SPMs and have it set up with ProcessPoolExecutor. On Linux is all works fine and I am able to step a simulation and pass back a solution in parallel. On Windows there is an issue with ParameterValues where it is not able to find the module definition of certain functions. The code below will reproduce the master and contains snippets from my ecm code that should be self contained. In this example I am not even using the parameters and the script works if you pass None (normally I would want to use them to non-dimensionalize a temperature)

To Reproduce

import pybamm
from concurrent.futures import ProcessPoolExecutor
import os
import numpy as np

def current_function(t):
    return pybamm.InputParameter("Current")

def make_spm(I_typical, thermal=True):
    if thermal:
        model_options = {
                "thermal": "x-lumped",
                "external submodels": ["thermal"],
            }
        model = pybamm.lithium_ion.SPM(model_options)
    else:
        model = pybamm.lithium_ion.SPM()
    geometry = model.default_geometry
    param = model.default_parameter_values
    param.update(
        {
            "Typical current [A]": I_typical,
            "Current function [A]": current_function,
            "Current": "[input]",
            "Electrode height [m]": "[input]",
        }
    )
    param.process_model(model)
    param.process_geometry(geometry)
    var = pybamm.standard_spatial_vars
    var_pts = {var.x_n: 5, var.x_s: 5, var.x_p: 5, var.r_n: 10, var.r_p: 10}
    spatial_methods = model.default_spatial_methods
    solver = pybamm.CasadiSolver()
#    solver = model.default_solver
    sim = pybamm.Simulation(
        model=model,
        geometry=geometry,
        parameter_values=param,
        var_pts=var_pts,
        spatial_methods=spatial_methods,
        solver=solver,
    )
    sim.build(False)
    return sim

def convert_temperature(sim, T_dim, inputs):
    temp_parms = sim.model.submodels["thermal"].param
    param = sim.parameter_values
    Delta_T = param.process_symbol(temp_parms.Delta_T).evaluate(u=inputs)
    T_ref = sim.parameter_values.process_symbol(temp_parms.T_ref).evaluate(u=inputs)
    return (T_dim - T_ref) / Delta_T

def step_spm(zipped):
    built_model, solver, param, solution, I_app, e_height, dt, T_av, dead = zipped
    inputs = {"Current": I_app,
              'Electrode height [m]': e_height}
#    T_av_non_dim = convert_temperature(sim, T_av, inputs)
    T_av_non_dim = 0.0
    if len(built_model.external_variables) > 0:
        external_variables = {"X-averaged cell temperature": T_av_non_dim}
    else:
        external_variables = None
    if ~dead:
#        print(inputs)
        if solution is not None:
            solved_len = solver.y0.shape[0]
            solver.y0 = solution.y[:solved_len, -1]
            solver.t = solution.t[-1]
        solution = solver.step(
            built_model, dt, external_variables=external_variables, inputs=inputs
        )

    return solution

def pool_func(inputs):
    model, solver, param, dt = inputs
    solution = step_spm((model, solver, param, None,
                         1.0, 1.0, dt, 303, False))
    return solution

def main():
    pass_param = False
    Nspm = 10
    max_workers = int(os.cpu_count() / 2)
    pool = ProcessPoolExecutor(max_workers=max_workers)
    sim = make_spm(I_typical=1.0, thermal=False)
    models = [sim.built_model for i in range(Nspm)]
    solvers = [sim.solver for i in range(Nspm)]
    if pass_param:
        params = [sim.parameter_values for i in range(Nspm)]
    else:
        params = [None for i in range(Nspm)]
    time_steps = np.linspace(0.1, 1, Nspm)*1e-6
    solutions = list(pool.map(pool_func, zip(models, solvers, params, time_steps)))
    print([sol.t[-1] for sol in solutions])
    pool.shutdown()
    del pool

if __name__ == '__main__':
    main()

Expected behaviour Changing pass_param to True will result in a BrokenProcessPool If you run the script from the command line you get more info. ModuleNotFoundError: No module named 'graphite_mcmb2528_diffusivity_Dualfoil1998'

valentinsulzer commented 3 years ago

Is this still an issue? Why do you need to pass the ParameterValues around after you have set the parameters?

valentinsulzer commented 2 years ago

Not sure if this is still an issue. Closing for now