From 1c1d86460ce4ed2a48731883e586a160f5406363 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Fri, 13 Mar 2020 18:48:50 -0400 Subject: [PATCH 1/7] #609 add tests --- .../test_discretisation.py | 41 ++++++++++++++++++- .../test_expression_tree/test_broadcasts.py | 26 ++++++++++++ .../test_finite_volume/test_finite_volume.py | 24 +++++++++-- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_discretisations/test_discretisation.py b/tests/unit/test_discretisations/test_discretisation.py index da38f07782..12b99cd542 100644 --- a/tests/unit/test_discretisations/test_discretisation.py +++ b/tests/unit/test_discretisations/test_discretisation.py @@ -909,8 +909,19 @@ def test_broadcast(self): self.assertIsInstance(broad1_disc.children[0], pybamm.StateVector) self.assertIsInstance(broad1_disc.children[1], pybamm.Vector) + # broadcast to edges + broad_edges = pybamm.FullBroadcastToEdges(a, ["negative electrode"], None) + broad_edges_disc = disc.process_symbol(broad_edges) + np.testing.assert_array_equal( + broad_edges_disc.evaluate(u={"a": 7}), + 7 * np.ones_like(combined_submesh[0].edges[:, np.newaxis]), + ) + self.assertIsInstance(broad_edges_disc, pybamm.Multiplication) + self.assertIsInstance(broad_edges_disc.children[0], pybamm.StateVector) + self.assertIsInstance(broad_edges_disc.children[1], pybamm.Vector) + def test_broadcast_2D(self): - # broadcast in 2D --> Outer symbol + # broadcast in 2D --> MatrixMultiplication var = pybamm.Variable("var", ["current collector"]) disc = get_1p1d_discretisation_for_testing() mesh = disc.mesh @@ -931,6 +942,22 @@ def test_broadcast_2D(self): np.outer(y_test, np.ones(mesh["separator"][0].npts)).reshape(-1, 1), ) + # test broadcast to edges + broad_to_edges = pybamm.PrimaryBroadcastToEdges(var, "separator") + broad_to_edges_disc = disc.process_symbol(broad_to_edges) + self.assertIsInstance(broad_to_edges_disc, pybamm.MatrixMultiplication) + self.assertIsInstance(broad_to_edges_disc.children[0], pybamm.Matrix) + self.assertIsInstance(broad_to_edges_disc.children[1], pybamm.StateVector) + self.assertEqual( + broad_to_edges_disc.shape, + ((mesh["separator"][0].npts + 1) * mesh["current collector"][0].npts, 1), + ) + y_test = np.linspace(0, 1, mesh["current collector"][0].npts) + np.testing.assert_array_equal( + broad_disc.evaluate(y=y_test), + np.outer(y_test, np.ones(mesh["separator"][0].npts + 1)).reshape(-1, 1), + ) + def test_secondary_broadcast_2D(self): # secondary broadcast in 2D --> Matrix multiplication disc = get_discretisation_for_testing() @@ -948,6 +975,18 @@ def test_secondary_broadcast_2D(self): (mesh["negative particle"][0].npts * mesh["negative electrode"][0].npts, 1), ) + # test broadcast to edges + broad_to_edges = pybamm.SecondaryBroadcastToEdges(var, "negative electrode") + disc.set_variable_slices([var]) + broad_to_edges_disc = disc.process_symbol(broad_to_edges) + self.assertIsInstance(broad_to_edges_disc, pybamm.MatrixMultiplication) + self.assertIsInstance(broad_to_edges_disc.children[0], pybamm.Matrix) + self.assertIsInstance(broad_to_edges_disc.children[1], pybamm.StateVector) + self.assertEqual( + broad_to_edges_disc.shape, + (mesh["negative particle"][0].npts * mesh["negative electrode"][0].npts, 1), + ) + def test_concatenation(self): a = pybamm.Symbol("a") b = pybamm.Symbol("b") diff --git a/tests/unit/test_expression_tree/test_broadcasts.py b/tests/unit/test_expression_tree/test_broadcasts.py index 8febf50bc5..137728e0d0 100644 --- a/tests/unit/test_expression_tree/test_broadcasts.py +++ b/tests/unit/test_expression_tree/test_broadcasts.py @@ -112,6 +112,32 @@ def test_ones_like(self): self.assertEqual(ones_like_ab.domain, a.domain) self.assertEqual(ones_like_ab.auxiliary_domains, a.auxiliary_domains) + def test_broadcast_to_edges(self): + a = pybamm.Symbol("a") + broad_a = pybamm.PrimaryBroadcastToEdges(a, ["negative electrode"]) + self.assertEqual(broad_a.name, "broadcast to edges") + self.assertEqual(broad_a.children[0].name, a.name) + self.assertEqual(broad_a.domain, ["negative electrode"]) + + a = pybamm.Symbol( + "a", + domain=["negative particle"], + auxiliary_domains={"secondary": "current collector"}, + ) + broad_a = pybamm.SecondaryBroadcastToEdges(a, ["negative electrode"]) + self.assertEqual(broad_a.domain, ["negative particle"]) + self.assertEqual( + broad_a.auxiliary_domains, + {"secondary": ["negative electrode"], "tertiary": ["current collector"]}, + ) + + a = pybamm.Symbol("a") + broad_a = pybamm.FullBroadcastToEdges( + a, ["negative electrode"], "current collector" + ) + self.assertEqual(broad_a.domain, ["negative electrode"]) + self.assertEqual(broad_a.auxiliary_domains["secondary"], ["current collector"]) + if __name__ == "__main__": print("Add -v for more debug output") diff --git a/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py b/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py index cc74227b3b..3732dec521 100644 --- a/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py +++ b/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py @@ -853,20 +853,38 @@ def test_indefinite_integral(self): left_boundary_value_disc.evaluate(y=c_exact), 0 ) - def test_indefinite_integral_on_nodes(self): + def test_indefinite_integral_of_broadcasted_to_cell_edges(self): + # create discretisation mesh = get_mesh_for_testing() - spatial_methods = {"macroscale": pybamm.FiniteVolume()} + spatial_methods = { + "macroscale": pybamm.FiniteVolume(), + "negative particle": pybamm.FiniteVolume(), + "positive particle": pybamm.FiniteVolume(), + "current collector": pybamm.ZeroDimensionalMethod(), + } disc = pybamm.Discretisation(mesh, spatial_methods) # input a phi, take grad, then integrate to recover phi approximation # (need to test this way as check evaluated on edges using if has grad # and no div) phi = pybamm.Variable("phi", domain=["negative electrode", "separator"]) + i = pybamm.PrimaryBroadcastToEdges(1, phi.domain) + + x = pybamm.SpatialVariable("x", ["negative electrode", "separator"]) + int_phi_av = pybamm.x_average(pybamm.IndefiniteIntegral(i / phi ** 2, x)) + disc.set_variable_slices([phi]) + int_phi_disc = disc.process_symbol(int_phi_av) + + def test_indefinite_integral_on_nodes(self): + mesh = get_mesh_for_testing() + spatial_methods = {"macroscale": pybamm.FiniteVolume()} + disc = pybamm.Discretisation(mesh, spatial_methods) + phi = pybamm.Variable("phi", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", ["negative electrode", "separator"]) + int_phi = pybamm.IndefiniteIntegral(phi, x) disc.set_variable_slices([phi]) - # Set boundary conditions (required for shape but don't matter) int_phi_disc = disc.process_symbol(int_phi) combined_submesh = mesh.combine_submeshes("negative electrode", "separator") From 59a989ccc93445956fad900929f93840574db5c3 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Fri, 13 Mar 2020 20:45:54 -0400 Subject: [PATCH 2/7] #609 add broadcast to edges and pass tests --- docs/source/expression_tree/broadcasts.rst | 11 + pybamm/discretisations/discretisation.py | 4 +- pybamm/expression_tree/broadcasts.py | 59 ++++- ...urface_form_stefan_maxwell_conductivity.py | 2 +- pybamm/spatial_methods/finite_volume.py | 2 +- .../spatial_methods/scikit_finite_element.py | 2 +- pybamm/spatial_methods/spatial_method.py | 31 ++- .../test_lithium_ion/test_dfn.py | 210 +++++++++--------- .../test_discretisation.py | 19 +- .../test_expression_tree/test_broadcasts.py | 3 + .../test_finite_volume/test_finite_volume.py | 28 ++- 11 files changed, 225 insertions(+), 146 deletions(-) diff --git a/docs/source/expression_tree/broadcasts.rst b/docs/source/expression_tree/broadcasts.rst index 743e48ce79..d4f58fe275 100644 --- a/docs/source/expression_tree/broadcasts.rst +++ b/docs/source/expression_tree/broadcasts.rst @@ -12,3 +12,14 @@ Broadcasting Operators .. autoclass:: pybamm.SecondaryBroadcast :members: + +.. autoclass:: pybamm.FullBroadcastToEdges + :members: + +.. autoclass:: pybamm.PrimaryBroadcastToEdges + :members: + +.. autoclass:: pybamm.SecondaryBroadcastToEdges + :members: + +.. autofunction:: pybamm.ones_like diff --git a/pybamm/discretisations/discretisation.py b/pybamm/discretisations/discretisation.py index 8f4e4ea3a5..d12c127ef3 100644 --- a/pybamm/discretisations/discretisation.py +++ b/pybamm/discretisations/discretisation.py @@ -226,7 +226,7 @@ def set_variable_slices(self, variables): for child, mesh in meshes.items(): for domain_mesh in mesh: submesh = domain_mesh[i] - end += submesh.npts_for_broadcast + end += submesh.npts_for_broadcast_to_nodes y_slices[child.id].append(slice(start, end)) start = end else: @@ -249,7 +249,7 @@ def _get_variable_size(self, variable): size = 0 for dom in variable.domain: for submesh in self.spatial_methods[dom].mesh[dom]: - size += submesh.npts_for_broadcast + size += submesh.npts_for_broadcast_to_nodes return size def _preprocess_external_variables(self, model): diff --git a/pybamm/expression_tree/broadcasts.py b/pybamm/expression_tree/broadcasts.py index 31228d8085..2f85f11e7a 100644 --- a/pybamm/expression_tree/broadcasts.py +++ b/pybamm/expression_tree/broadcasts.py @@ -37,7 +37,7 @@ def __init__( child, broadcast_domain, broadcast_auxiliary_domains=None, - broadcast_type="full", + broadcast_type="full to nodes", name=None, ): # Convert child to scalar if it is a number @@ -84,7 +84,9 @@ class PrimaryBroadcast(Broadcast): """ def __init__(self, child, broadcast_domain, name=None): - super().__init__(child, broadcast_domain, broadcast_type="primary", name=name) + super().__init__( + child, broadcast_domain, broadcast_type="primary to nodes", name=name + ) def check_and_set_domains( self, child, broadcast_type, broadcast_domain, broadcast_auxiliary_domains @@ -127,8 +129,8 @@ def check_and_set_domains( return domain, auxiliary_domains def _unary_new_copy(self, child): - """ See :meth:`pybamm.UnaryOperator.simplify()`. """ - return PrimaryBroadcast(child, self.broadcast_domain) + """ See :meth:`pybamm.UnaryOperator._unary_new_copy()`. """ + return self.__class__(child, self.broadcast_domain) def _evaluate_for_shape(self): """ @@ -140,6 +142,18 @@ def _evaluate_for_shape(self): return np.outer(child_eval, vec).reshape(-1, 1) +class PrimaryBroadcastToEdges(PrimaryBroadcast): + "A primary broadcast onto the edges of the domain" + + def __init__(self, child, broadcast_domain, name=None): + name = name or "broadcast to edges" + super().__init__(child, broadcast_domain, name) + self.broadcast_type = "primary to edges" + + def evaluates_on_edges(self): + return True + + class SecondaryBroadcast(Broadcast): """A node in the expression tree representing a primary broadcasting operator. Broadcasts in a `secondary` dimension only. That is, makes explicit copies of the @@ -162,7 +176,9 @@ class SecondaryBroadcast(Broadcast): """ def __init__(self, child, broadcast_domain, name=None): - super().__init__(child, broadcast_domain, broadcast_type="secondary", name=name) + super().__init__( + child, broadcast_domain, broadcast_type="secondary to nodes", name=name + ) def check_and_set_domains( self, child, broadcast_type, broadcast_domain, broadcast_auxiliary_domains @@ -207,7 +223,7 @@ def check_and_set_domains( return domain, auxiliary_domains def _unary_new_copy(self, child): - """ See :meth:`pybamm.UnaryOperator.simplify()`. """ + """ See :meth:`pybamm.UnaryOperator._unary_new_copy()`. """ return SecondaryBroadcast(child, self.broadcast_domain) def _evaluate_for_shape(self): @@ -220,6 +236,18 @@ def _evaluate_for_shape(self): return np.outer(vec, child_eval).reshape(-1, 1) +class SecondaryBroadcastToEdges(SecondaryBroadcast): + "A secondary broadcast onto the edges of a domain" + + def __init__(self, child, broadcast_domain, name=None): + name = name or "broadcast to edges" + super().__init__(child, broadcast_domain, name) + self.broadcast_type = "secondary to edges" + + def evaluates_on_edges(self): + return True + + class FullBroadcast(Broadcast): "A class for full broadcasts" @@ -230,7 +258,7 @@ def __init__(self, child, broadcast_domain, auxiliary_domains, name=None): child, broadcast_domain, broadcast_auxiliary_domains=auxiliary_domains, - broadcast_type="full", + broadcast_type="full to nodes", name=name, ) @@ -250,7 +278,7 @@ def check_and_set_domains( return domain, auxiliary_domains def _unary_new_copy(self, child): - """ See :meth:`pybamm.UnaryOperator.simplify()`. """ + """ See :meth:`pybamm.UnaryOperator._unary_new_copy()`. """ return FullBroadcast(child, self.broadcast_domain, self.auxiliary_domains) def _evaluate_for_shape(self): @@ -266,6 +294,21 @@ def _evaluate_for_shape(self): return child_eval * vec +class FullBroadcastToEdges(FullBroadcast): + """ + A full broadcast onto the edges of a domain (edges of primary dimension, nodes of + other dimensions) + """ + + def __init__(self, child, broadcast_domain, auxiliary_domains, name=None): + name = name or "broadcast to edges" + super().__init__(child, broadcast_domain, auxiliary_domains, name) + self.broadcast_type = "full to edges" + + def evaluates_on_edges(self): + return True + + def ones_like(*symbols): """ Create a symbol with the same shape as the input symbol and with constant value '1', diff --git a/pybamm/models/submodels/electrolyte/stefan_maxwell/conductivity/surface_potential_form/full_surface_form_stefan_maxwell_conductivity.py b/pybamm/models/submodels/electrolyte/stefan_maxwell/conductivity/surface_potential_form/full_surface_form_stefan_maxwell_conductivity.py index 945437ac41..ec55d41038 100644 --- a/pybamm/models/submodels/electrolyte/stefan_maxwell/conductivity/surface_potential_form/full_surface_form_stefan_maxwell_conductivity.py +++ b/pybamm/models/submodels/electrolyte/stefan_maxwell/conductivity/surface_potential_form/full_surface_form_stefan_maxwell_conductivity.py @@ -172,7 +172,7 @@ def _get_neg_pos_coupled_variables(self, variables): phi_e = phi_s - delta_phi variables.update(self._get_domain_potential_variables(phi_e)) - + variables.update({"test": pybamm.x_average(phi_s)}) return variables def _get_sep_coupled_variables(self, variables): diff --git a/pybamm/spatial_methods/finite_volume.py b/pybamm/spatial_methods/finite_volume.py index 03ce66a244..ccc3e1dd88 100644 --- a/pybamm/spatial_methods/finite_volume.py +++ b/pybamm/spatial_methods/finite_volume.py @@ -40,7 +40,7 @@ def build(self, mesh): # add npts_for_broadcast to mesh domains for this particular discretisation for dom in mesh.keys(): for i in range(len(mesh[dom])): - mesh[dom][i].npts_for_broadcast = mesh[dom][i].npts + mesh[dom][i].npts_for_broadcast_to_nodes = mesh[dom][i].npts def spatial_variable(self, symbol): """ diff --git a/pybamm/spatial_methods/scikit_finite_element.py b/pybamm/spatial_methods/scikit_finite_element.py index 48f01d3934..b384481e9f 100644 --- a/pybamm/spatial_methods/scikit_finite_element.py +++ b/pybamm/spatial_methods/scikit_finite_element.py @@ -36,7 +36,7 @@ def build(self, mesh): # add npts_for_broadcast to mesh domains for this particular discretisation for dom in mesh.keys(): for i in range(len(mesh[dom])): - mesh[dom][i].npts_for_broadcast = mesh[dom][i].npts + mesh[dom][i].npts_for_broadcast_to_nodes = mesh[dom][i].npts def spatial_variable(self, symbol): """ diff --git a/pybamm/spatial_methods/spatial_method.py b/pybamm/spatial_methods/spatial_method.py index 1976b806c0..04b6c7d82c 100644 --- a/pybamm/spatial_methods/spatial_method.py +++ b/pybamm/spatial_methods/spatial_method.py @@ -38,7 +38,7 @@ def build(self, mesh): # add npts_for_broadcast to mesh domains for this particular discretisation for dom in mesh.keys(): for i in range(len(mesh[dom])): - mesh[dom][i].npts_for_broadcast = mesh[dom][i].npts + mesh[dom][i].npts_for_broadcast_to_nodes = mesh[dom][i].npts self._mesh = mesh @property @@ -81,7 +81,8 @@ def broadcast(self, symbol, domain, auxiliary_domains, broadcast_type): domain : iterable of strings The domain to broadcast to broadcast_type : str - The type of broadcast, either: 'primary' or 'full' + The type of broadcast: 'primary to node', 'primary to edges', 'secondary to + nodes', 'secondary to edges', 'full to nodes' or 'full to edges' Returns ------- @@ -90,14 +91,24 @@ def broadcast(self, symbol, domain, auxiliary_domains, broadcast_type): """ primary_domain_size = sum( - self.mesh[dom][0].npts_for_broadcast for dom in domain + self.mesh[dom][0].npts_for_broadcast_to_nodes for dom in domain ) - + secondary_domain_size = sum( + self.mesh[dom][0].npts_for_broadcast_to_nodes + for dom in auxiliary_domains.get("secondary", []) + ) # returns empty list if auxiliary_domains doesn't have "secondary" key full_domain_size = sum( - subdom.npts_for_broadcast for dom in domain for subdom in self.mesh[dom] + subdom.npts_for_broadcast_to_nodes + for dom in domain + for subdom in self.mesh[dom] ) + if broadcast_type.endswith("to edges"): + # add one point to each domain for broadcasting to edges + primary_domain_size += 1 + secondary_domain_size += 1 + full_domain_size += 1 - if broadcast_type == "primary": + if broadcast_type.startswith("primary"): # Make copies of the child stacked on top of each other sub_vector = np.ones((primary_domain_size, 1)) if symbol.shape_for_testing == (): @@ -107,11 +118,7 @@ def broadcast(self, symbol, domain, auxiliary_domains, broadcast_type): matrix = csr_matrix(kron(eye(symbol.shape_for_testing[0]), sub_vector)) out = pybamm.Matrix(matrix) @ symbol out.domain = domain - elif broadcast_type == "secondary": - secondary_domain_size = sum( - self.mesh[dom][0].npts_for_broadcast - for dom in auxiliary_domains["secondary"] - ) + elif broadcast_type.startswith("secondary"): kron_size = full_domain_size // primary_domain_size # Symbol may be on edges so need to calculate size carefully symbol_primary_size = symbol.shape[0] // kron_size @@ -121,7 +128,7 @@ def broadcast(self, symbol, domain, auxiliary_domains, broadcast_type): # Repeat for secondary points matrix = csr_matrix(kron(eye(kron_size), sub_matrix)) out = pybamm.Matrix(matrix) @ symbol - elif broadcast_type == "full": + elif broadcast_type.startswith("full"): out = symbol * pybamm.Vector(np.ones(full_domain_size), domain=domain) out.auxiliary_domains = auxiliary_domains diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py index be6eb491f8..cee8fde78e 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py @@ -9,91 +9,91 @@ class TestDFN(unittest.TestCase): - def test_basic_processing(self): - options = {"thermal": "isothermal"} - model = pybamm.lithium_ion.DFN(options) - var = pybamm.standard_spatial_vars - var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} - modeltest = tests.StandardModelTest(model, var_pts=var_pts) - modeltest.test_all() - - def test_basic_processing_1plus1D(self): - options = {"current collector": "potential pair", "dimensionality": 1} - model = pybamm.lithium_ion.DFN(options) - var = pybamm.standard_spatial_vars - var_pts = { - var.x_n: 5, - var.x_s: 5, - var.x_p: 5, - var.r_n: 5, - var.r_p: 5, - var.y: 5, - var.z: 5, - } - modeltest = tests.StandardModelTest(model, var_pts=var_pts) - modeltest.test_all(skip_output_tests=True) - - def test_basic_processing_2plus1D(self): - options = {"current collector": "potential pair", "dimensionality": 2} - model = pybamm.lithium_ion.DFN(options) - var = pybamm.standard_spatial_vars - var_pts = { - var.x_n: 5, - var.x_s: 5, - var.x_p: 5, - var.r_n: 5, - var.r_p: 5, - var.y: 5, - var.z: 5, - } - modeltest = tests.StandardModelTest(model, var_pts=var_pts) - modeltest.test_all(skip_output_tests=True) - - def test_optimisations(self): - options = {"thermal": "isothermal"} - model = pybamm.lithium_ion.DFN(options) - optimtest = tests.OptimisationsTest(model) - - original = optimtest.evaluate_model() - simplified = optimtest.evaluate_model(simplify=True) - using_known_evals = optimtest.evaluate_model(use_known_evals=True) - simp_and_known = optimtest.evaluate_model(simplify=True, use_known_evals=True) - simp_and_python = optimtest.evaluate_model(simplify=True, to_python=True) - np.testing.assert_array_almost_equal(original, simplified) - np.testing.assert_array_almost_equal(original, using_known_evals) - np.testing.assert_array_almost_equal(original, simp_and_known) - - np.testing.assert_array_almost_equal(original, simp_and_python) - - def test_set_up(self): - model = pybamm.lithium_ion.DFN() - optimtest = tests.OptimisationsTest(model) - optimtest.set_up_model(simplify=False, to_python=True) - optimtest.set_up_model(simplify=False, to_python=False) - optimtest.set_up_model(simplify=True, to_python=True) - optimtest.set_up_model(simplify=True, to_python=False) - - def test_full_thermal(self): - options = {"thermal": "x-full"} - model = pybamm.lithium_ion.DFN(options) - var = pybamm.standard_spatial_vars - var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} - modeltest = tests.StandardModelTest(model, var_pts=var_pts) - modeltest.test_all() - - def test_lumped_thermal(self): - options = {"thermal": "x-lumped"} - model = pybamm.lithium_ion.DFN(options) - var = pybamm.standard_spatial_vars - var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} - modeltest = tests.StandardModelTest(model, var_pts=var_pts) - modeltest.test_all() - - def test_particle_fast_diffusion(self): - options = {"particle": "fast diffusion"} - model = pybamm.lithium_ion.DFN(options) - modeltest = tests.StandardModelTest(model) - modeltest.test_all() + # def test_basic_processing(self): + # options = {"thermal": "isothermal"} + # model = pybamm.lithium_ion.DFN(options) + # var = pybamm.standard_spatial_vars + # var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} + # modeltest = tests.StandardModelTest(model, var_pts=var_pts) + # modeltest.test_all() + + # def test_basic_processing_1plus1D(self): + # options = {"current collector": "potential pair", "dimensionality": 1} + # model = pybamm.lithium_ion.DFN(options) + # var = pybamm.standard_spatial_vars + # var_pts = { + # var.x_n: 5, + # var.x_s: 5, + # var.x_p: 5, + # var.r_n: 5, + # var.r_p: 5, + # var.y: 5, + # var.z: 5, + # } + # modeltest = tests.StandardModelTest(model, var_pts=var_pts) + # modeltest.test_all(skip_output_tests=True) + + # def test_basic_processing_2plus1D(self): + # options = {"current collector": "potential pair", "dimensionality": 2} + # model = pybamm.lithium_ion.DFN(options) + # var = pybamm.standard_spatial_vars + # var_pts = { + # var.x_n: 5, + # var.x_s: 5, + # var.x_p: 5, + # var.r_n: 5, + # var.r_p: 5, + # var.y: 5, + # var.z: 5, + # } + # modeltest = tests.StandardModelTest(model, var_pts=var_pts) + # modeltest.test_all(skip_output_tests=True) + + # def test_optimisations(self): + # options = {"thermal": "isothermal"} + # model = pybamm.lithium_ion.DFN(options) + # optimtest = tests.OptimisationsTest(model) + + # original = optimtest.evaluate_model() + # simplified = optimtest.evaluate_model(simplify=True) + # using_known_evals = optimtest.evaluate_model(use_known_evals=True) + # simp_and_known = optimtest.evaluate_model(simplify=True, use_known_evals=True) + # simp_and_python = optimtest.evaluate_model(simplify=True, to_python=True) + # np.testing.assert_array_almost_equal(original, simplified) + # np.testing.assert_array_almost_equal(original, using_known_evals) + # np.testing.assert_array_almost_equal(original, simp_and_known) + + # np.testing.assert_array_almost_equal(original, simp_and_python) + + # def test_set_up(self): + # model = pybamm.lithium_ion.DFN() + # optimtest = tests.OptimisationsTest(model) + # optimtest.set_up_model(simplify=False, to_python=True) + # optimtest.set_up_model(simplify=False, to_python=False) + # optimtest.set_up_model(simplify=True, to_python=True) + # optimtest.set_up_model(simplify=True, to_python=False) + + # def test_full_thermal(self): + # options = {"thermal": "x-full"} + # model = pybamm.lithium_ion.DFN(options) + # var = pybamm.standard_spatial_vars + # var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} + # modeltest = tests.StandardModelTest(model, var_pts=var_pts) + # modeltest.test_all() + + # def test_lumped_thermal(self): + # options = {"thermal": "x-lumped"} + # model = pybamm.lithium_ion.DFN(options) + # var = pybamm.standard_spatial_vars + # var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} + # modeltest = tests.StandardModelTest(model, var_pts=var_pts) + # modeltest.test_all() + + # def test_particle_fast_diffusion(self): + # options = {"particle": "fast diffusion"} + # model = pybamm.lithium_ion.DFN(options) + # modeltest = tests.StandardModelTest(model) + # modeltest.test_all() def test_surface_form_differential(self): options = {"surface form": "differential"} @@ -101,26 +101,26 @@ def test_surface_form_differential(self): modeltest = tests.StandardModelTest(model) modeltest.test_all() - def test_surface_form_algebraic(self): - options = {"surface form": "algebraic"} - model = pybamm.lithium_ion.DFN(options) - modeltest = tests.StandardModelTest(model) - modeltest.test_all() - - def test_particle_distribution_in_x(self): - model = pybamm.lithium_ion.DFN() - param = model.default_parameter_values - - def negative_distribution(x): - return 1 + x - - def positive_distribution(x): - return 1 + (x - (1 - model.param.l_p)) - - param["Negative particle distribution in x"] = negative_distribution - param["Positive particle distribution in x"] = positive_distribution - modeltest = tests.StandardModelTest(model, parameter_values=param) - modeltest.test_all() + # def test_surface_form_algebraic(self): + # options = {"surface form": "algebraic"} + # model = pybamm.lithium_ion.DFN(options) + # modeltest = tests.StandardModelTest(model) + # modeltest.test_all() + # + # def test_particle_distribution_in_x(self): + # model = pybamm.lithium_ion.DFN() + # param = model.default_parameter_values + + # def negative_distribution(x): + # return 1 + x + + # def positive_distribution(x): + # return 1 + (x - (1 - model.param.l_p)) + + # param["Negative particle distribution in x"] = negative_distribution + # param["Positive particle distribution in x"] = positive_distribution + # modeltest = tests.StandardModelTest(model, parameter_values=param) + # modeltest.test_all() if __name__ == "__main__": diff --git a/tests/unit/test_discretisations/test_discretisation.py b/tests/unit/test_discretisations/test_discretisation.py index 12b99cd542..8ac0d14383 100644 --- a/tests/unit/test_discretisations/test_discretisation.py +++ b/tests/unit/test_discretisations/test_discretisation.py @@ -910,15 +910,12 @@ def test_broadcast(self): self.assertIsInstance(broad1_disc.children[1], pybamm.Vector) # broadcast to edges - broad_edges = pybamm.FullBroadcastToEdges(a, ["negative electrode"], None) - broad_edges_disc = disc.process_symbol(broad_edges) + broad_to_edges = pybamm.FullBroadcastToEdges(a, ["negative electrode"], None) + broad_to_edges_disc = disc.process_symbol(broad_to_edges) np.testing.assert_array_equal( - broad_edges_disc.evaluate(u={"a": 7}), - 7 * np.ones_like(combined_submesh[0].edges[:, np.newaxis]), + broad_to_edges_disc.evaluate(u={"a": 7}), + 7 * np.ones_like(mesh["negative electrode"][0].edges[:, np.newaxis]), ) - self.assertIsInstance(broad_edges_disc, pybamm.Multiplication) - self.assertIsInstance(broad_edges_disc.children[0], pybamm.StateVector) - self.assertIsInstance(broad_edges_disc.children[1], pybamm.Vector) def test_broadcast_2D(self): # broadcast in 2D --> MatrixMultiplication @@ -954,7 +951,7 @@ def test_broadcast_2D(self): ) y_test = np.linspace(0, 1, mesh["current collector"][0].npts) np.testing.assert_array_equal( - broad_disc.evaluate(y=y_test), + broad_to_edges_disc.evaluate(y=y_test), np.outer(y_test, np.ones(mesh["separator"][0].npts + 1)).reshape(-1, 1), ) @@ -984,7 +981,11 @@ def test_secondary_broadcast_2D(self): self.assertIsInstance(broad_to_edges_disc.children[1], pybamm.StateVector) self.assertEqual( broad_to_edges_disc.shape, - (mesh["negative particle"][0].npts * mesh["negative electrode"][0].npts, 1), + ( + mesh["negative particle"][0].npts + * (mesh["negative electrode"][0].npts + 1), + 1, + ), ) def test_concatenation(self): diff --git a/tests/unit/test_expression_tree/test_broadcasts.py b/tests/unit/test_expression_tree/test_broadcasts.py index 137728e0d0..d666f67e12 100644 --- a/tests/unit/test_expression_tree/test_broadcasts.py +++ b/tests/unit/test_expression_tree/test_broadcasts.py @@ -118,6 +118,7 @@ def test_broadcast_to_edges(self): self.assertEqual(broad_a.name, "broadcast to edges") self.assertEqual(broad_a.children[0].name, a.name) self.assertEqual(broad_a.domain, ["negative electrode"]) + self.assertTrue(broad_a.evaluates_on_edges()) a = pybamm.Symbol( "a", @@ -130,6 +131,7 @@ def test_broadcast_to_edges(self): broad_a.auxiliary_domains, {"secondary": ["negative electrode"], "tertiary": ["current collector"]}, ) + self.assertTrue(broad_a.evaluates_on_edges()) a = pybamm.Symbol("a") broad_a = pybamm.FullBroadcastToEdges( @@ -137,6 +139,7 @@ def test_broadcast_to_edges(self): ) self.assertEqual(broad_a.domain, ["negative electrode"]) self.assertEqual(broad_a.auxiliary_domains["secondary"], ["current collector"]) + self.assertTrue(broad_a.evaluates_on_edges()) if __name__ == "__main__": diff --git a/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py b/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py index 3732dec521..648e3c5c7a 100644 --- a/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py +++ b/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py @@ -864,16 +864,30 @@ def test_indefinite_integral_of_broadcasted_to_cell_edges(self): } disc = pybamm.Discretisation(mesh, spatial_methods) - # input a phi, take grad, then integrate to recover phi approximation - # (need to test this way as check evaluated on edges using if has grad - # and no div) + # make a variable 'phi' and a vector 'i' which is broadcast onto edges + # the integral of this should then be put onto the nodes phi = pybamm.Variable("phi", domain=["negative electrode", "separator"]) i = pybamm.PrimaryBroadcastToEdges(1, phi.domain) - - x = pybamm.SpatialVariable("x", ["negative electrode", "separator"]) - int_phi_av = pybamm.x_average(pybamm.IndefiniteIntegral(i / phi ** 2, x)) + x = pybamm.SpatialVariable("x", phi.domain) disc.set_variable_slices([phi]) - int_phi_disc = disc.process_symbol(int_phi_av) + combined_submesh = mesh.combine_submeshes("negative electrode", "separator") + x_end = combined_submesh[0].edges[-1] + + # take indefinite integral + int_phi = pybamm.IndefiniteIntegral(i * phi, x) + # take integral again + int_int_phi = pybamm.Integral(int_phi, x) + int_int_phi_disc = disc.process_symbol(int_int_phi) + + # constant case + phi_exact = np.ones_like(combined_submesh[0].nodes) + phi_approx = int_int_phi_disc.evaluate(None, phi_exact) + np.testing.assert_array_equal(x_end ** 2 / 2, phi_approx) + + # linear case + phi_exact = combined_submesh[0].nodes[:, np.newaxis] + phi_approx = int_int_phi_disc.evaluate(None, phi_exact) + np.testing.assert_array_almost_equal(x_end ** 3 / 6, phi_approx, decimal=4) def test_indefinite_integral_on_nodes(self): mesh = get_mesh_for_testing() From f8053e5ab667bde9b7d0a6f5b269a2b91c54257c Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Sat, 14 Mar 2020 12:20:50 -0400 Subject: [PATCH 3/7] #609 raise error if taking average of symbol that evaluates on edges --- pybamm/expression_tree/unary_operators.py | 67 +++--- .../test_lithium_ion/test_dfn.py | 210 +++++++++--------- .../test_unary_operators.py | 23 +- 3 files changed, 165 insertions(+), 135 deletions(-) diff --git a/pybamm/expression_tree/unary_operators.py b/pybamm/expression_tree/unary_operators.py index f8fbc9c98e..5dfbe37d3e 100644 --- a/pybamm/expression_tree/unary_operators.py +++ b/pybamm/expression_tree/unary_operators.py @@ -901,6 +901,9 @@ def x_average(symbol): :class:`Symbol` the new averaged symbol """ + # Can't take average if the symbol evaluates on edges + if symbol.evaluates_on_edges(): + raise ValueError("Can't take the x-average of a symbol that evaluates on edges") # If symbol doesn't have a domain, its average value is itself if symbol.domain in [[], ["current collector"]]: new_symbol = symbol.new_copy() @@ -963,6 +966,9 @@ def z_average(symbol): :class:`Symbol` the new averaged symbol """ + # Can't take average if the symbol evaluates on edges + if symbol.evaluates_on_edges(): + raise ValueError("Can't take the z-average of a symbol that evaluates on edges") # Symbol must have domain [] or ["current collector"] if symbol.domain not in [[], ["current collector"]]: raise pybamm.DomainError( @@ -1024,6 +1030,38 @@ def yz_average(symbol): return Integral(symbol, [y, z]) / (l_y * l_z) +def r_average(symbol): + """convenience function for creating an average in the r-direction + + Parameters + ---------- + symbol : :class:`pybamm.Symbol` + The function to be averaged + + Returns + ------- + :class:`Symbol` + the new averaged symbol + """ + # Can't take average if the symbol evaluates on edges + if symbol.evaluates_on_edges(): + raise ValueError("Can't take the r-average of a symbol that evaluates on edges") + # If symbol doesn't have a particle domain, its r-averaged value is itself + if symbol.domain not in [["positive particle"], ["negative particle"]]: + new_symbol = symbol.new_copy() + new_symbol.parent = None + return new_symbol + # If symbol is a Broadcast, its average value is its child + elif isinstance(symbol, pybamm.Broadcast): + return symbol.orphans[0] + else: + r = pybamm.SpatialVariable("r", symbol.domain) + v = pybamm.FullBroadcast( + pybamm.Scalar(1), symbol.domain, symbol.auxiliary_domains + ) + return Integral(symbol, r) / Integral(v, r) + + def boundary_value(symbol, side): """convenience function for creating a :class:`pybamm.BoundaryValue` @@ -1061,35 +1099,6 @@ def boundary_value(symbol, side): return BoundaryValue(symbol, side) -def r_average(symbol): - """convenience function for creating an average in the r-direction - - Parameters - ---------- - symbol : :class:`pybamm.Symbol` - The function to be averaged - - Returns - ------- - :class:`Symbol` - the new averaged symbol - """ - # If symbol doesn't have a particle domain, its r-averaged value is itself - if symbol.domain not in [["positive particle"], ["negative particle"]]: - new_symbol = symbol.new_copy() - new_symbol.parent = None - return new_symbol - # If symbol is a Broadcast, its average value is its child - elif isinstance(symbol, pybamm.Broadcast): - return symbol.orphans[0] - else: - r = pybamm.SpatialVariable("r", symbol.domain) - v = pybamm.FullBroadcast( - pybamm.Scalar(1), symbol.domain, symbol.auxiliary_domains - ) - return Integral(symbol, r) / Integral(v, r) - - def sign(symbol): " Returns a :class:`Sign` object. " return Sign(symbol) diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py index cee8fde78e..be6eb491f8 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py @@ -9,91 +9,91 @@ class TestDFN(unittest.TestCase): - # def test_basic_processing(self): - # options = {"thermal": "isothermal"} - # model = pybamm.lithium_ion.DFN(options) - # var = pybamm.standard_spatial_vars - # var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} - # modeltest = tests.StandardModelTest(model, var_pts=var_pts) - # modeltest.test_all() - - # def test_basic_processing_1plus1D(self): - # options = {"current collector": "potential pair", "dimensionality": 1} - # model = pybamm.lithium_ion.DFN(options) - # var = pybamm.standard_spatial_vars - # var_pts = { - # var.x_n: 5, - # var.x_s: 5, - # var.x_p: 5, - # var.r_n: 5, - # var.r_p: 5, - # var.y: 5, - # var.z: 5, - # } - # modeltest = tests.StandardModelTest(model, var_pts=var_pts) - # modeltest.test_all(skip_output_tests=True) - - # def test_basic_processing_2plus1D(self): - # options = {"current collector": "potential pair", "dimensionality": 2} - # model = pybamm.lithium_ion.DFN(options) - # var = pybamm.standard_spatial_vars - # var_pts = { - # var.x_n: 5, - # var.x_s: 5, - # var.x_p: 5, - # var.r_n: 5, - # var.r_p: 5, - # var.y: 5, - # var.z: 5, - # } - # modeltest = tests.StandardModelTest(model, var_pts=var_pts) - # modeltest.test_all(skip_output_tests=True) - - # def test_optimisations(self): - # options = {"thermal": "isothermal"} - # model = pybamm.lithium_ion.DFN(options) - # optimtest = tests.OptimisationsTest(model) - - # original = optimtest.evaluate_model() - # simplified = optimtest.evaluate_model(simplify=True) - # using_known_evals = optimtest.evaluate_model(use_known_evals=True) - # simp_and_known = optimtest.evaluate_model(simplify=True, use_known_evals=True) - # simp_and_python = optimtest.evaluate_model(simplify=True, to_python=True) - # np.testing.assert_array_almost_equal(original, simplified) - # np.testing.assert_array_almost_equal(original, using_known_evals) - # np.testing.assert_array_almost_equal(original, simp_and_known) - - # np.testing.assert_array_almost_equal(original, simp_and_python) - - # def test_set_up(self): - # model = pybamm.lithium_ion.DFN() - # optimtest = tests.OptimisationsTest(model) - # optimtest.set_up_model(simplify=False, to_python=True) - # optimtest.set_up_model(simplify=False, to_python=False) - # optimtest.set_up_model(simplify=True, to_python=True) - # optimtest.set_up_model(simplify=True, to_python=False) - - # def test_full_thermal(self): - # options = {"thermal": "x-full"} - # model = pybamm.lithium_ion.DFN(options) - # var = pybamm.standard_spatial_vars - # var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} - # modeltest = tests.StandardModelTest(model, var_pts=var_pts) - # modeltest.test_all() - - # def test_lumped_thermal(self): - # options = {"thermal": "x-lumped"} - # model = pybamm.lithium_ion.DFN(options) - # var = pybamm.standard_spatial_vars - # var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} - # modeltest = tests.StandardModelTest(model, var_pts=var_pts) - # modeltest.test_all() - - # def test_particle_fast_diffusion(self): - # options = {"particle": "fast diffusion"} - # model = pybamm.lithium_ion.DFN(options) - # modeltest = tests.StandardModelTest(model) - # modeltest.test_all() + def test_basic_processing(self): + options = {"thermal": "isothermal"} + model = pybamm.lithium_ion.DFN(options) + var = pybamm.standard_spatial_vars + var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all() + + def test_basic_processing_1plus1D(self): + options = {"current collector": "potential pair", "dimensionality": 1} + model = pybamm.lithium_ion.DFN(options) + var = pybamm.standard_spatial_vars + var_pts = { + var.x_n: 5, + var.x_s: 5, + var.x_p: 5, + var.r_n: 5, + var.r_p: 5, + var.y: 5, + var.z: 5, + } + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all(skip_output_tests=True) + + def test_basic_processing_2plus1D(self): + options = {"current collector": "potential pair", "dimensionality": 2} + model = pybamm.lithium_ion.DFN(options) + var = pybamm.standard_spatial_vars + var_pts = { + var.x_n: 5, + var.x_s: 5, + var.x_p: 5, + var.r_n: 5, + var.r_p: 5, + var.y: 5, + var.z: 5, + } + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all(skip_output_tests=True) + + def test_optimisations(self): + options = {"thermal": "isothermal"} + model = pybamm.lithium_ion.DFN(options) + optimtest = tests.OptimisationsTest(model) + + original = optimtest.evaluate_model() + simplified = optimtest.evaluate_model(simplify=True) + using_known_evals = optimtest.evaluate_model(use_known_evals=True) + simp_and_known = optimtest.evaluate_model(simplify=True, use_known_evals=True) + simp_and_python = optimtest.evaluate_model(simplify=True, to_python=True) + np.testing.assert_array_almost_equal(original, simplified) + np.testing.assert_array_almost_equal(original, using_known_evals) + np.testing.assert_array_almost_equal(original, simp_and_known) + + np.testing.assert_array_almost_equal(original, simp_and_python) + + def test_set_up(self): + model = pybamm.lithium_ion.DFN() + optimtest = tests.OptimisationsTest(model) + optimtest.set_up_model(simplify=False, to_python=True) + optimtest.set_up_model(simplify=False, to_python=False) + optimtest.set_up_model(simplify=True, to_python=True) + optimtest.set_up_model(simplify=True, to_python=False) + + def test_full_thermal(self): + options = {"thermal": "x-full"} + model = pybamm.lithium_ion.DFN(options) + var = pybamm.standard_spatial_vars + var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all() + + def test_lumped_thermal(self): + options = {"thermal": "x-lumped"} + model = pybamm.lithium_ion.DFN(options) + var = pybamm.standard_spatial_vars + var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all() + + def test_particle_fast_diffusion(self): + options = {"particle": "fast diffusion"} + model = pybamm.lithium_ion.DFN(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() def test_surface_form_differential(self): options = {"surface form": "differential"} @@ -101,26 +101,26 @@ def test_surface_form_differential(self): modeltest = tests.StandardModelTest(model) modeltest.test_all() - # def test_surface_form_algebraic(self): - # options = {"surface form": "algebraic"} - # model = pybamm.lithium_ion.DFN(options) - # modeltest = tests.StandardModelTest(model) - # modeltest.test_all() - # - # def test_particle_distribution_in_x(self): - # model = pybamm.lithium_ion.DFN() - # param = model.default_parameter_values - - # def negative_distribution(x): - # return 1 + x - - # def positive_distribution(x): - # return 1 + (x - (1 - model.param.l_p)) - - # param["Negative particle distribution in x"] = negative_distribution - # param["Positive particle distribution in x"] = positive_distribution - # modeltest = tests.StandardModelTest(model, parameter_values=param) - # modeltest.test_all() + def test_surface_form_algebraic(self): + options = {"surface form": "algebraic"} + model = pybamm.lithium_ion.DFN(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_particle_distribution_in_x(self): + model = pybamm.lithium_ion.DFN() + param = model.default_parameter_values + + def negative_distribution(x): + return 1 + x + + def positive_distribution(x): + return 1 + (x - (1 - model.param.l_p)) + + param["Negative particle distribution in x"] = negative_distribution + param["Positive particle distribution in x"] = positive_distribution + modeltest = tests.StandardModelTest(model, parameter_values=param) + modeltest.test_all() if __name__ == "__main__": diff --git a/tests/unit/test_expression_tree/test_unary_operators.py b/tests/unit/test_expression_tree/test_unary_operators.py index 83ea1e81a0..3fad2f3c0e 100644 --- a/tests/unit/test_expression_tree/test_unary_operators.py +++ b/tests/unit/test_expression_tree/test_unary_operators.py @@ -269,7 +269,7 @@ def test_boundary_value(self): pybamm.boundary_value(var, "negative tab") pybamm.boundary_value(var, "positive tab") - def test_average(self): + def test_x_average(self): a = pybamm.Scalar(1) average_a = pybamm.x_average(a) self.assertEqual(average_a.id, a.id) @@ -311,6 +311,13 @@ def test_average(self): self.assertEqual(av_a.children[1].integration_variable[0].domain, a.domain) self.assertEqual(av_a.children[1].children[0].id, pybamm.ones_like(a).id) + # x-average of symbol that evaluates on edges raises error + symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") + with self.assertRaisesRegex( + ValueError, "Can't take the x-average of a symbol that evaluates on edges" + ): + pybamm.x_average(symbol_on_edges) + def test_r_average(self): a = pybamm.Scalar(1) average_a = pybamm.r_average(a) @@ -331,6 +338,13 @@ def test_r_average(self): # electrode domains go to current collector when averaged self.assertEqual(av_a.domain, []) + # r-average of symbol that evaluates on edges raises error + symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") + with self.assertRaisesRegex( + ValueError, "Can't take the r-average of a symbol that evaluates on edges" + ): + pybamm.r_average(symbol_on_edges) + def test_yz_average(self): a = pybamm.Scalar(1) z_average_a = pybamm.z_average(a) @@ -369,6 +383,13 @@ def test_yz_average(self): with self.assertRaises(pybamm.DomainError): pybamm.yz_average(a) + # average of symbol that evaluates on edges raises error + symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") + with self.assertRaisesRegex( + ValueError, "Can't take the z-average of a symbol that evaluates on edges" + ): + pybamm.z_average(symbol_on_edges) + if __name__ == "__main__": print("Add -v for more debug output") From 6604a49ba85eb140a7646efdcf64e7ce15c85863 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Sat, 14 Mar 2020 12:55:09 -0400 Subject: [PATCH 4/7] #609 reformat particle models --- .../particle/fast/base_fast_particle.rst | 5 --- .../particle/fast/fast_many_particles.rst | 5 --- .../particle/fast/fast_single_particle.rst | 5 --- .../models/submodels/particle/fast/index.rst | 8 ---- .../particle/fast_many_particles.rst | 5 +++ .../particle/fast_single_particle.rst | 5 +++ .../fickian/fickian_many_particles.rst | 7 ---- .../fickian/fickian_single_particle.rst | 6 --- .../submodels/particle/fickian/index.rst | 8 ---- .../particle/fickian_many_particles.rst | 7 ++++ .../particle/fickian_single_particle.rst | 6 +++ .../models/submodels/particle/index.rst | 6 ++- examples/notebooks/using-submodels.ipynb | 6 +-- examples/scripts/custom_model.py | 4 +- .../full_battery_models/lithium_ion/dfn.py | 8 ++-- .../full_battery_models/lithium_ion/spm.py | 16 ++++---- .../full_battery_models/lithium_ion/spme.py | 16 ++++---- pybamm/models/submodels/particle/__init__.py | 6 ++- .../submodels/particle/fast/__init__.py | 3 -- .../particle/fast/base_fast_particle.py | 37 ------------------- .../{fast => }/fast_many_particles.py | 19 +++++----- .../{fast => }/fast_single_particle.py | 32 +++++++++------- .../submodels/particle/fickian/__init__.py | 2 - .../{fickian => }/fickian_many_particles.py | 4 +- .../{fickian => }/fickian_single_particle.py | 4 +- .../test_fast/test_fast_many_particles.py | 4 +- .../test_fast/test_fast_single_particle.py | 4 +- .../test_fickian_many_particles.py | 4 +- .../test_fickian_single_particle.py | 4 +- 29 files changed, 97 insertions(+), 149 deletions(-) delete mode 100644 docs/source/models/submodels/particle/fast/base_fast_particle.rst delete mode 100644 docs/source/models/submodels/particle/fast/fast_many_particles.rst delete mode 100644 docs/source/models/submodels/particle/fast/fast_single_particle.rst delete mode 100644 docs/source/models/submodels/particle/fast/index.rst create mode 100644 docs/source/models/submodels/particle/fast_many_particles.rst create mode 100644 docs/source/models/submodels/particle/fast_single_particle.rst delete mode 100644 docs/source/models/submodels/particle/fickian/fickian_many_particles.rst delete mode 100644 docs/source/models/submodels/particle/fickian/fickian_single_particle.rst delete mode 100644 docs/source/models/submodels/particle/fickian/index.rst create mode 100644 docs/source/models/submodels/particle/fickian_many_particles.rst create mode 100644 docs/source/models/submodels/particle/fickian_single_particle.rst delete mode 100644 pybamm/models/submodels/particle/fast/__init__.py delete mode 100644 pybamm/models/submodels/particle/fast/base_fast_particle.py rename pybamm/models/submodels/particle/{fast => }/fast_many_particles.py (82%) rename pybamm/models/submodels/particle/{fast => }/fast_single_particle.py (75%) delete mode 100644 pybamm/models/submodels/particle/fickian/__init__.py rename pybamm/models/submodels/particle/{fickian => }/fickian_many_particles.py (97%) rename pybamm/models/submodels/particle/{fickian => }/fickian_single_particle.py (98%) diff --git a/docs/source/models/submodels/particle/fast/base_fast_particle.rst b/docs/source/models/submodels/particle/fast/base_fast_particle.rst deleted file mode 100644 index 0fd382e7f8..0000000000 --- a/docs/source/models/submodels/particle/fast/base_fast_particle.rst +++ /dev/null @@ -1,5 +0,0 @@ -Base Model -========== - -.. autoclass:: pybamm.particle.fast.BaseModel - :members: diff --git a/docs/source/models/submodels/particle/fast/fast_many_particles.rst b/docs/source/models/submodels/particle/fast/fast_many_particles.rst deleted file mode 100644 index 727edb6253..0000000000 --- a/docs/source/models/submodels/particle/fast/fast_many_particles.rst +++ /dev/null @@ -1,5 +0,0 @@ -Many Particle -============= - -.. autoclass:: pybamm.particle.fast.ManyParticles - :members: diff --git a/docs/source/models/submodels/particle/fast/fast_single_particle.rst b/docs/source/models/submodels/particle/fast/fast_single_particle.rst deleted file mode 100644 index 72990961dc..0000000000 --- a/docs/source/models/submodels/particle/fast/fast_single_particle.rst +++ /dev/null @@ -1,5 +0,0 @@ -Single Particle -=============== - -.. autoclass:: pybamm.particle.fast.SingleParticle - :members: diff --git a/docs/source/models/submodels/particle/fast/index.rst b/docs/source/models/submodels/particle/fast/index.rst deleted file mode 100644 index a565803646..0000000000 --- a/docs/source/models/submodels/particle/fast/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -Fast -==== - -.. toctree:: - - base_fast_particle - fast_many_particles - fast_single_particle diff --git a/docs/source/models/submodels/particle/fast_many_particles.rst b/docs/source/models/submodels/particle/fast_many_particles.rst new file mode 100644 index 0000000000..703a49047c --- /dev/null +++ b/docs/source/models/submodels/particle/fast_many_particles.rst @@ -0,0 +1,5 @@ +Fast Many Particles +=================== + +.. autoclass:: pybamm.particle.FastManyParticles + :members: diff --git a/docs/source/models/submodels/particle/fast_single_particle.rst b/docs/source/models/submodels/particle/fast_single_particle.rst new file mode 100644 index 0000000000..6c1de67f5c --- /dev/null +++ b/docs/source/models/submodels/particle/fast_single_particle.rst @@ -0,0 +1,5 @@ +Fast Single Particle +==================== + +.. autoclass:: pybamm.particle.FastSingleParticle + :members: diff --git a/docs/source/models/submodels/particle/fickian/fickian_many_particles.rst b/docs/source/models/submodels/particle/fickian/fickian_many_particles.rst deleted file mode 100644 index 4e27cf5d40..0000000000 --- a/docs/source/models/submodels/particle/fickian/fickian_many_particles.rst +++ /dev/null @@ -1,7 +0,0 @@ -Many Particle -============= - -.. autoclass:: pybamm.particle.fickian.ManyParticles - :members: - - diff --git a/docs/source/models/submodels/particle/fickian/fickian_single_particle.rst b/docs/source/models/submodels/particle/fickian/fickian_single_particle.rst deleted file mode 100644 index 96c6332a8c..0000000000 --- a/docs/source/models/submodels/particle/fickian/fickian_single_particle.rst +++ /dev/null @@ -1,6 +0,0 @@ -Single Particle -=============== - -.. autoclass:: pybamm.particle.fickian.SingleParticle - :members: - diff --git a/docs/source/models/submodels/particle/fickian/index.rst b/docs/source/models/submodels/particle/fickian/index.rst deleted file mode 100644 index a6f8c36ceb..0000000000 --- a/docs/source/models/submodels/particle/fickian/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -Fickian -======== - -.. toctree:: - - fickian_many_particles - fickian_single_particle - diff --git a/docs/source/models/submodels/particle/fickian_many_particles.rst b/docs/source/models/submodels/particle/fickian_many_particles.rst new file mode 100644 index 0000000000..c9834bc5d9 --- /dev/null +++ b/docs/source/models/submodels/particle/fickian_many_particles.rst @@ -0,0 +1,7 @@ +Fickian Many Particles +====================== + +.. autoclass:: pybamm.particle.FickianManyParticles + :members: + + diff --git a/docs/source/models/submodels/particle/fickian_single_particle.rst b/docs/source/models/submodels/particle/fickian_single_particle.rst new file mode 100644 index 0000000000..008b2e8b48 --- /dev/null +++ b/docs/source/models/submodels/particle/fickian_single_particle.rst @@ -0,0 +1,6 @@ +Fickian Single Particle +======================= + +.. autoclass:: pybamm.particle.FickianSingleParticle + :members: + diff --git a/docs/source/models/submodels/particle/index.rst b/docs/source/models/submodels/particle/index.rst index 78c4ca7f11..a28c75a83a 100644 --- a/docs/source/models/submodels/particle/index.rst +++ b/docs/source/models/submodels/particle/index.rst @@ -5,5 +5,7 @@ Particle :maxdepth: 1 base_particle - fickian/index - fast/index + fickian_single_particle + fickian_many_particles + fast_single_particle + fast_many_particles diff --git a/examples/notebooks/using-submodels.ipynb b/examples/notebooks/using-submodels.ipynb index d378e0c85a..ffe6892cba 100644 --- a/examples/notebooks/using-submodels.ipynb +++ b/examples/notebooks/using-submodels.ipynb @@ -113,7 +113,7 @@ "metadata": {}, "outputs": [], "source": [ - "model.submodels[\"negative particle\"] = pybamm.particle.fast.SingleParticle(model.param, \"Negative\")" + "model.submodels[\"negative particle\"] = pybamm.particle.FastSingleParticle(model.param, \"Negative\")" ] }, { @@ -370,10 +370,10 @@ "metadata": {}, "outputs": [], "source": [ - "model.submodels[\"negative particle\"] = pybamm.particle.fast.SingleParticle(\n", + "model.submodels[\"negative particle\"] = pybamm.particle.FastSingleParticle(\n", " model.param, \"Negative\"\n", ")\n", - "model.submodels[\"positive particle\"] = pybamm.particle.fast.SingleParticle(\n", + "model.submodels[\"positive particle\"] = pybamm.particle.FastSingleParticle(\n", " model.param, \"Positive\"\n", ")" ] diff --git a/examples/scripts/custom_model.py b/examples/scripts/custom_model.py index 53b4239ea2..0a6429357a 100644 --- a/examples/scripts/custom_model.py +++ b/examples/scripts/custom_model.py @@ -22,10 +22,10 @@ model.submodels["positive electrode"] = pybamm.electrode.ohm.LeadingOrder( model.param, "Positive" ) -model.submodels["negative particle"] = pybamm.particle.fast.SingleParticle( +model.submodels["negative particle"] = pybamm.particle.FastSingleParticle( model.param, "Negative" ) -model.submodels["positive particle"] = pybamm.particle.fast.SingleParticle( +model.submodels["positive particle"] = pybamm.particle.FastSingleParticle( model.param, "Positive" ) model.submodels["negative interface"] = pybamm.interface.InverseButlerVolmer( diff --git a/pybamm/models/full_battery_models/lithium_ion/dfn.py b/pybamm/models/full_battery_models/lithium_ion/dfn.py index 07a552e43e..5bf6354b47 100644 --- a/pybamm/models/full_battery_models/lithium_ion/dfn.py +++ b/pybamm/models/full_battery_models/lithium_ion/dfn.py @@ -70,17 +70,17 @@ def set_interfacial_submodel(self): def set_particle_submodel(self): if self.options["particle"] == "Fickian diffusion": - self.submodels["negative particle"] = pybamm.particle.fickian.ManyParticles( + self.submodels["negative particle"] = pybamm.particle.FickianManyParticles( self.param, "Negative" ) - self.submodels["positive particle"] = pybamm.particle.fickian.ManyParticles( + self.submodels["positive particle"] = pybamm.particle.FickianManyParticles( self.param, "Positive" ) elif self.options["particle"] == "fast diffusion": - self.submodels["negative particle"] = pybamm.particle.fast.ManyParticles( + self.submodels["negative particle"] = pybamm.particle.FastManyParticles( self.param, "Negative" ) - self.submodels["positive particle"] = pybamm.particle.fast.ManyParticles( + self.submodels["positive particle"] = pybamm.particle.FastManyParticles( self.param, "Positive" ) diff --git a/pybamm/models/full_battery_models/lithium_ion/spm.py b/pybamm/models/full_battery_models/lithium_ion/spm.py index 8e70e7af8e..5acd8a19e9 100644 --- a/pybamm/models/full_battery_models/lithium_ion/spm.py +++ b/pybamm/models/full_battery_models/lithium_ion/spm.py @@ -79,17 +79,17 @@ def set_interfacial_submodel(self): def set_particle_submodel(self): if self.options["particle"] == "Fickian diffusion": - self.submodels[ - "negative particle" - ] = pybamm.particle.fickian.SingleParticle(self.param, "Negative") - self.submodels[ - "positive particle" - ] = pybamm.particle.fickian.SingleParticle(self.param, "Positive") + self.submodels["negative particle"] = pybamm.particle.FickianSingleParticle( + self.param, "Negative" + ) + self.submodels["positive particle"] = pybamm.particle.FickianSingleParticle( + self.param, "Positive" + ) elif self.options["particle"] == "fast diffusion": - self.submodels["negative particle"] = pybamm.particle.fast.SingleParticle( + self.submodels["negative particle"] = pybamm.particle.FastSingleParticle( self.param, "Negative" ) - self.submodels["positive particle"] = pybamm.particle.fast.SingleParticle( + self.submodels["positive particle"] = pybamm.particle.FastSingleParticle( self.param, "Positive" ) diff --git a/pybamm/models/full_battery_models/lithium_ion/spme.py b/pybamm/models/full_battery_models/lithium_ion/spme.py index d94c4f9560..4317cdb1a7 100644 --- a/pybamm/models/full_battery_models/lithium_ion/spme.py +++ b/pybamm/models/full_battery_models/lithium_ion/spme.py @@ -81,17 +81,17 @@ def set_interfacial_submodel(self): def set_particle_submodel(self): if self.options["particle"] == "Fickian diffusion": - self.submodels[ - "negative particle" - ] = pybamm.particle.fickian.SingleParticle(self.param, "Negative") - self.submodels[ - "positive particle" - ] = pybamm.particle.fickian.SingleParticle(self.param, "Positive") + self.submodels["negative particle"] = pybamm.particle.FickianSingleParticle( + self.param, "Negative" + ) + self.submodels["positive particle"] = pybamm.particle.FickianSingleParticle( + self.param, "Positive" + ) elif self.options["particle"] == "fast diffusion": - self.submodels["negative particle"] = pybamm.particle.fast.SingleParticle( + self.submodels["negative particle"] = pybamm.particle.FastSingleParticle( self.param, "Negative" ) - self.submodels["positive particle"] = pybamm.particle.fast.SingleParticle( + self.submodels["positive particle"] = pybamm.particle.FastSingleParticle( self.param, "Positive" ) diff --git a/pybamm/models/submodels/particle/__init__.py b/pybamm/models/submodels/particle/__init__.py index 601bf2de9f..374c59674c 100644 --- a/pybamm/models/submodels/particle/__init__.py +++ b/pybamm/models/submodels/particle/__init__.py @@ -1,3 +1,5 @@ from .base_particle import BaseParticle -from . import fickian -from . import fast +from .fickian_many_particles import FickianManyParticles +from .fickian_single_particle import FickianSingleParticle +from .fast_many_particles import FastManyParticles +from .fast_single_particle import FastSingleParticle diff --git a/pybamm/models/submodels/particle/fast/__init__.py b/pybamm/models/submodels/particle/fast/__init__.py deleted file mode 100644 index 3b23cc74e1..0000000000 --- a/pybamm/models/submodels/particle/fast/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .base_fast_particle import BaseModel -from .fast_many_particles import ManyParticles -from .fast_single_particle import SingleParticle diff --git a/pybamm/models/submodels/particle/fast/base_fast_particle.py b/pybamm/models/submodels/particle/fast/base_fast_particle.py deleted file mode 100644 index a39e00b519..0000000000 --- a/pybamm/models/submodels/particle/fast/base_fast_particle.py +++ /dev/null @@ -1,37 +0,0 @@ -# -# Base class for particles each with uniform concentration (i.e. infinitely fast -# diffusion in r) -# -from ..base_particle import BaseParticle - - -class BaseModel(BaseParticle): - """Base class for molar conservation in particles with uniform concentration - in r (i.e. infinitely fast diffusion within particles). - - Parameters - ---------- - param : parameter class - The parameters to use for this submodel - domain : str - The domain of the model either 'Negative' or 'Positive' - - - **Extends:** :class:`pybamm.particle.BaseParticle` - """ - - def __init__(self, param, domain): - super().__init__(param, domain) - - def _unpack(self, variables): - raise NotImplementedError - - def set_rhs(self, variables): - - c, _, j = self._unpack(variables) - - if self.domain == "Negative": - self.rhs = {c: -3 * j / self.param.a_n} - - elif self.domain == "Positive": - self.rhs = {c: -3 * j / self.param.a_p / self.param.gamma_p} diff --git a/pybamm/models/submodels/particle/fast/fast_many_particles.py b/pybamm/models/submodels/particle/fast_many_particles.py similarity index 82% rename from pybamm/models/submodels/particle/fast/fast_many_particles.py rename to pybamm/models/submodels/particle/fast_many_particles.py index b1ca21a7f1..c23bc6e3bb 100644 --- a/pybamm/models/submodels/particle/fast/fast_many_particles.py +++ b/pybamm/models/submodels/particle/fast_many_particles.py @@ -4,10 +4,10 @@ # import pybamm -from .base_fast_particle import BaseModel +from .base_particle import BaseParticle -class ManyParticles(BaseModel): +class FastManyParticles(BaseParticle): """Base class for molar conservation in many particles with uniform concentration in r (i.e. infinitely fast diffusion within particles). @@ -19,7 +19,7 @@ class ManyParticles(BaseModel): The domain of the model either 'Negative' or 'Positive' - **Extends:** :class:`pybamm.particle.fast.BaseModel` + **Extends:** :class:`pybamm.particle.BaseParticle` """ def __init__(self, param, domain): @@ -63,16 +63,17 @@ def get_fundamental_variables(self): return variables - def _unpack(self, variables): + def set_rhs(self, variables): c_s_surf = variables[self.domain + " particle surface concentration"] - N_s = variables[self.domain + " particle flux"] j = variables[self.domain + " electrode interfacial current density"] + if self.domain == "Negative": + self.rhs = {c_s_surf: -3 * j / self.param.a_n} - return c_s_surf, N_s, j + elif self.domain == "Positive": + self.rhs = {c_s_surf: -3 * j / self.param.a_p / self.param.gamma_p} def set_initial_conditions(self, variables): - c, _, _ = self._unpack(variables) - + c_s_surf = variables[self.domain + " particle surface concentration"] if self.domain == "Negative": x_n = pybamm.standard_spatial_vars.x_n c_init = self.param.c_n_init(x_n) @@ -81,4 +82,4 @@ def set_initial_conditions(self, variables): x_p = pybamm.standard_spatial_vars.x_p c_init = self.param.c_p_init(x_p) - self.initial_conditions = {c: c_init} + self.initial_conditions = {c_s_surf: c_init} diff --git a/pybamm/models/submodels/particle/fast/fast_single_particle.py b/pybamm/models/submodels/particle/fast_single_particle.py similarity index 75% rename from pybamm/models/submodels/particle/fast/fast_single_particle.py rename to pybamm/models/submodels/particle/fast_single_particle.py index 3cfc6c9d6a..60046017d1 100644 --- a/pybamm/models/submodels/particle/fast/fast_single_particle.py +++ b/pybamm/models/submodels/particle/fast_single_particle.py @@ -4,10 +4,10 @@ # import pybamm -from .base_fast_particle import BaseModel +from .base_particle import BaseParticle -class SingleParticle(BaseModel): +class FastSingleParticle(BaseParticle): """Base class for molar conservation in a single x-averaged particle with uniform concentration in r (i.e. infinitely fast diffusion within particles). @@ -19,7 +19,7 @@ class SingleParticle(BaseModel): The domain of the model either 'Negative' or 'Positive' - **Extends:** :class:`pybamm.particle.fast.BaseModel` + **Extends:** :class:`pybamm.particle.BaseParticle` """ def __init__(self, param, domain): @@ -35,7 +35,7 @@ def get_fundamental_variables(self): c_s_xav = pybamm.PrimaryBroadcast(c_s_surf_xav, ["negative particle"]) c_s = pybamm.SecondaryBroadcast(c_s_xav, ["negative electrode"]) - N_s = pybamm.FullBroadcast( + N_s = pybamm.FullBroadcastToEdges( 0, ["negative particle"], auxiliary_domains={ @@ -43,14 +43,14 @@ def get_fundamental_variables(self): "tertiary": "current collector", }, ) - N_s_xav = pybamm.x_average(N_s) + N_s_xav = pybamm.FullBroadcast(0, "negative electrode", "current collector") elif self.domain == "Positive": c_s_surf_xav = pybamm.standard_variables.c_s_p_surf_xav c_s_xav = pybamm.PrimaryBroadcast(c_s_surf_xav, ["positive particle"]) c_s = pybamm.SecondaryBroadcast(c_s_xav, ["positive electrode"]) - N_s = pybamm.FullBroadcast( + N_s = pybamm.FullBroadcastToEdges( 0, ["positive particle"], auxiliary_domains={ @@ -58,25 +58,29 @@ def get_fundamental_variables(self): "tertiary": "current collector", }, ) - N_s_xav = pybamm.x_average(N_s) + N_s_xav = pybamm.FullBroadcast(0, "positive electrode", "current collector") variables = self._get_standard_concentration_variables(c_s, c_s_xav) variables.update(self._get_standard_flux_variables(N_s, N_s_xav)) return variables - def _unpack(self, variables): + def set_rhs(self, variables): + c_s_surf_xav = variables[ "X-averaged " + self.domain.lower() + " particle surface concentration" ] - N_s_xav = variables["X-averaged " + self.domain.lower() + " particle flux"] - j_av = variables[ + j_xav = variables[ "X-averaged " + self.domain.lower() + " electrode interfacial current density" ] - return c_s_surf_xav, N_s_xav, j_av + if self.domain == "Negative": + self.rhs = {c_s_surf_xav: -3 * j_xav / self.param.a_n} + + elif self.domain == "Positive": + self.rhs = {c_s_surf_xav: -3 * j_xav / self.param.a_p / self.param.gamma_p} def set_initial_conditions(self, variables): """ @@ -84,7 +88,9 @@ def set_initial_conditions(self, variables): arbitrarily evaluate them at x=0 in the negative electrode and x=1 in the positive electrode (they will usually be constant) """ - c, _, _ = self._unpack(variables) + c_s_surf_xav = variables[ + "X-averaged " + self.domain.lower() + " particle surface concentration" + ] if self.domain == "Negative": c_init = self.param.c_n_init(0) @@ -92,4 +98,4 @@ def set_initial_conditions(self, variables): elif self.domain == "Positive": c_init = self.param.c_p_init(1) - self.initial_conditions = {c: c_init} + self.initial_conditions = {c_s_surf_xav: c_init} diff --git a/pybamm/models/submodels/particle/fickian/__init__.py b/pybamm/models/submodels/particle/fickian/__init__.py deleted file mode 100644 index 86a9ba03be..0000000000 --- a/pybamm/models/submodels/particle/fickian/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .fickian_many_particles import ManyParticles -from .fickian_single_particle import SingleParticle diff --git a/pybamm/models/submodels/particle/fickian/fickian_many_particles.py b/pybamm/models/submodels/particle/fickian_many_particles.py similarity index 97% rename from pybamm/models/submodels/particle/fickian/fickian_many_particles.py rename to pybamm/models/submodels/particle/fickian_many_particles.py index 87677fde30..343e386603 100644 --- a/pybamm/models/submodels/particle/fickian/fickian_many_particles.py +++ b/pybamm/models/submodels/particle/fickian_many_particles.py @@ -2,10 +2,10 @@ # Class for many particles with Fickian diffusion # import pybamm -from ..base_particle import BaseParticle +from .base_particle import BaseParticle -class ManyParticles(BaseParticle): +class FickianManyParticles(BaseParticle): """Base class for molar conservation in many particles which employs Fick's law. diff --git a/pybamm/models/submodels/particle/fickian/fickian_single_particle.py b/pybamm/models/submodels/particle/fickian_single_particle.py similarity index 98% rename from pybamm/models/submodels/particle/fickian/fickian_single_particle.py rename to pybamm/models/submodels/particle/fickian_single_particle.py index 84579de66c..1d049dffd7 100644 --- a/pybamm/models/submodels/particle/fickian/fickian_single_particle.py +++ b/pybamm/models/submodels/particle/fickian_single_particle.py @@ -3,10 +3,10 @@ # import pybamm -from ..base_particle import BaseParticle +from .base_particle import BaseParticle -class SingleParticle(BaseParticle): +class FickianSingleParticle(BaseParticle): """Base class for molar conservation in a single x-averaged particle which employs Fick's law. diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_many_particles.py b/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_many_particles.py index cb842fc9a2..de87f3ca0e 100644 --- a/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_many_particles.py +++ b/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_many_particles.py @@ -20,12 +20,12 @@ def test_public_functions(self): variables = {"Negative electrode interfacial current density": a_n} - submodel = pybamm.particle.fast.ManyParticles(param, "Negative") + submodel = pybamm.particle.FastManyParticles(param, "Negative") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() variables = {"Positive electrode interfacial current density": a_p} - submodel = pybamm.particle.fast.ManyParticles(param, "Positive") + submodel = pybamm.particle.FastManyParticles(param, "Positive") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_single_particle.py b/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_single_particle.py index 6d1a3a21a6..28677bc9e6 100644 --- a/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_single_particle.py +++ b/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_single_particle.py @@ -14,12 +14,12 @@ def test_public_functions(self): a = pybamm.PrimaryBroadcast(pybamm.Scalar(0), "current collector") variables = {"X-averaged negative electrode interfacial current density": a} - submodel = pybamm.particle.fast.SingleParticle(param, "Negative") + submodel = pybamm.particle.FastSingleParticle(param, "Negative") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() variables = {"X-averaged positive electrode interfacial current density": a} - submodel = pybamm.particle.fast.SingleParticle(param, "Positive") + submodel = pybamm.particle.FastSingleParticle(param, "Positive") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_many_particles.py b/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_many_particles.py index e0d803b2ef..8953203915 100644 --- a/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_many_particles.py +++ b/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_many_particles.py @@ -23,7 +23,7 @@ def test_public_functions(self): "Negative electrode temperature": a_n, } - submodel = pybamm.particle.fickian.ManyParticles(param, "Negative") + submodel = pybamm.particle.FickianManyParticles(param, "Negative") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() @@ -31,7 +31,7 @@ def test_public_functions(self): "Positive electrode interfacial current density": a_p, "Positive electrode temperature": a_p, } - submodel = pybamm.particle.fickian.ManyParticles(param, "Positive") + submodel = pybamm.particle.FickianManyParticles(param, "Positive") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_single_particle.py b/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_single_particle.py index 8cf3c43d06..fbf27e700c 100644 --- a/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_single_particle.py +++ b/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_single_particle.py @@ -17,7 +17,7 @@ def test_public_functions(self): "X-averaged negative electrode temperature": a, } - submodel = pybamm.particle.fickian.SingleParticle(param, "Negative") + submodel = pybamm.particle.FickianSingleParticle(param, "Negative") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() @@ -25,7 +25,7 @@ def test_public_functions(self): "X-averaged positive electrode interfacial current density": a, "X-averaged positive electrode temperature": a, } - submodel = pybamm.particle.fickian.SingleParticle(param, "Positive") + submodel = pybamm.particle.FickianSingleParticle(param, "Positive") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() From 4a1b51d313a2d88eda935f9bbe4183483f7aab92 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Sat, 14 Mar 2020 13:09:55 -0400 Subject: [PATCH 5/7] #609 reformat other models --- examples/scripts/compare_lithium_ion.py | 2 +- .../constant_stefan_maxwell_diffusion.py | 2 +- .../leading_stefan_maxwell_diffusion.py | 2 +- .../submodels/oxygen_diffusion/no_oxygen.py | 2 +- .../submodels/particle/fast_many_particles.py | 8 ++--- .../thermal/isothermal/isothermal.py | 2 +- .../thermal/x_lumped/base_x_lumped.py | 2 +- .../test_particle/test_fast/__init__.py | 0 .../test_fast/test_base_fast_particle.py | 30 ------------------- .../test_fast_many_particles.py | 0 .../test_fast_single_particle.py | 0 .../test_particle/test_fickian/__init__.py | 0 .../test_fickian_many_particles.py | 0 .../test_fickian_single_particle.py | 0 14 files changed, 10 insertions(+), 40 deletions(-) delete mode 100644 tests/unit/test_models/test_submodels/test_particle/test_fast/__init__.py delete mode 100644 tests/unit/test_models/test_submodels/test_particle/test_fast/test_base_fast_particle.py rename tests/unit/test_models/test_submodels/test_particle/{test_fast => }/test_fast_many_particles.py (100%) rename tests/unit/test_models/test_submodels/test_particle/{test_fast => }/test_fast_single_particle.py (100%) delete mode 100644 tests/unit/test_models/test_submodels/test_particle/test_fickian/__init__.py rename tests/unit/test_models/test_submodels/test_particle/{test_fickian => }/test_fickian_many_particles.py (100%) rename tests/unit/test_models/test_submodels/test_particle/{test_fickian => }/test_fickian_single_particle.py (100%) diff --git a/examples/scripts/compare_lithium_ion.py b/examples/scripts/compare_lithium_ion.py index 7f5dac6b58..12aa958592 100644 --- a/examples/scripts/compare_lithium_ion.py +++ b/examples/scripts/compare_lithium_ion.py @@ -16,7 +16,7 @@ pybamm.set_logging_level("INFO") # load models -options = {"thermal": "isothermal"} +options = {"thermal": "x-lumped"} models = [ pybamm.lithium_ion.SPM(options), pybamm.lithium_ion.SPMe(options), diff --git a/pybamm/models/submodels/electrolyte/stefan_maxwell/diffusion/constant_stefan_maxwell_diffusion.py b/pybamm/models/submodels/electrolyte/stefan_maxwell/diffusion/constant_stefan_maxwell_diffusion.py index fb2547342b..09c1b49590 100644 --- a/pybamm/models/submodels/electrolyte/stefan_maxwell/diffusion/constant_stefan_maxwell_diffusion.py +++ b/pybamm/models/submodels/electrolyte/stefan_maxwell/diffusion/constant_stefan_maxwell_diffusion.py @@ -29,7 +29,7 @@ def get_fundamental_variables(self): variables = self._get_standard_concentration_variables(c_e) - N_e = pybamm.FullBroadcast( + N_e = pybamm.FullBroadcastToEdges( 0, ["negative electrode", "separator", "positive electrode"], "current collector", diff --git a/pybamm/models/submodels/electrolyte/stefan_maxwell/diffusion/leading_stefan_maxwell_diffusion.py b/pybamm/models/submodels/electrolyte/stefan_maxwell/diffusion/leading_stefan_maxwell_diffusion.py index 9e167532c5..4da2eea137 100644 --- a/pybamm/models/submodels/electrolyte/stefan_maxwell/diffusion/leading_stefan_maxwell_diffusion.py +++ b/pybamm/models/submodels/electrolyte/stefan_maxwell/diffusion/leading_stefan_maxwell_diffusion.py @@ -35,7 +35,7 @@ def get_fundamental_variables(self): def get_coupled_variables(self, variables): - N_e = pybamm.FullBroadcast( + N_e = pybamm.FullBroadcastToEdges( 0, ["negative electrode", "separator", "positive electrode"], "current collector", diff --git a/pybamm/models/submodels/oxygen_diffusion/no_oxygen.py b/pybamm/models/submodels/oxygen_diffusion/no_oxygen.py index 0dc6b5f142..658cc729ac 100644 --- a/pybamm/models/submodels/oxygen_diffusion/no_oxygen.py +++ b/pybamm/models/submodels/oxygen_diffusion/no_oxygen.py @@ -29,7 +29,7 @@ def get_fundamental_variables(self): variables = self._get_standard_concentration_variables(c_ox) - N_e = pybamm.FullBroadcast( + N_e = pybamm.FullBroadcastToEdges( 0, ["negative electrode", "separator", "positive electrode"], "current collector", diff --git a/pybamm/models/submodels/particle/fast_many_particles.py b/pybamm/models/submodels/particle/fast_many_particles.py index c23bc6e3bb..25e44544f2 100644 --- a/pybamm/models/submodels/particle/fast_many_particles.py +++ b/pybamm/models/submodels/particle/fast_many_particles.py @@ -33,7 +33,7 @@ def get_fundamental_variables(self): c_s = pybamm.PrimaryBroadcast(c_s_surf, ["negative particle"]) c_s_xav = pybamm.x_average(c_s) - N_s = pybamm.FullBroadcast( + N_s = pybamm.FullBroadcastToEdges( 0, ["negative particle"], auxiliary_domains={ @@ -41,14 +41,14 @@ def get_fundamental_variables(self): "tertiary": "current collector", }, ) - N_s_xav = pybamm.x_average(N_s) + N_s_xav = pybamm.FullBroadcast(0, "negative electrode", "current collector") elif self.domain == "Positive": c_s_surf = pybamm.standard_variables.c_s_p_surf c_s = pybamm.PrimaryBroadcast(c_s_surf, ["positive particle"]) c_s_xav = pybamm.x_average(c_s) - N_s = pybamm.FullBroadcast( + N_s = pybamm.FullBroadcastToEdges( 0, ["positive particle"], auxiliary_domains={ @@ -56,7 +56,7 @@ def get_fundamental_variables(self): "tertiary": "current collector", }, ) - N_s_xav = pybamm.x_average(N_s) + N_s_xav = pybamm.FullBroadcast(0, "positive electrode", "current collector") variables = self._get_standard_concentration_variables(c_s, c_s_xav) variables.update(self._get_standard_flux_variables(N_s, N_s_xav)) diff --git a/pybamm/models/submodels/thermal/isothermal/isothermal.py b/pybamm/models/submodels/thermal/isothermal/isothermal.py index a10fc80e9e..9f99df373e 100644 --- a/pybamm/models/submodels/thermal/isothermal/isothermal.py +++ b/pybamm/models/submodels/thermal/isothermal/isothermal.py @@ -57,7 +57,7 @@ def get_coupled_variables(self, variables): def _flux_law(self, T): """Zero heat flux since temperature is constant""" - q = pybamm.FullBroadcast( + q = pybamm.FullBroadcastToEdges( pybamm.Scalar(0), ["negative electrode", "separator", "positive electrode"], "current collector", diff --git a/pybamm/models/submodels/thermal/x_lumped/base_x_lumped.py b/pybamm/models/submodels/thermal/x_lumped/base_x_lumped.py index 88225fdf04..9fff53d818 100644 --- a/pybamm/models/submodels/thermal/x_lumped/base_x_lumped.py +++ b/pybamm/models/submodels/thermal/x_lumped/base_x_lumped.py @@ -42,7 +42,7 @@ def get_coupled_variables(self, variables): def _flux_law(self, T): """Fast heat diffusion (temperature has no spatial dependence)""" - q = pybamm.FullBroadcast( + q = pybamm.FullBroadcastToEdges( pybamm.Scalar(0), ["negative electrode", "separator", "positive electrode"], "current collector", diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fast/__init__.py b/tests/unit/test_models/test_submodels/test_particle/test_fast/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fast/test_base_fast_particle.py b/tests/unit/test_models/test_submodels/test_particle/test_fast/test_base_fast_particle.py deleted file mode 100644 index ed8302c972..0000000000 --- a/tests/unit/test_models/test_submodels/test_particle/test_fast/test_base_fast_particle.py +++ /dev/null @@ -1,30 +0,0 @@ -# -# Test base fast submodel -# - -import pybamm -import tests -import unittest - - -class TestBaseModel(unittest.TestCase): - def test_public_functions(self): - submodel = pybamm.particle.fast.BaseModel(None, "Negative") - std_tests = tests.StandardSubModelTests(submodel) - with self.assertRaises(NotImplementedError): - std_tests.test_all() - - submodel = pybamm.particle.fast.BaseModel(None, "Positive") - std_tests = tests.StandardSubModelTests(submodel) - with self.assertRaises(NotImplementedError): - std_tests.test_all() - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_many_particles.py b/tests/unit/test_models/test_submodels/test_particle/test_fast_many_particles.py similarity index 100% rename from tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_many_particles.py rename to tests/unit/test_models/test_submodels/test_particle/test_fast_many_particles.py diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_single_particle.py b/tests/unit/test_models/test_submodels/test_particle/test_fast_single_particle.py similarity index 100% rename from tests/unit/test_models/test_submodels/test_particle/test_fast/test_fast_single_particle.py rename to tests/unit/test_models/test_submodels/test_particle/test_fast_single_particle.py diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fickian/__init__.py b/tests/unit/test_models/test_submodels/test_particle/test_fickian/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_many_particles.py b/tests/unit/test_models/test_submodels/test_particle/test_fickian_many_particles.py similarity index 100% rename from tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_many_particles.py rename to tests/unit/test_models/test_submodels/test_particle/test_fickian_many_particles.py diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_single_particle.py b/tests/unit/test_models/test_submodels/test_particle/test_fickian_single_particle.py similarity index 100% rename from tests/unit/test_models/test_submodels/test_particle/test_fickian/test_fickian_single_particle.py rename to tests/unit/test_models/test_submodels/test_particle/test_fickian_single_particle.py From 145caec0124c79230572a4270f2827bf453a2b0f Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Sat, 14 Mar 2020 18:22:19 -0400 Subject: [PATCH 6/7] #609 make spatial variable on edges work --- .../expression_tree/independent_variable.py | 24 +++++++++++++- pybamm/geometry/standard_spatial_vars.py | 32 +++++++++---------- pybamm/spatial_methods/finite_volume.py | 6 ++-- pybamm/spatial_methods/spatial_method.py | 2 +- .../test_independent_variable.py | 6 ++++ .../test_base_spatial_method.py | 6 ++-- .../test_finite_volume/test_finite_volume.py | 19 +++++++++++ 7 files changed, 72 insertions(+), 23 deletions(-) diff --git a/pybamm/expression_tree/independent_variable.py b/pybamm/expression_tree/independent_variable.py index 8c11288d98..f91b8df1ff 100644 --- a/pybamm/expression_tree/independent_variable.py +++ b/pybamm/expression_tree/independent_variable.py @@ -104,10 +104,32 @@ def __init__(self, name, domain=None, auxiliary_domains=None, coord_sys=None): def new_copy(self): """ See :meth:`pybamm.Symbol.new_copy()`. """ - return SpatialVariable( + return self.__class__( self.name, self.domain, self.auxiliary_domains, self.coord_sys ) +class SpatialVariableEdge(SpatialVariable): + """A node in the expression tree representing a spatial variable, which evaluates + on the edges + + Parameters + ---------- + name : str + name of the node (e.g. "x", "y", "z", "r", "x_n", "x_s", "x_p", "r_n", "r_p") + domain : iterable of str + list of domains that this variable is valid over (e.g. "cartesian", "spherical + polar") + + *Extends:* :class:`Symbol` + """ + + def __init__(self, name, domain=None, auxiliary_domains=None, coord_sys=None): + super().__init__(name, domain, auxiliary_domains, coord_sys) + + def evaluates_on_edges(self): + return True + + # the independent variable time t = Time() diff --git a/pybamm/geometry/standard_spatial_vars.py b/pybamm/geometry/standard_spatial_vars.py index cbb23736d1..8f547ef6da 100644 --- a/pybamm/geometry/standard_spatial_vars.py +++ b/pybamm/geometry/standard_spatial_vars.py @@ -51,40 +51,40 @@ ) # Domains at cell edges -x_n_edge = pybamm.SpatialVariable( - "x_n_edge", +x_n_edge = pybamm.SpatialVariableEdge( + "x_n", domain=["negative electrode"], auxiliary_domains={"secondary": "current collector"}, coord_sys="cartesian", ) -x_s_edge = pybamm.SpatialVariable( - "x_s_edge", +x_s_edge = pybamm.SpatialVariableEdge( + "x_s", domain=["separator"], auxiliary_domains={"secondary": "current collector"}, coord_sys="cartesian", ) -x_p_edge = pybamm.SpatialVariable( - "x_p_edge", +x_p_edge = pybamm.SpatialVariableEdge( + "x_p", domain=["positive electrode"], auxiliary_domains={"secondary": "current collector"}, coord_sys="cartesian", ) -x_edge = pybamm.SpatialVariable( - "x_edge", +x_edge = pybamm.SpatialVariableEdge( + "x", domain=whole_cell, auxiliary_domains={"secondary": "current collector"}, coord_sys="cartesian", ) -y_edge = pybamm.SpatialVariable( - "y_edge", domain="current collector", coord_sys="cartesian" +y_edge = pybamm.SpatialVariableEdge( + "y", domain="current collector", coord_sys="cartesian" ) -z_edge = pybamm.SpatialVariable( - "z_edge", domain="current collector", coord_sys="cartesian" +z_edge = pybamm.SpatialVariableEdge( + "z", domain="current collector", coord_sys="cartesian" ) -r_n_edge = pybamm.SpatialVariable( - "r_n_edge", +r_n_edge = pybamm.SpatialVariableEdge( + "r_n", domain=["negative particle"], auxiliary_domains={ "secondary": "negative electrode", @@ -92,8 +92,8 @@ }, coord_sys="spherical polar", ) -r_p_edge = pybamm.SpatialVariable( - "r_p_edge", +r_p_edge = pybamm.SpatialVariableEdge( + "r_p", domain=["positive particle"], auxiliary_domains={ "secondary": "positive electrode", diff --git a/pybamm/spatial_methods/finite_volume.py b/pybamm/spatial_methods/finite_volume.py index ccc3e1dd88..d748f55909 100644 --- a/pybamm/spatial_methods/finite_volume.py +++ b/pybamm/spatial_methods/finite_volume.py @@ -57,9 +57,11 @@ def spatial_variable(self, symbol): :class:`pybamm.Vector` Contains the discretised spatial variable """ - # for finite volume we use the cell centres symbol_mesh = self.mesh.combine_submeshes(*symbol.domain) - entries = np.concatenate([mesh.nodes for mesh in symbol_mesh]) + if symbol.evaluates_on_edges(): + entries = np.concatenate([mesh.edges for mesh in symbol_mesh]) + else: + entries = np.concatenate([mesh.nodes for mesh in symbol_mesh]) return pybamm.Vector( entries, domain=symbol.domain, auxiliary_domains=symbol.auxiliary_domains ) diff --git a/pybamm/spatial_methods/spatial_method.py b/pybamm/spatial_methods/spatial_method.py index 04b6c7d82c..371d50ebc4 100644 --- a/pybamm/spatial_methods/spatial_method.py +++ b/pybamm/spatial_methods/spatial_method.py @@ -62,7 +62,7 @@ def spatial_variable(self, symbol): Contains the discretised spatial variable """ symbol_mesh = self.mesh.combine_submeshes(*symbol.domain) - if symbol.name.endswith("_edge"): + if symbol.evaluates_on_edges(): entries = np.concatenate([mesh.edges for mesh in symbol_mesh]) else: entries = np.concatenate([mesh.nodes for mesh in symbol_mesh]) diff --git a/tests/unit/test_expression_tree/test_independent_variable.py b/tests/unit/test_expression_tree/test_independent_variable.py index 72f8f40620..a00fa9079d 100644 --- a/tests/unit/test_expression_tree/test_independent_variable.py +++ b/tests/unit/test_expression_tree/test_independent_variable.py @@ -36,6 +36,7 @@ def test_time(self): def test_spatial_variable(self): x = pybamm.SpatialVariable("x", "negative electrode") self.assertEqual(x.name, "x") + self.assertFalse(x.evaluates_on_edges()) y = pybamm.SpatialVariable("y", "separator") self.assertEqual(y.name, "y") z = pybamm.SpatialVariable("z", "positive electrode") @@ -56,6 +57,11 @@ def test_spatial_variable(self): with self.assertRaises(pybamm.DomainError): pybamm.SpatialVariable("x", ["negative particle"]) + def test_spatial_variable_edge(self): + x = pybamm.SpatialVariableEdge("x", "negative electrode") + self.assertEqual(x.name, "x") + self.assertTrue(x.evaluates_on_edges()) + if __name__ == "__main__": print("Add -v for more debug output") diff --git a/tests/unit/test_spatial_methods/test_base_spatial_method.py b/tests/unit/test_spatial_methods/test_base_spatial_method.py index 708d3c73d2..475c3aa91f 100644 --- a/tests/unit/test_spatial_methods/test_base_spatial_method.py +++ b/tests/unit/test_spatial_methods/test_base_spatial_method.py @@ -50,9 +50,9 @@ def test_discretise_spatial_variable(self): ) # edges - x1_edge = pybamm.SpatialVariable("x_edge", ["negative electrode"]) - x2_edge = pybamm.SpatialVariable("x_edge", ["negative electrode", "separator"]) - r_edge = pybamm.SpatialVariable("r_edge", ["negative particle"]) + x1_edge = pybamm.SpatialVariableEdge("x", ["negative electrode"]) + x2_edge = pybamm.SpatialVariableEdge("x", ["negative electrode", "separator"]) + r_edge = pybamm.SpatialVariableEdge("r", ["negative particle"]) for var in [x1_edge, x2_edge, r_edge]: var_disc = spatial_method.spatial_variable(var) self.assertIsInstance(var_disc, pybamm.Vector) diff --git a/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py b/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py index 648e3c5c7a..c80a83a9d3 100644 --- a/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py +++ b/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py @@ -811,6 +811,25 @@ def test_indefinite_integral(self): left_boundary_value_disc.evaluate(y=phi_exact), 0 ) + # -------------------------------------------------------------------- + # indefinite integral of a spatial variable + x = pybamm.SpatialVariable("x", ["negative electrode", "separator"]) + x_edge = pybamm.SpatialVariableEdge("x", ["negative electrode", "separator"]) + int_x = pybamm.IndefiniteIntegral(x, x) + int_x_edge = pybamm.IndefiniteIntegral(x_edge, x) + + x_disc = disc.process_symbol(x) + x_edge_disc = disc.process_symbol(x_edge) + int_x_disc = disc.process_symbol(int_x) + int_x_edge_disc = disc.process_symbol(int_x_edge) + + np.testing.assert_almost_equal( + int_x_disc.evaluate(), x_edge_disc.evaluate() ** 2 / 2 + ) + np.testing.assert_almost_equal( + int_x_edge_disc.evaluate(), x_disc.evaluate() ** 2 / 2, decimal=4 + ) + # -------------------------------------------------------------------- # micrsoscale case c = pybamm.Variable("c", domain=["negative particle"]) From afc9e73d2fd8bffcd2fcb0b2a604f74d3c6e5624 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Mon, 16 Mar 2020 09:31:09 -0400 Subject: [PATCH 7/7] #609 changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c0d13c98..2c1d976a81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +- Added functionality to broadcast to edges ([#891](https://github.com/pybamm-team/PyBaMM/pull/891)) - Added additional notebooks showing how to create and compare models ([#877](https://github.com/pybamm-team/PyBaMM/pull/877)) - Added `Minimum`, `Maximum` and `Sign` operators ([#876](https://github.com/pybamm-team/PyBaMM/pull/876)) - Added a search feature to `FuzzyDict` ([#875](https://github.com/pybamm-team/PyBaMM/pull/875))