Repository: pybamm-team/PyBaMM (python)

Issue:
make check model optional
during `process_model` in the discretisation step we perform some model checks (`check_model`). for big problems this makes up the vast majority of the discretise time. it would be good to be able to pass an option that skips these checks so that if you know your model works fine you dont redo the check every time you discretise 

Pull request:
#738 check model option
# Description
Allows users to optionally skip model checks during discretisation, as this can be slow for large systems. By default the checks are performed, and it is recommended to do the model checks when developing, testing etc.

Fixes #738 

## Type of change

Please add a line in the relevant section of [CHANGELOG.md](https://github.com/pybamm-team/PyBaMM/blob/master/CHANGELOG.md) to document the change (include PR #) - note reverse order of PR #s. If necessary, also add to the list of breaking changes.

- [ ] New feature (non-breaking change which adds functionality)
- [ ] Optimization (back-end change that speeds up the code)
- [ ] Bug fix (non-breaking change which fixes an issue)


# Key checklist:

- [ ] No style issues: `$ flake8`
- [ ] All tests pass: `$ python run-tests.py --unit`
- [ ] The documentation builds: `$ cd docs` and then `$ make clean; make html`

You can run all three at once, using `$ python run-tests.py --quick`.

## Further checks:

- [ ] Code is commented, particularly in hard-to-understand areas
- [ ] Tests added that prove fix is effective or that feature works


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aac622f6d..292a7642a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@
 
 ## Optimizations
 
+-   Added an option to skip model checks during discretisation, which could be slow for large models ([#739](https://github.com/pybamm-team/PyBaMM/pull/739))
 -   Use CasADi's automatic differentation algorithms by default when solving a model ([#714](https://github.com/pybamm-team/PyBaMM/pull/714))
 -   Avoid re-checking size when making a copy of an `Index` object ([#656](https://github.com/pybamm-team/PyBaMM/pull/656))
 -   Avoid recalculating `_evaluation_array` when making a copy of a `StateVector` object ([#653](https://github.com/pybamm-team/PyBaMM/pull/653))
diff --git a/input/parameters/lithium-ion/cells/kokam_Marquis2019/parameters.csv b/input/parameters/lithium-ion/cells/kokam_Marquis2019/parameters.csv
index f0419a9b6..f98187ecd 100644
--- a/input/parameters/lithium-ion/cells/kokam_Marquis2019/parameters.csv
+++ b/input/parameters/lithium-ion/cells/kokam_Marquis2019/parameters.csv
@@ -33,4 +33,4 @@ Negative current collector thermal conductivity [W.m-1.K-1],401,,
 Positive current collector thermal conductivity [W.m-1.K-1],237,,
 ,,,
 # Electrical,,,
-Cell capacity [A.h],0.68,,24 Ah/m2 * 0.137m * 0.207m
+Cell capacity [A.h],0.680616,,24 Ah/m2 * 0.137m * 0.207m
diff --git a/pybamm/discretisations/discretisation.py b/pybamm/discretisations/discretisation.py
index 708fbeb50..d82466cfa 100644
--- a/pybamm/discretisations/discretisation.py
+++ b/pybamm/discretisations/discretisation.py
@@ -82,7 +82,7 @@ class Discretisation(object):
         # reset discretised_symbols
         self._discretised_symbols = {}
 
-    def process_model(self, model, inplace=True):
+    def process_model(self, model, inplace=True, check_model=True):
         """Discretise a model.
         Currently inplace, could be changed to return a new model.
 
@@ -91,9 +91,15 @@ class Discretisation(object):
         model : :class:`pybamm.BaseModel`
             Model to dicretise. Must have attributes rhs, initial_conditions and
             boundary_conditions (all dicts of {variable: equation})
-        inplace: bool, optional
+        inplace : bool, optional
             If True, discretise the model in place. Otherwise, return a new
             discretised model. Default is True.
+        check_model : bool, optional
+            If True, model checks are performed after discretisation. For large
+            systems these checks can be slow, so can be skipped by setting this
+            option to False. When developing, testing or debugging it is recommened
+            to leave this option as True as it may help to identify any errors.
+            Default is True.
 
         Returns
         -------
@@ -173,7 +179,9 @@ class Discretisation(object):
         model_disc.mass_matrix = self.create_mass_matrix(model_disc)
 
         # Check that resulting model makes sense
-        self.check_model(model_disc)
+        if check_model:
+            pybamm.logger.info("Performing model checks for {}".format(model.name))
+            self.check_model(model_disc)
 
         pybamm.logger.info("Finish discretising {}".format(model.name))
 
diff --git a/pybamm/simulation.py b/pybamm/simulation.py
index 1a75fc563..87235f3d6 100644
--- a/pybamm/simulation.py
+++ b/pybamm/simulation.py
@@ -98,13 +98,19 @@ class Simulation:
         )
         self._parameter_values.process_geometry(self._geometry)
 
-    def build(self):
+    def build(self, check_model=True):
         """
         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.
+
+        Parameters
+        ----------
+        check_model : bool, optional
+            If True, model checks are performed after discretisation (see
+            :meth:`pybamm.Discretisation.process_model`). Default is True.
         """
 
         if self.built_model:
@@ -113,9 +119,11 @@ class Simulation:
         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._built_model = self._disc.process_model(
+            self._model, inplace=False, check_model=check_model
+        )
 
-    def solve(self, t_eval=None, solver=None):
+    def solve(self, t_eval=None, solver=None, check_model=True):
         """
         A method to solve the model. This method will automatically build
         and set the model parameters if not already done so.
@@ -129,8 +137,11 @@ class Simulation:
             non-dimensional time of 1.
         solver : :class:`pybamm.BaseSolver`
             The solver to use to solve the model.
+        check_model : bool, optional
+            If True, model checks are performed after discretisation (see
+            :meth:`pybamm.Discretisation.process_model`). Default is True.
         """
-        self.build()
+        self.build(check_model=check_model)
 
         if t_eval is None:
             try:


Test patch:
diff --git a/tests/unit/test_discretisations/test_discretisation.py b/tests/unit/test_discretisations/test_discretisation.py
index 0a61b0507..34472d813 100644
--- a/tests/unit/test_discretisations/test_discretisation.py
+++ b/tests/unit/test_discretisations/test_discretisation.py
@@ -852,6 +852,23 @@ class TestDiscretise(unittest.TestCase):
         with self.assertRaisesRegex(pybamm.ModelError, "Boundary conditions"):
             disc.check_tab_conditions(b, bcs)
 
+    def test_process_with_no_check(self):
+        # create model
+        whole_cell = ["negative electrode", "separator", "positive electrode"]
+        c = pybamm.Variable("c", domain=whole_cell)
+        N = pybamm.grad(c)
+        model = pybamm.BaseModel()
+        model.rhs = {c: pybamm.div(N)}
+        model.initial_conditions = {c: pybamm.Scalar(3)}
+        model.boundary_conditions = {
+            c: {"left": (0, "Neumann"), "right": (0, "Neumann")}
+        }
+        model.variables = {"c": c, "N": N}
+
+        # create discretisation
+        disc = get_discretisation_for_testing()
+        disc.process_model(model, check_model=False)
+
 
 if __name__ == "__main__":
     print("Add -v for more debug output")
diff --git a/tests/unit/test_parameters/test_update_parameters.py b/tests/unit/test_parameters/test_update_parameters.py
index 29832187a..e26c153da 100644
--- a/tests/unit/test_parameters/test_update_parameters.py
+++ b/tests/unit/test_parameters/test_update_parameters.py
@@ -47,7 +47,7 @@ class TestUpdateParameters(unittest.TestCase):
         model2 = pybamm.lithium_ion.SPM()
         modeltest2 = tests.StandardModelTest(model2)
         modeltest2.test_all(skip_output_tests=True)
-        self.assertEqual(model2.variables["Current [A]"].evaluate(), 0.68)
+        self.assertEqual(model2.variables["Current [A]"].evaluate(), 0.680616)
         # process and solve with updated parameter values
         parameter_values_update = pybamm.ParameterValues(
             chemistry=pybamm.parameter_sets.Marquis2019
@@ -73,8 +73,6 @@ class TestUpdateParameters(unittest.TestCase):
         modeltest3.test_solving(t_eval=t_eval)
         Y3 = modeltest3.solution.y
 
-        # function.parameters should be pybamm.Scalar(0), but parameters_eval s
-        # should be a float
         self.assertIsInstance(model3.variables["Current [A]"], pybamm.Scalar)
         self.assertEqual(model3.variables["Current [A]"].evaluate(), 0.0)
 
diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py
index 25cd6c7bf..e4ada227d 100644
--- a/tests/unit/test_simulation.py
+++ b/tests/unit/test_simulation.py
@@ -71,6 +71,13 @@ class TestSimulation(unittest.TestCase):
 
         self.assertEqual(sim._solution, None)
 
+        # test solve without check
+        sim.reset()
+        sim.solve(check_model=False)
+        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))
+
     def test_reuse_commands(self):
 
         sim = pybamm.Simulation(pybamm.lithium_ion.SPM())