Closed tlestang closed 3 years ago
couple of points on this:
set_up
.evaluate
needs to be pickled, can we just tell pickle not to include it? The EvaluatorPython
class can instead store a string of the function to be compiled (I think it might already do this), and if the compiled version doesn't exist when EvaluatorPython.evaluate
is called, compile it then.The reason the compiled models are stored in the model and not the solver itself is that we allow the same solver to be used to solve different models, so if the compiled models are stored in the solver then solving a second model would overwrite the first one and require a new setup. As a solution we could either:
solver.models_set_up = {model1: {"rhs": ..., "algebraic":, ...}, model2: {"rhs": ..., "algebraic":, ...}}
Yup, agree that this functionality is useful. It would also be nice to be able to use multiple solvers on the same model. I've run into this a number of times, where I've wanted to swap between different solvers for the same model because they worked better/worse in different areas of parameter space. But I can't easily do this because I need to create a whole new model for each solver.
Your second option, enforcing only one model per solver, might be the safest approach. If we use a dictionary a user could potentially: (a) create a solver and set it up with a model, (b) modify the model in order to create what they believe to be a new model, then (c) setup the same solver with this "new" model. Do you think we need to worry about that?
For the second option, it depends how hashing works - if you change an attribute of a python object (or an attribute of an attribute of an attribute ...), does it change the hash?
We would have to implement the hashing function for a pybamm.Model
, using the __hash__
magic method (or the __repr__
one). So it depends on whether we can create a good hashing function for a model.
Sorry I didn't notice #734 .
there is no particular reason that the compiled evaluate needs to be pickled, can we just tell pickle not to include it?
I skimmed through the documentation for Pickle and it looks to me like this would be possible. I'll read in more details and give it a try.
Otherwise I don't have (yet?) a strong opinion regarding whether compiled modes should be attributes of the solver or model instances. I'm not sure I understand why enforcing one model per solver is the best option if what we want to make/keep possible is solving several models with the same solver or use multiple solvers with one model. @martinjrobins why do you need to create a new model each time you change solver? Isn't solver.set_up
independent of the solver type?
At the moment, you can't use multiple solvers with a model because each solver stores its temporary files (i.e. compiled models) in that model. Plus you need to set the model.convert_to_format
appropriately for the solver you want to use.
It would be nice to be able to do this:
model = create_and_discretise_model()
solvers = [pybamm.CasadiSolver(), pybamm.JaxSolver(), pybamm.ScipySolver()]
[solver.set_up(model) for solver in solvers]
or perhaps this,
model = create_and_discretise_model()
solver_classes = [pybamm.CasadiSolver, pybamm.JaxSolver, pybamm.ScipySolver]
solvers = [solver(model) for solver in solvers]
but as far as I can see you can't as each call to set_up
will clobber the previous solver's compiled models (whether they are compiled via casadi, python or jax)
Fixed by #1298
Description
Instances of
BaseModel
and derived classes cannot be pickled when attributeconvert_to_format
is"python"
. This hampers use of multiprocessing, since it relies on pickling objects for passing them between processes, see #1261 .To reproduce
Details
Function
base_solver.set_up
does several things but in particular is creates theSolverCallable
objects the solver relies on to evaluate the RHS, initial conditions and jacobian.When
model.convert_to_format == "python"
,SolverCallable.__call__
is a wrapper aroundEvaluatorPython.evaluate()
which evaluates the Python code generated inEvaluatorPython.__init__
to translate the expression tree. When anEvaluatorPython
instance is created, a global functionevaluate
is defined by compiling its definition on the fly. This function is then pointed to byEvaluatorPython._evaluate
.According to the docs for Pickle, only functions defined at the top level of a module can be pickled and I think this is the reason why
evaluate
cannot be pickled.Note that
evaluate
is a global function and not a local function inEvaluatorPython.__init__
, i.eprint(self._evaluate)
evaluates to<function evaluate at 0x7fa4fee843a0>
and not<function EvaluatorPython.__init__.<locals>.evaluate 0x7fa4fee843a0>
. However sinceevaluate
is compiled on the fly, I don't thinkPickle
can find the name of the module it is defined in…Minimal example:
Models can be pickled fine when
mode.convert_to_format == "casadi"
, since in this caseSolverCallable.function
calls an instance ofCasadi.Function
, which can pickled.