From 5ed9dee4ba20f82c3bea3b071701f0d78b139fe4 Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Mon, 28 Oct 2019 12:46:22 +0000 Subject: [PATCH 01/16] #688 created outline of sim class --- examples/scripts/run_simulation.py | 6 ++ pybamm/__init__.py | 3 +- pybamm/simulation.py | 99 ++++++++++++++++++++++++++++ tests/integration/test_simulation.py | 18 +++++ tests/unit/test_simulation.py | 12 ++++ 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 examples/scripts/run_simulation.py create mode 100644 pybamm/simulation.py create mode 100644 tests/integration/test_simulation.py create mode 100644 tests/unit/test_simulation.py diff --git a/examples/scripts/run_simulation.py b/examples/scripts/run_simulation.py new file mode 100644 index 0000000000..ecb56d656d --- /dev/null +++ b/examples/scripts/run_simulation.py @@ -0,0 +1,6 @@ +import pybamm + +model = pybamm.lithium_ion.SPM() +sim = pybamm.Simulation(model) +sim.solve() +sim.plot() diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 482d6c9476..6ec3e0ccaf 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -265,7 +265,6 @@ def version(formatted=False): from .solvers.algebraic_solver import AlgebraicSolver from .solvers.idaklu_solver import IDAKLU, have_idaklu - # # Current profiles # @@ -282,6 +281,8 @@ def version(formatted=False): from .processed_variable import post_process_variables, ProcessedVariable from .quick_plot import QuickPlot, ax_min, ax_max +from .simulation import Simulation + # # Remove any imported modules, so we don't expose them as part of pybamm # diff --git a/pybamm/simulation.py b/pybamm/simulation.py new file mode 100644 index 0000000000..a995bca026 --- /dev/null +++ b/pybamm/simulation.py @@ -0,0 +1,99 @@ +import pybamm +import numpy as np + + +class Simulation: + def __init__(self, model): + + self._model = model + self._geometry = model.default_geometry + self._parameter_values = model.default_parameter_values + self._submesh_types = model.default_submesh_types + self._var_pts = model.default_var_pts + self._spatial_methods = model.default_spatial_methods + self._solver = model.default_solver + self._quick_plot_vars = None + + @property + def model(self): + return self._model + + @property + def geometry(self): + return self._geometry + + @geometry.setter + def geometry(self, geometry): + self._geometry = geometry + + @property + def parameter_values(self): + return self.parameter_values + + @parameter_values.setter + def parameter_values(self, parameter_values): + self._parameter_values = parameter_values + + @property + def submesh_types(self): + return self._submesh_types + + @submesh_types.setter + def submesh_types(self, submesh_types): + # check of correct form + self._submesh_types = submesh_types + + @property + def var_pts(self): + return self._var_pts + + @var_pts.setter + def var_pts(self, var_pts): + self._var_pts = var_pts + + @property + def spatial_methods(self): + return self._spatial_methods + + @spatial_methods.setter + def spatial_methods(self, spatial_methods): + self._spatial_methods = spatial_methods + + @property + def solver(self): + return self._solver + + @solver.setter + def solver(self, solver): + self._solver = solver + + @property + def quick_plot_vars(self): + return self._quick_plot_vars + + @quick_plot_vars.setter + def quick_plot_vars(self, quick_plot_vars): + self._quick_plot_vars = quick_plot_vars + + @property + def solution(self): + return self._solution + + def solve(self, t_eval=None): + self._parameter_values.process_model(self._model) + self._parameter_values.process_geometry(self._geometry) + self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts) + self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods) + self._disc.process_model(self._model) + + if t_eval is None: + t_eval = np.linspace(0, 1, 100) + + self._solution = self.solver.solve(self._model, t_eval) + + def plot(self): + plot = pybamm.QuickPlot( + self._model, self._mesh, self._solution, self._quick_plot_vars + ) + plot.dynamic_plot() + diff --git a/tests/integration/test_simulation.py b/tests/integration/test_simulation.py new file mode 100644 index 0000000000..a3632f21bb --- /dev/null +++ b/tests/integration/test_simulation.py @@ -0,0 +1,18 @@ +import pybamm +import unittest + + +class TestSimulation(unittest.TestCase): + def test_run_with_spm(self): + model = pybamm.lithium_ion.SPM() + sim = pybamm.Simulation(model) + sim.solve() + + +if __name__ == "__main__": + print("Add -v for more debug output") + import sys + + if "-v" in sys.argv: + debug = True + unittest.main() diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py new file mode 100644 index 0000000000..605913560d --- /dev/null +++ b/tests/unit/test_simulation.py @@ -0,0 +1,12 @@ +import pybamm +import unittest + + +class TestSimulation(unittest.TestCase): + def test_set_model(self): + + model = pybamm.lithium_ion.SPM() + sim = pybamm.Simulation(model) + + self.assertEqual(sim.model, model) + From 7bdb2f935b62067dbd4b15e86d48cdf114dc879e Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Mon, 28 Oct 2019 18:22:03 +0000 Subject: [PATCH 02/16] #688 refined simulation class to allow for easy reseting of properties --- .../models/compare-lithium-ion.ipynb | 2 +- examples/notebooks/parameter-values.ipynb | 2 +- pybamm/simulation.py | 156 ++++++++++++------ tests/integration/test_simulation.py | 6 + tests/unit/test_simulation.py | 9 + 5 files changed, 127 insertions(+), 48 deletions(-) diff --git a/examples/notebooks/models/compare-lithium-ion.ipynb b/examples/notebooks/models/compare-lithium-ion.ipynb index 718009401b..076b1eb468 100644 --- a/examples/notebooks/models/compare-lithium-ion.ipynb +++ b/examples/notebooks/models/compare-lithium-ion.ipynb @@ -463,7 +463,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.7.3" } }, "nbformat": 4, diff --git a/examples/notebooks/parameter-values.ipynb b/examples/notebooks/parameter-values.ipynb index 1a2e2b798a..0ece138286 100644 --- a/examples/notebooks/parameter-values.ipynb +++ b/examples/notebooks/parameter-values.ipynb @@ -482,7 +482,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.7.3" } }, "nbformat": 4, diff --git a/pybamm/simulation.py b/pybamm/simulation.py index a995bca026..3164adc62a 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -1,23 +1,80 @@ import pybamm import numpy as np +import copy class Simulation: def __init__(self, model): - - self._model = model - self._geometry = model.default_geometry - self._parameter_values = model.default_parameter_values - self._submesh_types = model.default_submesh_types - self._var_pts = model.default_var_pts - self._spatial_methods = model.default_spatial_methods - self._solver = model.default_solver + self.model = model + self.set_defaults() + self.reset() + + def set_defaults(self): + self.geometry = self._model.default_geometry + + self._parameter_values = self._model.default_parameter_values + self._submesh_types = self._model.default_submesh_types + self._var_pts = self._model.default_var_pts + self._spatial_methods = self._model.default_spatial_methods + self._solver = self._model.default_solver self._quick_plot_vars = None + def reset(self): + self.model = self._model_class(self._model_options) + self.geometry = copy.deepcopy(self._unprocessed_geometry) + self._mesh = None + self._discretization = None + self._solution = None + self._status = "Unprocessed" + + def parameterize(self): + if self._status == "Unprocessed": + self._parameter_values.process_model(self._model) + self._parameter_values.process_geometry(self._geometry) + self._status = "Parameterized" + elif self._status == "Built": + # There is a function to update parameters in the model + # but this misses some geometric parameters. This class + # is for convenience and not speed so just re-build. + self.reset() + self.build() + + def build(self): + if self._status != "Built": + self.parameterize() + self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts) + self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods) + self._disc.process_model(self._model) + self._model_status = "Built" + + def solve(self, t_eval=None): + self.build() + + if t_eval is None: + t_eval = np.linspace(0, 1, 100) + + self._solution = self.solver.solve(self._model, t_eval) + + def plot(self): + plot = pybamm.QuickPlot( + self._model, self._mesh, self._solution, self._quick_plot_vars + ) + plot.dynamic_plot() + @property def model(self): return self._model + @model.setter + def model(self, model): + self._model = model + self._model_class = model.__class__ + self._model_options = model.options + + @property + def model_options(self): + return self._model_options + @property def geometry(self): return self._geometry @@ -25,6 +82,7 @@ def geometry(self): @geometry.setter def geometry(self, geometry): self._geometry = geometry + self._unprocessed_geometry = copy.deepcopy(geometry) @property def parameter_values(self): @@ -34,66 +92,72 @@ def parameter_values(self): def parameter_values(self, parameter_values): self._parameter_values = parameter_values + if self._status == "Parameterized": + self.reset() + self.parameterize() + + elif self._status == "Built": + self._parameter_values.update_model(self._model, self._disc) + @property def submesh_types(self): return self._submesh_types - @submesh_types.setter - def submesh_types(self, submesh_types): - # check of correct form - self._submesh_types = submesh_types - @property def var_pts(self): return self._var_pts - @var_pts.setter - def var_pts(self, var_pts): - self._var_pts = var_pts - @property def spatial_methods(self): return self._spatial_methods - @spatial_methods.setter - def spatial_methods(self, spatial_methods): - self._spatial_methods = spatial_methods - @property def solver(self): return self._solver - @solver.setter - def solver(self, solver): - self._solver = solver - @property def quick_plot_vars(self): return self._quick_plot_vars - @quick_plot_vars.setter - def quick_plot_vars(self, quick_plot_vars): - self._quick_plot_vars = quick_plot_vars - @property def solution(self): return self._solution - def solve(self, t_eval=None): - self._parameter_values.process_model(self._model) - self._parameter_values.process_geometry(self._geometry) - self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts) - self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods) - self._disc.process_model(self._model) - - if t_eval is None: - t_eval = np.linspace(0, 1, 100) - - self._solution = self.solver.solve(self._model, t_eval) - - def plot(self): - plot = pybamm.QuickPlot( - self._model, self._mesh, self._solution, self._quick_plot_vars - ) - plot.dynamic_plot() + def set_specs( + self, + model_options=None, + geometry=None, + parameter_values=None, + submesh_types=None, + var_pts=None, + spatial_methods=None, + solver=None, + quick_plot_vars=None, + ): + + if model_options: + self._model_options = model_options + + if geometry: + self.geometry = geometry + + if parameter_values: + self._parameter_values = parameter_values + if submesh_types: + self._submesh_types = submesh_types + if var_pts: + self._var_pts = var_pts + if spatial_methods: + self._spatial_methods = spatial_methods + if solver: + self._solver = solver + if quick_plot_vars: + self._quick_plot_vars = quick_plot_vars + + if self._status == "Parameterized": + self.reset() + self.parameterize + elif self._status == "Built": + self.reset() + self.build() diff --git a/tests/integration/test_simulation.py b/tests/integration/test_simulation.py index a3632f21bb..a7ce9f02a6 100644 --- a/tests/integration/test_simulation.py +++ b/tests/integration/test_simulation.py @@ -8,6 +8,12 @@ def test_run_with_spm(self): sim = pybamm.Simulation(model) sim.solve() + def test_update_parameters(self): + model = pybamm.lithium_ion.SPM() + sim = pybamm.Simulation(model) + + sim.parameterize_model() + if __name__ == "__main__": print("Add -v for more debug output") diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index 605913560d..450c0e5bd5 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -10,3 +10,12 @@ def test_set_model(self): self.assertEqual(sim.model, model) + def test_reset_model(self): + + sim = pybamm.Simulation(pybamm.SPM()) + + sim.discretize_model() + + sim.reset_model() + + # check can now re-parameterize model From 65ad72e5e8ec220e0d67c5197d3ff215b9a869ab Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Wed, 30 Oct 2019 16:37:49 +0000 Subject: [PATCH 03/16] #688 added docs --- docs/source/simulation.rst | 5 + pybamm/simulation.py | 159 +++++++++++++++++++-------- pybamm/solvers/scipy_solver.py | 2 +- tests/integration/test_simulation.py | 24 ---- tests/unit/test_simulation.py | 132 +++++++++++++++++++++- 5 files changed, 248 insertions(+), 74 deletions(-) create mode 100644 docs/source/simulation.rst delete mode 100644 tests/integration/test_simulation.py diff --git a/docs/source/simulation.rst b/docs/source/simulation.rst new file mode 100644 index 0000000000..a50a67eb21 --- /dev/null +++ b/docs/source/simulation.rst @@ -0,0 +1,5 @@ +Simulation +========== + +.. autoclass:: pybamm.Simulation + :members: \ No newline at end of file diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 3164adc62a..dd0757ca17 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -4,14 +4,25 @@ class Simulation: + """A Simulation class for easy building and running of PyBaMM simulations. + + Parameters + ---------- + model : :class:`pybamm.BaseModel` + The model to be simulated + """ + def __init__(self, model): self.model = model self.set_defaults() self.reset() def set_defaults(self): + """ + A method to set all the simulation specs to default values for the + supplied model. + """ self.geometry = self._model.default_geometry - self._parameter_values = self._model.default_parameter_values self._submesh_types = self._model.default_submesh_types self._var_pts = self._model.default_var_pts @@ -20,44 +31,85 @@ def set_defaults(self): self._quick_plot_vars = None def reset(self): + """ + A method to reset a simulation back to its unprocessed state. + """ self.model = self._model_class(self._model_options) self.geometry = copy.deepcopy(self._unprocessed_geometry) self._mesh = None - self._discretization = None + self._disc = None self._solution = None self._status = "Unprocessed" def parameterize(self): - if self._status == "Unprocessed": - self._parameter_values.process_model(self._model) - self._parameter_values.process_geometry(self._geometry) - self._status = "Parameterized" - elif self._status == "Built": - # There is a function to update parameters in the model - # but this misses some geometric parameters. This class - # is for convenience and not speed so just re-build. - self.reset() - self.build() + """ + A method to set the parameters in the model and the associated geometry. If + the model has already been built or solved then this will first reset to the + unprocessed state and then set the parameter values. + """ + + if self._status != "Unprocessed": + return None + + self._parameter_values.process_model(self._model) + self._parameter_values.process_geometry(self._geometry) + self._status = "Parameterized" def build(self): - if self._status != "Built": - self.parameterize() - self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts) - self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods) - self._disc.process_model(self._model) - self._model_status = "Built" - - def solve(self, t_eval=None): + """ + A method to build the model as a pure linear algebra expression. If the model + has already been built or solved then this function will have no effect. + If you want to rebuild, first use "reset()". This method will + automatically set the parameters if they have not already been set. + """ + + if self._status == "Built" or self._status == "Solved": + return None + + self.parameterize() + self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts) + self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods) + self._disc.process_model(self._model) + self._status = "Built" + + def solve(self, t_eval=None, solver=None): + """ + A method to solve the model. This method will automatically build + and set the model parameters if not already done so. + + Parameters + ---------- + t_eval : numeric type (optional) + The times at which to compute the solution + solver : :class:`pybamm.BaseSolver` + The solver to use to solve the model. + """ self.build() if t_eval is None: t_eval = np.linspace(0, 1, 100) - self._solution = self.solver.solve(self._model, t_eval) + if solver is None: + solver = self.solver + + self._solution = solver.solve(self._model, t_eval) + self._status = "Solved" + + def plot(self, quick_plot_vars=None): + """ + A method to quickly plot the outputs of the simulation. + + Parameters + ---------- + quick_plot_vars: list + A list of the variables to plot. + """ + + if quick_plot_vars is None: + quick_plot_vars = self.quick_plot_vars - def plot(self): plot = pybamm.QuickPlot( - self._model, self._mesh, self._solution, self._quick_plot_vars + self._model, self._mesh, self._solution, quick_plot_vars ) plot.dynamic_plot() @@ -85,19 +137,12 @@ def geometry(self, geometry): self._unprocessed_geometry = copy.deepcopy(geometry) @property - def parameter_values(self): - return self.parameter_values - - @parameter_values.setter - def parameter_values(self, parameter_values): - self._parameter_values = parameter_values - - if self._status == "Parameterized": - self.reset() - self.parameterize() + def unprocessed_geometry(self): + return self._unprocessed_geometry - elif self._status == "Built": - self._parameter_values.update_model(self._model, self._disc) + @property + def parameter_values(self): + return self._parameter_values @property def submesh_types(self): @@ -115,15 +160,23 @@ def spatial_methods(self): def solver(self): return self._solver + @solver.setter + def solver(self, solver): + self._solver = solver + @property def quick_plot_vars(self): return self._quick_plot_vars + @quick_plot_vars.setter + def quick_plot_vars(self, quick_plot_vars): + self._quick_plot_vars = quick_plot_vars + @property def solution(self): return self._solution - def set_specs( + def specs( self, model_options=None, geometry=None, @@ -134,6 +187,32 @@ def set_specs( solver=None, quick_plot_vars=None, ): + """ + A method to set the various specs of the simulation. This method + automatically resets the model after the new specs have been set. + + Parameters + ---------- + model_options: dict (optional) + A dictionary of options to tweak the model you are using + geometry: :class:`pybamm.Geometry` (optional) + The geometry upon which to solve the model + parameter_values: dict (optional) + A dictionary of parameters and their corresponding numerical + values + submesh_types: dict (optional) + A dictionary of the types of submesh to use on each subdomain + var_pts: dict (optional) + A dictionary of the number of points used by each spatial + variable + spatial_methods: dict (optional) + A dictionary of the types of spatial method to use on each + domain (e.g. pybamm.FiniteVolume) + solver: :class:`pybamm.BaseSolver` + The solver to use to solve the model. + quick_plot_vars: list + A list of variables to plot automatically + """ if model_options: self._model_options = model_options @@ -154,10 +233,4 @@ def set_specs( if quick_plot_vars: self._quick_plot_vars = quick_plot_vars - if self._status == "Parameterized": - self.reset() - self.parameterize - elif self._status == "Built": - self.reset() - self.build() - + self.reset() diff --git a/pybamm/solvers/scipy_solver.py b/pybamm/solvers/scipy_solver.py index fd7afbf498..23e11483c5 100644 --- a/pybamm/solvers/scipy_solver.py +++ b/pybamm/solvers/scipy_solver.py @@ -83,7 +83,7 @@ def integrate( termination = "event" t_event = [] for time in sol.t_events: - if time: + if time.size > 0: t_event = np.append(t_event, np.max(time)) t_event = np.array([np.max(t_event)]) y_event = sol.sol(t_event) diff --git a/tests/integration/test_simulation.py b/tests/integration/test_simulation.py deleted file mode 100644 index a7ce9f02a6..0000000000 --- a/tests/integration/test_simulation.py +++ /dev/null @@ -1,24 +0,0 @@ -import pybamm -import unittest - - -class TestSimulation(unittest.TestCase): - def test_run_with_spm(self): - model = pybamm.lithium_ion.SPM() - sim = pybamm.Simulation(model) - sim.solve() - - def test_update_parameters(self): - model = pybamm.lithium_ion.SPM() - sim = pybamm.Simulation(model) - - sim.parameterize_model() - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - unittest.main() diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index 450c0e5bd5..f1fed05f87 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -3,19 +3,139 @@ class TestSimulation(unittest.TestCase): - def test_set_model(self): + def test_basic_ops(self): model = pybamm.lithium_ion.SPM() sim = pybamm.Simulation(model) - self.assertEqual(sim.model, model) + self.assertEqual(model.__class__, sim._model_class) + self.assertEqual(model.options, sim._model_options) - def test_reset_model(self): + # check that the model is unprocessed + self.assertEqual(sim._status, "Unprocessed") + self.assertEqual(sim._mesh, None) + self.assertEqual(sim._disc, None) + for val in list(sim.model.rhs.values()): + self.assertTrue(val.has_symbol_of_classes(pybamm.Parameter)) + self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) - sim = pybamm.Simulation(pybamm.SPM()) + sim.parameterize() + self.assertEqual(sim._status, "Parameterized") + self.assertEqual(sim._mesh, None) + self.assertEqual(sim._disc, None) + for val in list(sim.model.rhs.values()): + self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) + self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) - sim.discretize_model() + sim.build() + self.assertEqual(sim._status, "Built") + self.assertFalse(sim._mesh is None) + self.assertFalse(sim._disc is None) + for val in list(sim.model.rhs.values()): + self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) + self.assertTrue(val.has_symbol_of_classes(pybamm.Matrix)) - sim.reset_model() + sim.parameterize() + self.assertEqual(sim._status, "Parameterized") + self.assertEqual(sim._mesh, None) + self.assertEqual(sim._disc, None) + for val in list(sim.model.rhs.values()): + self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) + self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) + sim.build() + sim.reset() + self.assertEqual(sim._status, "Unprocessed") + self.assertEqual(sim._mesh, None) + self.assertEqual(sim._disc, None) + for val in list(sim.model.rhs.values()): + self.assertTrue(val.has_symbol_of_classes(pybamm.Parameter)) + self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) + + def test_solve(self): + + sim = pybamm.Simulation(pybamm.lithium_ion.SPM()) + sim.solve() + self.assertFalse(sim._solution is None) + self.assertEqual(sim._status, "Solved") + for val in list(sim.model.rhs.values()): + self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) + self.assertTrue(val.has_symbol_of_classes(pybamm.Matrix)) + + sim.reset() + self.assertEqual(sim._status, "Unprocessed") + for val in list(sim.model.rhs.values()): + self.assertTrue(val.has_symbol_of_classes(pybamm.Parameter)) + self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) + + self.assertEqual(sim._solution, None) # check can now re-parameterize model + + def test_reuse_commands(self): + + sim = pybamm.Simulation(pybamm.lithium_ion.SPM()) + + sim.parameterize() + sim.parameterize() + + sim.build() + sim.build() + + sim.solve() + sim.solve() + + sim.build() + sim.solve() + sim.parameterize() + + def test_specs(self): + # test can rebuild after setting specs + sim = pybamm.Simulation(pybamm.lithium_ion.SPM()) + sim.build() + + model_options = {"thermal": "lumped"} + sim.specs(model_options=model_options) + sim.build() + self.assertEqual(sim.model.options["thermal"], "lumped") + + params = sim.parameter_values + # normally is 0.0001 + params.update({"Negative electrode thickness [m]": 0.0002}) + sim.specs(parameter_values=params) + + self.assertEqual( + sim.parameter_values["Negative electrode thickness [m]"], 0.0002 + ) + sim.build() + + geometry = sim.unprocessed_geometry + custom_geometry = {} + x_n = pybamm.standard_spatial_vars.x_n + custom_geometry["negative electrode"] = { + "primary": { + x_n: {"min": pybamm.Scalar(0), "max": pybamm.geometric_parameters.l_n} + } + } + geometry.update(custom_geometry) + sim.specs(geometry=geometry) + sim.build() + + var_pts = sim.var_pts + var_pts[pybamm.standard_spatial_vars.x_n] = 5 + sim.specs(var_pts=var_pts) + sim.build() + + spatial_methods = sim.spatial_methods + # nothing to change this to at the moment but just reload in + sim.specs(spatial_methods=spatial_methods) + sim.build() + + +if __name__ == "__main__": + print("Add -v for more debug output") + import sys + + if "-v" in sys.argv: + debug = True + unittest.main() + From 98dd8b12023137d66e76321e0d9161a2ebcedf4a Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Wed, 30 Oct 2019 16:55:12 +0000 Subject: [PATCH 04/16] #688 passes tests --- tests/unit/test_simulation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index f1fed05f87..75bf849bc2 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -35,6 +35,7 @@ def test_basic_ops(self): self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) self.assertTrue(val.has_symbol_of_classes(pybamm.Matrix)) + sim.reset() sim.parameterize() self.assertEqual(sim._status, "Parameterized") self.assertEqual(sim._mesh, None) From 74d05a4a73fe5e0151a27a88e6ff4eaaaf7520a1 Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Wed, 30 Oct 2019 17:03:39 +0000 Subject: [PATCH 05/16] #688 added simulation to toctree --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index 49b6913f16..d991331d6c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,6 +32,7 @@ Contents source/solvers/index source/processed_variable source/util + source/simulation Examples ======== From 699c27470394834514c52d3df3b66cd78199d540 Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Wed, 30 Oct 2019 17:05:41 +0000 Subject: [PATCH 06/16] #688 updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07421aef02..92814cf09e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +- Added Simulation class ([#693](https://github.com/pybamm-team/PyBaMM/pull/693)) - Added interface (via pybind11) to sundials with the IDA KLU sparse linear solver ([#657](https://github.com/pybamm-team/PyBaMM/pull/657)) - Add method to evaluate parameters more easily ([#669](https://github.com/pybamm-team/PyBaMM/pull/669)) - Add `Jacobian` class to reuse known Jacobians of expressions ([#665](https://github.com/pybamm-team/PyBaMM/pull/670)) From 7daddaf33112fc5fe86c194c73db844f4926cb1b Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 09:58:35 +0000 Subject: [PATCH 07/16] #688 allowed init with specs --- pybamm/simulation.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pybamm/simulation.py b/pybamm/simulation.py index dd0757ca17..3572dc2a44 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -12,9 +12,27 @@ class Simulation: The model to be simulated """ - def __init__(self, model): + def __init__( + self, + model, + geometry=None, + parameter_values=None, + submesh_types=None, + var_pts=None, + spatial_methods=None, + solver=None, + quick_plot_vars=None, + ): self.model = model - self.set_defaults() + + self.geometry = geometry or model.default_geometry + self._parameter_values = parameter_values or model.default_parameter_values + self._submesh_types = submesh_types or model.default_submesh_types + self._var_pts = var_pts or model.default_var_pts + self._spatial_methods = spatial_methods or model.default_spatial_methods + self._solver = solver or self._model.default_solver + self._quick_plot_vars = quick_plot_vars + self.reset() def set_defaults(self): From 6b2f94eddeac327e24d0e56c447ec4c0fee43752 Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 10:04:00 +0000 Subject: [PATCH 08/16] #688 changed paraterize to set_parameters --- pybamm/simulation.py | 16 ++++++++++++---- tests/unit/test_simulation.py | 14 +++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 3572dc2a44..be562777a2 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -59,7 +59,7 @@ def reset(self): self._solution = None self._status = "Unprocessed" - def parameterize(self): + def set_parameters(self): """ A method to set the parameters in the model and the associated geometry. If the model has already been built or solved then this will first reset to the @@ -71,7 +71,7 @@ def parameterize(self): self._parameter_values.process_model(self._model) self._parameter_values.process_geometry(self._geometry) - self._status = "Parameterized" + self._status = "Parameters set" def build(self): """ @@ -84,7 +84,7 @@ def build(self): if self._status == "Built" or self._status == "Solved": return None - self.parameterize() + self.set_parameters() self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts) self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods) self._disc.process_model(self._model) @@ -251,4 +251,12 @@ def specs( if quick_plot_vars: self._quick_plot_vars = quick_plot_vars - self.reset() + if ( + model_options + or geometry + or parameter_values + or submesh_types + or var_pts + or spatial_methods + ): + self.reset() diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index 75bf849bc2..038e4b6265 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -19,8 +19,8 @@ def test_basic_ops(self): self.assertTrue(val.has_symbol_of_classes(pybamm.Parameter)) self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) - sim.parameterize() - self.assertEqual(sim._status, "Parameterized") + sim.set_parameters() + self.assertEqual(sim._status, "Parameters set") self.assertEqual(sim._mesh, None) self.assertEqual(sim._disc, None) for val in list(sim.model.rhs.values()): @@ -36,8 +36,8 @@ def test_basic_ops(self): self.assertTrue(val.has_symbol_of_classes(pybamm.Matrix)) sim.reset() - sim.parameterize() - self.assertEqual(sim._status, "Parameterized") + sim.set_parameters() + self.assertEqual(sim._status, "Parameters set") self.assertEqual(sim._mesh, None) self.assertEqual(sim._disc, None) for val in list(sim.model.rhs.values()): @@ -76,8 +76,8 @@ def test_reuse_commands(self): sim = pybamm.Simulation(pybamm.lithium_ion.SPM()) - sim.parameterize() - sim.parameterize() + sim.set_parameters() + sim.set_parameters() sim.build() sim.build() @@ -87,7 +87,7 @@ def test_reuse_commands(self): sim.build() sim.solve() - sim.parameterize() + sim.set_parameters() def test_specs(self): # test can rebuild after setting specs From 8f9d4f65eb26aaf5b35c8fa9ededc3021150d97d Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 10:05:53 +0000 Subject: [PATCH 09/16] #688 removed linear algebra phrase --- pybamm/simulation.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pybamm/simulation.py b/pybamm/simulation.py index be562777a2..87dbf9046a 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -75,10 +75,11 @@ def set_parameters(self): def build(self): """ - A method to build the model as a pure linear algebra expression. If the model - has already been built or solved then this function will have no effect. - If you want to rebuild, first use "reset()". This method will - automatically set the parameters if they have not already been set. + A method to build the model into a system of matrices and vectors suitable for + performing numerical computations. If the model has already been built or + solved then this function will have no effect. If you want to rebuild, + first use "reset()". This method will automatically set the parameters + if they have not already been set. """ if self._status == "Built" or self._status == "Solved": From a15ae4f70f14a0bd8de99cc2cb3ecb5b9da99b77 Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 10:09:40 +0000 Subject: [PATCH 10/16] #688 added readme --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2946fc01dc..8e4ca96df3 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,17 @@ Python Battery Mathematical Modelling solves continuum models for batteries, usi ## How do I use PyBaMM? -PyBaMM comes with a number of [detailed examples](examples/notebooks/README.md), hosted here on +The easiest way to use PyBaMM is to run a 1C constant-current discharge with a model of your choice with all the default settings: +```python3 +import pybamm +model = pybamm.lithium_ion.DFN() # Doyle-Fuller-Newman model +sim = pybamm.Simulation(model) +sim.solve() +sim.plot() +``` +However, much greater customisation is available. It is possible to change the physics, parameter values, geometry, submesh type, number of submesh points, methods for spatial discretisation and solver for integration (see DFN [script](examples/scripts/DFN.py) or [notebook](examples/notebooks/models/dfn.ipynb)). + +Further details can be found in a number of [detailed examples](examples/notebooks/README.md), hosted here on github. In addition, there is a [full API documentation](http://pybamm.readthedocs.io/), hosted on [Read The Docs](readthedocs.io). A set of slides giving an overview of PyBaMM can be found From b550d7462ae169655b234ea49d0fa0db294f6184 Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 10:29:06 +0000 Subject: [PATCH 11/16] #688 added inplace to parameter values --- pybamm/parameters/parameter_values.py | 23 ++++++++++++++++++++--- pybamm/simulation.py | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py index 5aff085d3e..71a12246d9 100644 --- a/pybamm/parameters/parameter_values.py +++ b/pybamm/parameters/parameter_values.py @@ -211,13 +211,13 @@ def check_and_update_parameter_values(self, values): values["C-rate"] = float(values["Typical current [A]"]) / capacity return values - def process_model(self, model, processing="process"): + def process_model(self, unprocessed_model, processing="process", inplace=True): """Assign parameter values to a model. Currently inplace, could be changed to return a new model. Parameters ---------- - model : :class:`pybamm.BaseModel` + unprocessed_model : :class:`pybamm.BaseModel` Model to assign parameter values for processing : str, optional Flag to indicate how to process model (default 'process') @@ -226,6 +226,9 @@ def process_model(self, model, processing="process"): and replace any Parameter with a Value) * 'update': Calls :meth:`update_scalars()` for use on already-processed \ model (update the value of any Scalars in the expression tree.) + inplace: bool, optional + If True, replace the parameters in the model in place. Otherwise, return a + new model with parameter values set. Default is True. Raises ------ @@ -233,7 +236,21 @@ def process_model(self, model, processing="process"): If an empty model is passed (`model.rhs = {}` and `model.algebraic={}`) """ - pybamm.logger.info("Start setting parameters for {}".format(model.name)) + pybamm.logger.info("Start setting parameters for {}".format(unprocessed_model.name)) + + # set up inplace vs not inplace + if inplace: + # any changes to model_disc attributes will change model attributes + # since they point to the same object + model = unprocessed_model + else: + # create a blank model so that original model is unchanged + model = pybamm.BaseModel() + model.name = unprocessed_model.name + model.options = unprocessed_model.options + model.use_jacobian = unprocessed_model.use_jacobian + model.use_simplify = unprocessed_model.use_simplify + model.convert_to_format = unprocessed_model.convert_to_format if len(model.rhs) == 0 and len(model.algebraic) == 0: raise pybamm.ModelError("Cannot process parameters for empty model") diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 87dbf9046a..336b4fa421 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -88,7 +88,7 @@ def build(self): self.set_parameters() self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts) self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods) - self._disc.process_model(self._model) + self._built_model = self._disc.process_model(self._model, inplace=False) self._status = "Built" def solve(self, t_eval=None, solver=None): From f14d7dcffde5c4ea283d7a1aaa141c90828a878a Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 10:41:56 +0000 Subject: [PATCH 12/16] #688 added inplace for parameters --- pybamm/parameters/parameter_values.py | 20 +++++++++++-------- .../test_parameters/test_update_parameters.py | 11 ++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py index 71a12246d9..20e8cdce20 100644 --- a/pybamm/parameters/parameter_values.py +++ b/pybamm/parameters/parameter_values.py @@ -236,7 +236,9 @@ def process_model(self, unprocessed_model, processing="process", inplace=True): If an empty model is passed (`model.rhs = {}` and `model.algebraic={}`) """ - pybamm.logger.info("Start setting parameters for {}".format(unprocessed_model.name)) + pybamm.logger.info( + "Start setting parameters for {}".format(unprocessed_model.name) + ) # set up inplace vs not inplace if inplace: @@ -252,7 +254,7 @@ def process_model(self, unprocessed_model, processing="process", inplace=True): model.use_simplify = unprocessed_model.use_simplify model.convert_to_format = unprocessed_model.convert_to_format - if len(model.rhs) == 0 and len(model.algebraic) == 0: + if len(unprocessed_model.rhs) == 0 and len(unprocessed_model.algebraic) == 0: raise pybamm.ModelError("Cannot process parameters for empty model") if processing == "process": @@ -260,13 +262,13 @@ def process_model(self, unprocessed_model, processing="process", inplace=True): elif processing == "update": processing_function = self.update_scalars - for variable, equation in model.rhs.items(): + for variable, equation in unprocessed_model.rhs.items(): pybamm.logger.debug( "{} parameters for {!r} (rhs)".format(processing.capitalize(), variable) ) model.rhs[variable] = processing_function(equation) - for variable, equation in model.algebraic.items(): + for variable, equation in unprocessed_model.algebraic.items(): pybamm.logger.debug( "{} parameters for {!r} (algebraic)".format( processing.capitalize(), variable @@ -274,7 +276,7 @@ def process_model(self, unprocessed_model, processing="process", inplace=True): ) model.algebraic[variable] = processing_function(equation) - for variable, equation in model.initial_conditions.items(): + for variable, equation in unprocessed_model.initial_conditions.items(): pybamm.logger.debug( "{} parameters for {!r} (initial conditions)".format( processing.capitalize(), variable @@ -287,7 +289,7 @@ def process_model(self, unprocessed_model, processing="process", inplace=True): # small number of variables, e.g. {"negative tab": neg. tab bc, # "positive tab": pos. tab bc "no tab": no tab bc}. new_boundary_conditions = {} - for variable, bcs in model.boundary_conditions.items(): + for variable, bcs in unprocessed_model.boundary_conditions.items(): processed_variable = processing_function(variable) new_boundary_conditions[processed_variable] = {} for side in ["left", "right", "negative tab", "positive tab", "no tab"]: @@ -305,14 +307,14 @@ def process_model(self, unprocessed_model, processing="process", inplace=True): model.boundary_conditions = new_boundary_conditions - for variable, equation in model.variables.items(): + for variable, equation in unprocessed_model.variables.items(): pybamm.logger.debug( "{} parameters for {!r} (variables)".format( processing.capitalize(), variable ) ) model.variables[variable] = processing_function(equation) - for event, equation in model.events.items(): + for event, equation in unprocessed_model.events.items(): pybamm.logger.debug( "{} parameters for event '{}''".format(processing.capitalize(), event) ) @@ -320,6 +322,8 @@ def process_model(self, unprocessed_model, processing="process", inplace=True): pybamm.logger.info("Finish setting parameters for {}".format(model.name)) + return model + def update_model(self, model, disc): """Process a discretised model. Currently inplace, could be changed to return a new model. diff --git a/tests/unit/test_parameters/test_update_parameters.py b/tests/unit/test_parameters/test_update_parameters.py index ccc13a3c7e..bab24a8f42 100644 --- a/tests/unit/test_parameters/test_update_parameters.py +++ b/tests/unit/test_parameters/test_update_parameters.py @@ -94,6 +94,17 @@ def test_update_model(self): # results should be different self.assertNotEqual(np.linalg.norm(Y1 - Y3), 0) + def test_inplace(self): + model = pybamm.lithium_ion.SPM() + param = model.default_parameter_values + new_model = param.process_model(model, inplace=False) + + for val in list(model.rhs.values()): + self.assertTrue(val.has_symbol_of_classes(pybamm.Parameter)) + + for val in list(new_model.rhs.values()): + self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) + def test_update_geometry(self): # test on simple lead-acid model model1 = pybamm.lead_acid.LOQS() From ffa11b09a00da6caf160b902725a4bb309c63bdc Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 10:56:12 +0000 Subject: [PATCH 13/16] #688 updated sim to use inplace instead of status --- pybamm/simulation.py | 24 ++++++++++++++++-------- tests/unit/test_simulation.py | 22 ++++++++++------------ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 336b4fa421..7cce647837 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -54,10 +54,11 @@ def reset(self): """ self.model = self._model_class(self._model_options) self.geometry = copy.deepcopy(self._unprocessed_geometry) + self._model_with_set_params = None + self._built_model = None self._mesh = None self._disc = None self._solution = None - self._status = "Unprocessed" def set_parameters(self): """ @@ -66,12 +67,13 @@ def set_parameters(self): unprocessed state and then set the parameter values. """ - if self._status != "Unprocessed": + if self.model_with_set_params: return None - self._parameter_values.process_model(self._model) + self._model_with_set_params = self._parameter_values.process_model( + self._model, inplace=True + ) self._parameter_values.process_geometry(self._geometry) - self._status = "Parameters set" def build(self): """ @@ -82,14 +84,13 @@ def build(self): if they have not already been set. """ - if self._status == "Built" or self._status == "Solved": + if self.built_model: return None self.set_parameters() self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts) self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods) self._built_model = self._disc.process_model(self._model, inplace=False) - self._status = "Built" def solve(self, t_eval=None, solver=None): """ @@ -111,8 +112,7 @@ def solve(self, t_eval=None, solver=None): if solver is None: solver = self.solver - self._solution = solver.solve(self._model, t_eval) - self._status = "Solved" + self._solution = solver.solve(self.built_model, t_eval) def plot(self, quick_plot_vars=None): """ @@ -142,6 +142,14 @@ def model(self, model): self._model_class = model.__class__ self._model_options = model.options + @property + def model_with_set_params(self): + return self._model_with_set_params + + @property + def built_model(self): + return self._built_model + @property def model_options(self): return self._model_options diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index 038e4b6265..8dd3aa1b36 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -12,7 +12,6 @@ def test_basic_ops(self): self.assertEqual(model.options, sim._model_options) # check that the model is unprocessed - self.assertEqual(sim._status, "Unprocessed") self.assertEqual(sim._mesh, None) self.assertEqual(sim._disc, None) for val in list(sim.model.rhs.values()): @@ -20,35 +19,35 @@ def test_basic_ops(self): self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) sim.set_parameters() - self.assertEqual(sim._status, "Parameters set") self.assertEqual(sim._mesh, None) self.assertEqual(sim._disc, None) - for val in list(sim.model.rhs.values()): + for val in list(sim.model_with_set_params.rhs.values()): self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) sim.build() - self.assertEqual(sim._status, "Built") self.assertFalse(sim._mesh is None) self.assertFalse(sim._disc is None) - for val in list(sim.model.rhs.values()): + for val in list(sim.built_model.rhs.values()): self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) self.assertTrue(val.has_symbol_of_classes(pybamm.Matrix)) sim.reset() sim.set_parameters() - self.assertEqual(sim._status, "Parameters set") self.assertEqual(sim._mesh, None) self.assertEqual(sim._disc, None) - for val in list(sim.model.rhs.values()): + self.assertEqual(sim.built_model, None) + + for val in list(sim.model_with_set_params.rhs.values()): self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) sim.build() sim.reset() - self.assertEqual(sim._status, "Unprocessed") self.assertEqual(sim._mesh, None) self.assertEqual(sim._disc, None) + self.assertEqual(sim.model_with_set_params, None) + self.assertEqual(sim.built_model, None) for val in list(sim.model.rhs.values()): self.assertTrue(val.has_symbol_of_classes(pybamm.Parameter)) self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) @@ -58,19 +57,18 @@ def test_solve(self): sim = pybamm.Simulation(pybamm.lithium_ion.SPM()) sim.solve() self.assertFalse(sim._solution is None) - self.assertEqual(sim._status, "Solved") - for val in list(sim.model.rhs.values()): + for val in list(sim.built_model.rhs.values()): self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) self.assertTrue(val.has_symbol_of_classes(pybamm.Matrix)) sim.reset() - self.assertEqual(sim._status, "Unprocessed") + self.assertEqual(sim.model_with_set_params, None) + self.assertEqual(sim.built_model, None) for val in list(sim.model.rhs.values()): self.assertTrue(val.has_symbol_of_classes(pybamm.Parameter)) self.assertFalse(val.has_symbol_of_classes(pybamm.Matrix)) self.assertEqual(sim._solution, None) - # check can now re-parameterize model def test_reuse_commands(self): From 6f68df1d6f2c042fdb37a2923040b419c3e677eb Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 12:10:04 +0000 Subject: [PATCH 14/16] #688 fixed error for using inplace disctretization --- examples/scripts/SPMe.py | 7 ++++--- examples/scripts/run_simulation.py | 1 + pybamm/discretisations/discretisation.py | 4 ++-- pybamm/parameters/parameter_values.py | 4 ++-- pybamm/simulation.py | 10 +++++++++- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/examples/scripts/SPMe.py b/examples/scripts/SPMe.py index 2485dfd305..25659f7f9e 100644 --- a/examples/scripts/SPMe.py +++ b/examples/scripts/SPMe.py @@ -15,7 +15,7 @@ # load parameter values and process model and geometry param = model.default_parameter_values -param.process_model(model) +model2 = param.process_model(model) param.process_geometry(geometry) # set mesh @@ -23,12 +23,13 @@ # discretise model disc = pybamm.Discretisation(mesh, model.default_spatial_methods) -disc.process_model(model) +model3 = disc.process_model(model2, inplace=False) +disc.process_model(model2, inplace=True) # solve model t_eval = np.linspace(0, 0.2, 100) solution = model.default_solver.solve(model, t_eval) # plot -plot = pybamm.QuickPlot(model, mesh, solution) +plot = pybamm.QuickPlot(model3, mesh, solution) plot.dynamic_plot() diff --git a/examples/scripts/run_simulation.py b/examples/scripts/run_simulation.py index ecb56d656d..09e2f3ed42 100644 --- a/examples/scripts/run_simulation.py +++ b/examples/scripts/run_simulation.py @@ -1,6 +1,7 @@ import pybamm model = pybamm.lithium_ion.SPM() + sim = pybamm.Simulation(model) sim.solve() sim.plot() diff --git a/pybamm/discretisations/discretisation.py b/pybamm/discretisations/discretisation.py index 284e375cb7..0c1ab0c6ab 100644 --- a/pybamm/discretisations/discretisation.py +++ b/pybamm/discretisations/discretisation.py @@ -122,8 +122,8 @@ def process_model(self, model, inplace=True): # since they point to the same object model_disc = model else: - # create a blank model so that original model is unchanged - model_disc = pybamm.BaseModel() + # create a model of the same class as the original model + model_disc = model.__class__(model.options) model_disc.name = model.name model_disc.options = model.options model_disc.use_jacobian = model.use_jacobian diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py index 20e8cdce20..69b1cc464f 100644 --- a/pybamm/parameters/parameter_values.py +++ b/pybamm/parameters/parameter_values.py @@ -246,8 +246,8 @@ def process_model(self, unprocessed_model, processing="process", inplace=True): # since they point to the same object model = unprocessed_model else: - # create a blank model so that original model is unchanged - model = pybamm.BaseModel() + # create a blank model of the same class + model = model.__class__(model.options) model.name = unprocessed_model.name model.options = unprocessed_model.options model.use_jacobian = unprocessed_model.use_jacobian diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 7cce647837..774a5750b8 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -124,11 +124,19 @@ def plot(self, quick_plot_vars=None): A list of the variables to plot. """ + if self._solution is None: + raise ValueError( + "Model has not been solved, please solve the model before plotting." + ) + if quick_plot_vars is None: quick_plot_vars = self.quick_plot_vars plot = pybamm.QuickPlot( - self._model, self._mesh, self._solution, quick_plot_vars + self.built_model, + self._mesh, + self._solution, + output_variables=quick_plot_vars, ) plot.dynamic_plot() From 0b2f0fe35fbf8ea9686a0a43356ce627789930d6 Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 12:30:01 +0000 Subject: [PATCH 15/16] #688 fixed issue in parameter values inplace --- examples/scripts/SPMe.py | 5 ++--- pybamm/parameters/parameter_values.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/scripts/SPMe.py b/examples/scripts/SPMe.py index 25659f7f9e..bec8e54099 100644 --- a/examples/scripts/SPMe.py +++ b/examples/scripts/SPMe.py @@ -15,7 +15,7 @@ # load parameter values and process model and geometry param = model.default_parameter_values -model2 = param.process_model(model) +param.process_model(model) param.process_geometry(geometry) # set mesh @@ -23,8 +23,7 @@ # discretise model disc = pybamm.Discretisation(mesh, model.default_spatial_methods) -model3 = disc.process_model(model2, inplace=False) -disc.process_model(model2, inplace=True) +disc.process_model(model) # solve model t_eval = np.linspace(0, 0.2, 100) diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py index 69b1cc464f..9880746c00 100644 --- a/pybamm/parameters/parameter_values.py +++ b/pybamm/parameters/parameter_values.py @@ -247,7 +247,7 @@ def process_model(self, unprocessed_model, processing="process", inplace=True): model = unprocessed_model else: # create a blank model of the same class - model = model.__class__(model.options) + model = unprocessed_model.__class__(unprocessed_model.options) model.name = unprocessed_model.name model.options = unprocessed_model.options model.use_jacobian = unprocessed_model.use_jacobian From 0035628a33051f17584353114f76af5a0f7b51e1 Mon Sep 17 00:00:00 2001 From: Scott Marquis <marquis@maths.ox.ac.uk> Date: Thu, 31 Oct 2019 14:09:36 +0000 Subject: [PATCH 16/16] #688 correct spme script --- examples/scripts/SPMe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/scripts/SPMe.py b/examples/scripts/SPMe.py index bec8e54099..2485dfd305 100644 --- a/examples/scripts/SPMe.py +++ b/examples/scripts/SPMe.py @@ -30,5 +30,5 @@ solution = model.default_solver.solve(model, t_eval) # plot -plot = pybamm.QuickPlot(model3, mesh, solution) +plot = pybamm.QuickPlot(model, mesh, solution) plot.dynamic_plot()