Exemplo n.º 1
0
    def test_model_solver_python(self):
        # Create model
        model = pybamm.BaseModel()
        model.convert_to_format = "python"
        domain = ["negative electrode", "separator", "positive electrode"]
        var = pybamm.Variable("var", domain=domain)
        model.rhs = {var: 0.1 * var}
        model.initial_conditions = {var: 1}
        # No need to set parameters; can use base discretisation (no spatial operators)

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)
        # Solve
        solver = pybamm.ScipySolver(rtol=1e-8, atol=1e-8, method="RK45")
        t_eval = np.linspace(0, 1, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_equal(solution.t, t_eval)
        np.testing.assert_allclose(solution.y[0], np.exp(0.1 * solution.t))

        # Test time
        self.assertEqual(solution.total_time,
                         solution.solve_time + solution.set_up_time)
        self.assertEqual(solution.termination, "final time")
Exemplo n.º 2
0
    def test_cartesian_div_convergence(self):
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}

        # Function for convergence testing
        def get_error(n):
            # create mesh and discretisation
            mesh = get_mesh_for_testing(n)
            disc = pybamm.Discretisation(mesh, spatial_methods)
            combined_submesh = mesh.combine_submeshes(*whole_cell)
            x = combined_submesh[0].nodes
            x_edge = combined_submesh[0].edges

            # Define flux and bcs
            N = pybamm.Vector(x_edge**2 * np.cos(x_edge), domain=whole_cell)
            div_eqn = pybamm.div(N)
            # Define exact solutions
            # N = x**2 * cos(x) --> dNdx = x*(2cos(x) - xsin(x))
            div_exact = x * (2 * np.cos(x) - x * np.sin(x))

            # Discretise and evaluate
            div_eqn_disc = disc.process_symbol(div_eqn)
            div_approx = div_eqn_disc.evaluate()

            # Return difference between approx and exact
            return div_approx[:, 0] - div_exact

        # Get errors
        ns = 10 * 2**np.arange(6)
        errs = {n: get_error(int(n)) for n in ns}
        # expect quadratic convergence everywhere
        err_norm = np.array([np.linalg.norm(errs[n], np.inf) for n in ns])
        rates = np.log2(err_norm[:-1] / err_norm[1:])
        np.testing.assert_array_less(1.99 * np.ones_like(rates), rates)
Exemplo n.º 3
0
    def test_spherical_div_convergence_quadratic(self):
        # test div( r**2 * sin(r) ) == 4*r*sin(r) - r**2*cos(r)
        spatial_methods = {"negative particle": pybamm.FiniteVolume()}

        # Function for convergence testing
        def get_error(n):
            # create mesh and discretisation (single particle)
            mesh = get_mesh_for_testing(rpts=n)
            disc = pybamm.Discretisation(mesh, spatial_methods)
            submesh = mesh["negative particle"]
            r = submesh[0].nodes
            r_edge = submesh[0].edges

            # Define flux and bcs
            N = pybamm.Vector(r_edge**2 * np.sin(r_edge),
                              domain=["negative particle"])
            div_eqn = pybamm.div(N)
            # Define exact solutions
            # N = r**3 --> div(N) = 5 * r**2
            div_exact = 4 * r * np.sin(r) + r**2 * np.cos(r)

            # Discretise and evaluate
            div_eqn_disc = disc.process_symbol(div_eqn)
            div_approx = div_eqn_disc.evaluate()

            # Return difference between approx and exact
            return div_approx[:, 0] - div_exact

        # Get errors
        ns = 10 * 2**np.arange(6)
        errs = {n: get_error(int(n)) for n in ns}
        # expect quadratic convergence everywhere
        err_norm = np.array([np.linalg.norm(errs[n], np.inf) for n in ns])
        rates = np.log2(err_norm[:-1] / err_norm[1:])
        np.testing.assert_array_less(1.99 * np.ones_like(rates), rates)
Exemplo n.º 4
0
    def test_mass_matrix_shape(self):
        """
        Test mass matrix shape
        """
        # one equation
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        c = pybamm.Variable("c", domain=whole_cell)
        N = pybamm.grad(c)
        model = pybamm.BaseModel()
        model.rhs = {c: pybamm.div(N)}
        model.initial_conditions = {c: pybamm.Scalar(0)}
        model.boundary_conditions = {
            c: {
                "left": (0, "Dirichlet"),
                "right": (0, "Dirichlet")
            }
        }
        model.variables = {"c": c, "N": N}

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)

        combined_submesh = mesh.combine_submeshes(*whole_cell)
        disc.process_model(model)

        # mass matrix
        mass = np.eye(combined_submesh[0].npts)
        np.testing.assert_array_equal(mass,
                                      model.mass_matrix.entries.toarray())
Exemplo n.º 5
0
    def test_p2d_mass_matrix_shape(self):
        """
        Test mass matrix shape in the pseudo 2-dimensional case
        """
        c = pybamm.Variable("c", domain=["negative particle"])
        N = pybamm.grad(c)
        model = pybamm.BaseModel()
        model.rhs = {c: pybamm.div(N)}
        model.initial_conditions = {c: pybamm.Scalar(0)}
        model.boundary_conditions = {
            c: {
                "left": (0, "Dirichlet"),
                "right": (0, "Dirichlet")
            }
        }
        model.variables = {"c": c, "N": N}
        mesh = get_p2d_mesh_for_testing()
        spatial_methods = {"negative particle": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        prim_pts = mesh["negative particle"][0].npts
        sec_pts = len(mesh["negative particle"])
        mass_local = eye(prim_pts)
        mass = kron(eye(sec_pts), mass_local)
        np.testing.assert_array_equal(mass.toarray(),
                                      model.mass_matrix.entries.toarray())
Exemplo n.º 6
0
    def test_grad_1plus1d(self):
        mesh = get_1p1d_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)

        a = pybamm.Variable("a", domain=["negative electrode"])
        b = pybamm.Variable("b", domain=["separator"])
        c = pybamm.Variable("c", domain=["positive electrode"])
        var = pybamm.Concatenation(a, b, c)
        boundary_conditions = {
            var.id: {
                "left": (pybamm.Vector(np.linspace(0, 1, 15)), "Neumann"),
                "right": (pybamm.Vector(np.linspace(0, 1, 15)), "Neumann"),
            }
        }

        disc.bcs = boundary_conditions
        disc.set_variable_slices([var])
        grad_eqn_disc = disc.process_symbol(pybamm.grad(var))

        # Evaulate
        combined_submesh = mesh.combine_submeshes(*var.domain)
        linear_y = np.outer(np.linspace(0, 1, 15),
                            combined_submesh[0].nodes).reshape(-1, 1)

        expected = np.outer(np.linspace(0, 1, 15),
                            np.ones_like(combined_submesh[0].edges)).reshape(
                                -1, 1)
        np.testing.assert_array_almost_equal(
            grad_eqn_disc.evaluate(None, linear_y), expected)
Exemplo n.º 7
0
    def test_model_solver_with_event_python(self):
        # Create model
        model = pybamm.BaseModel()
        model.convert_to_format = "python"
        domain = ["negative electrode", "separator", "positive electrode"]
        var = pybamm.Variable("var", domain=domain)
        model.rhs = {var: -0.1 * var}
        model.initial_conditions = {var: 1}
        # needs to work with multiple events (to avoid bug where only last event is
        # used)
        model.events = [
            pybamm.Event("var=0.5", pybamm.min(var - 0.5)),
            pybamm.Event("var=-0.5", pybamm.min(var + 0.5)),
        ]
        # No need to set parameters; can use base discretisation (no spatial operators)

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)
        # Solve
        solver = pybamm.ScipySolver(rtol=1e-8, atol=1e-8, method="RK45")
        t_eval = np.linspace(0, 10, 100)
        solution = solver.solve(model, t_eval)
        self.assertLess(len(solution.t), len(t_eval))
        np.testing.assert_array_equal(solution.t, t_eval[:len(solution.t)])
        np.testing.assert_allclose(solution.y[0], np.exp(-0.1 * solution.t))
Exemplo n.º 8
0
    def test_model_solver_with_external(self):
        # Create model
        model = pybamm.BaseModel()
        domain = ["negative electrode", "separator", "positive electrode"]
        var1 = pybamm.Variable("var1", domain=domain)
        var2 = pybamm.Variable("var2", domain=domain)
        model.rhs = {var1: -var2}
        model.initial_conditions = {var1: 1}
        model.external_variables = [var2]
        model.variables = {"var2": var2}
        # No need to set parameters; can use base discretisation (no spatial
        # operators)

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)
        # Solve
        solver = pybamm.ScipySolver(rtol=1e-8, atol=1e-8)
        t_eval = np.linspace(0, 10, 100)
        solution = solver.solve(
            model, t_eval, external_variables={"var2": 0.5 * np.ones(100)})
        np.testing.assert_allclose(solution.y[0],
                                   1 - 0.5 * solution.t,
                                   rtol=1e-06)
Exemplo n.º 9
0
 def default_spatial_methods(self):
     base_spatial_methods = {
         "lithium counter electrode": pybamm.FiniteVolume(),
         "separator": pybamm.FiniteVolume(),
         "working electrode": pybamm.FiniteVolume(),
         "working particle": pybamm.FiniteVolume(),
     }
     if self.options["dimensionality"] == 0:
         # 0D submesh - use base spatial method
         base_spatial_methods[
             "current collector"
         ] = pybamm.ZeroDimensionalSpatialMethod()
     elif self.options["dimensionality"] == 1:
         base_spatial_methods["current collector"] = pybamm.FiniteVolume()
     elif self.options["dimensionality"] == 2:
         base_spatial_methods["current collector"] = pybamm.ScikitFiniteElement()
     return base_spatial_methods
Exemplo n.º 10
0
    def test_adding_1D_external_variable(self):
        model = pybamm.BaseModel()

        a = pybamm.Variable("a", domain=["test"])
        b = pybamm.Variable("b", domain=["test"])

        model.rhs = {a: a * b}
        model.boundary_conditions = {
            a: {
                "left": (0, "Dirichlet"),
                "right": (0, "Dirichlet")
            }
        }
        model.initial_conditions = {a: 0}
        model.external_variables = [b]
        model.variables = {
            "a": a,
            "b": b,
            "c": a * b,
            "grad b": pybamm.grad(b),
            "div grad b": pybamm.div(pybamm.grad(b)),
        }

        x = pybamm.SpatialVariable("x", domain="test", coord_sys="cartesian")
        geometry = {
            "test": {
                "primary": {
                    x: {
                        "min": pybamm.Scalar(0),
                        "max": pybamm.Scalar(1)
                    }
                }
            }
        }

        submesh_types = {"test": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh)}
        var_pts = {x: 10}
        mesh = pybamm.Mesh(geometry, submesh_types, var_pts)

        spatial_methods = {"test": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        self.assertEqual(disc.y_slices[a.id][0], slice(0, 10, None))

        b_test = np.ones((10, 1))
        np.testing.assert_array_equal(
            model.variables["b"].evaluate(u={"b": b_test}), b_test)

        # check that b is added to the boundary conditions
        model.bcs[b.id]["left"]
        model.bcs[b.id]["right"]

        # check that grad and div(grad ) produce the correct shapes
        self.assertEqual(model.variables["b"].shape_for_testing, (10, 1))
        self.assertEqual(model.variables["grad b"].shape_for_testing, (11, 1))
        self.assertEqual(model.variables["div grad b"].shape_for_testing,
                         (10, 1))
Exemplo n.º 11
0
    def test_add_ghost_nodes(self):
        # Set up

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)

        # Add ghost nodes
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        var = pybamm.Variable("var", domain=whole_cell)
        disc.set_variable_slices([var])
        discretised_symbol = pybamm.StateVector(*disc.y_slices[var.id])
        bcs = {
            "left": (pybamm.Scalar(0), "Dirichlet"),
            "right": (pybamm.Scalar(3), "Dirichlet"),
        }

        # Test
        sp_meth = pybamm.FiniteVolume()
        sp_meth.build(mesh)
        sym_ghost, _ = sp_meth.add_ghost_nodes(var, discretised_symbol, bcs)
        combined_submesh = mesh.combine_submeshes(*whole_cell)
        y_test = np.linspace(0, 1, combined_submesh[0].npts)
        np.testing.assert_array_equal(
            sym_ghost.evaluate(y=y_test)[1:-1], discretised_symbol.evaluate(y=y_test)
        )
        self.assertEqual(
            (sym_ghost.evaluate(y=y_test)[0] + sym_ghost.evaluate(y=y_test)[1]) / 2, 0
        )
        self.assertEqual(
            (sym_ghost.evaluate(y=y_test)[-2] + sym_ghost.evaluate(y=y_test)[-1]) / 2, 3
        )

        # test errors
        bcs = {"left": (pybamm.Scalar(0), "x"), "right": (pybamm.Scalar(3), "Neumann")}
        with self.assertRaisesRegex(ValueError, "boundary condition must be"):
            sp_meth.add_ghost_nodes(var, discretised_symbol, bcs)
        with self.assertRaisesRegex(ValueError, "boundary condition must be"):
            sp_meth.add_neumann_values(var, discretised_symbol, bcs, var.domain)
        bcs = {"left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(3), "x")}
        with self.assertRaisesRegex(ValueError, "boundary condition must be"):
            sp_meth.add_ghost_nodes(var, discretised_symbol, bcs)
        with self.assertRaisesRegex(ValueError, "boundary condition must be"):
            sp_meth.add_neumann_values(var, discretised_symbol, bcs, var.domain)
Exemplo n.º 12
0
    def test_grad_div_shapes_mixed_domain(self):
        """
        Test grad and div with Dirichlet boundary conditions (applied by grad on var)
        """
        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)

        # grad
        var = pybamm.Variable("var",
                              domain=["negative electrode", "separator"])
        grad_eqn = pybamm.grad(var)
        boundary_conditions = {
            var.id: {
                "left": (pybamm.Scalar(1), "Dirichlet"),
                "right": (pybamm.Scalar(1), "Dirichlet"),
            }
        }
        disc.bcs = boundary_conditions

        disc.set_variable_slices([var])

        grad_eqn_disc = disc.process_symbol(grad_eqn)

        combined_submesh = mesh.combine_submeshes("negative electrode",
                                                  "separator")
        constant_y = np.ones_like(combined_submesh[0].nodes[:, np.newaxis])
        np.testing.assert_array_equal(
            grad_eqn_disc.evaluate(None, constant_y),
            np.zeros_like(combined_submesh[0].edges[:, np.newaxis]),
        )

        # div: test on linear y (should have laplacian zero) so change bcs
        linear_y = combined_submesh[0].nodes
        N = pybamm.grad(var)
        div_eqn = pybamm.div(N)
        boundary_conditions = {
            var.id: {
                "left": (pybamm.Scalar(0), "Dirichlet"),
                "right":
                (pybamm.Scalar(combined_submesh[0].edges[-1]), "Dirichlet"),
            }
        }
        disc.bcs = boundary_conditions

        grad_eqn_disc = disc.process_symbol(grad_eqn)
        np.testing.assert_array_almost_equal(
            grad_eqn_disc.evaluate(None, linear_y),
            np.ones_like(combined_submesh[0].edges[:, np.newaxis]),
        )

        div_eqn_disc = disc.process_symbol(div_eqn)
        np.testing.assert_array_almost_equal(
            div_eqn_disc.evaluate(None, linear_y),
            np.zeros_like(combined_submesh[0].nodes[:, np.newaxis]),
        )
Exemplo n.º 13
0
    def test_add_ghost_nodes_concatenation(self):
        # Set up

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)

        # Add ghost nodes
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        var_n = pybamm.Variable("var", domain=["negative electrode"])
        var_s = pybamm.Variable("var", domain=["separator"])
        var_p = pybamm.Variable("var", domain=["positive electrode"])
        var = pybamm.Concatenation(var_n, var_s, var_p)
        disc.set_variable_slices([var])
        discretised_symbol = disc.process_symbol(var)
        bcs = {
            "left": (pybamm.Scalar(0), "Dirichlet"),
            "right": (pybamm.Scalar(3), "Dirichlet"),
        }

        # Test
        combined_submesh = mesh.combine_submeshes(*whole_cell)
        y_test = np.ones_like(combined_submesh[0].nodes[:, np.newaxis])

        # both
        sp_meth = pybamm.FiniteVolume()
        sp_meth.build(mesh)
        symbol_plus_ghost_both, _ = sp_meth.add_ghost_nodes(
            var, discretised_symbol, bcs)
        np.testing.assert_array_equal(
            symbol_plus_ghost_both.evaluate(None, y_test)[1:-1],
            discretised_symbol.evaluate(None, y_test),
        )
        self.assertEqual(
            (symbol_plus_ghost_both.evaluate(None, y_test)[0] +
             symbol_plus_ghost_both.evaluate(None, y_test)[1]) / 2,
            0,
        )
        self.assertEqual(
            (symbol_plus_ghost_both.evaluate(None, y_test)[-2] +
             symbol_plus_ghost_both.evaluate(None, y_test)[-1]) / 2,
            3,
        )
Exemplo n.º 14
0
    def test_extrapolate_on_nonuniform_grid(self):
        var = pybamm.standard_spatial_vars
        geometry = {
            "negative particle": {
                var.r_n: {
                    "min": 0,
                    "max": 1
                }
            },
            "positive particle": {
                var.r_p: {
                    "min": 0,
                    "max": 1
                }
            },
        }

        submesh_types = {
            "negative particle":
            pybamm.MeshGenerator(pybamm.Exponential1DSubMesh),
            "positive particle":
            pybamm.MeshGenerator(pybamm.Exponential1DSubMesh),
        }

        rpts = 10
        var_pts = {var.r_n: rpts, var.r_p: rpts}
        mesh = pybamm.Mesh(geometry, submesh_types, var_pts)
        method_options = {
            "extrapolation": {
                "order": "linear",
                "use bcs": False
            }
        }
        spatial_methods = {
            "negative particle": pybamm.FiniteVolume(method_options)
        }
        disc = pybamm.Discretisation(mesh, spatial_methods)

        var = pybamm.Variable("var", domain="negative particle")
        surf_eqn = pybamm.surf(var)
        disc.set_variable_slices([var])
        surf_eqn_disc = disc.process_symbol(surf_eqn)

        micro_submesh = mesh["negative particle"]

        # check constant extrapolates to constant
        constant_y = np.ones_like(micro_submesh.nodes[:, np.newaxis])
        np.testing.assert_array_almost_equal(
            surf_eqn_disc.evaluate(None, constant_y), 1)

        # check linear variable extrapolates correctly
        linear_y = micro_submesh.nodes
        y_surf = micro_submesh.edges[-1]
        np.testing.assert_array_almost_equal(
            surf_eqn_disc.evaluate(None, linear_y), y_surf)
Exemplo n.º 15
0
    def test_model_solver_ode_with_jacobian_python(self):
        # Create model
        model = pybamm.BaseModel()
        model.convert_to_format = "python"
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        var1 = pybamm.Variable("var1", domain=whole_cell)
        var2 = pybamm.Variable("var2", domain=whole_cell)
        model.rhs = {var1: var1, var2: 1 - var1}
        model.initial_conditions = {var1: 1.0, var2: -1.0}
        model.variables = {"var1": var1, "var2": var2}

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        # Add user-supplied Jacobian to model
        combined_submesh = mesh.combine_submeshes("negative electrode",
                                                  "separator",
                                                  "positive electrode")
        N = combined_submesh.npts

        # construct jacobian in order of model.rhs
        J = []
        for var in model.rhs.keys():
            if var.id == var1.id:
                J.append([np.eye(N), np.zeros((N, N))])
            else:
                J.append([-1.0 * np.eye(N), np.zeros((N, N))])

        J = np.block(J)

        def jacobian(t, y):
            return J

        # Solve
        solver = pybamm.ScipySolver(rtol=1e-9, atol=1e-9)
        t_eval = np.linspace(0, 1, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_equal(solution.t, t_eval)

        T, Y = solution.t, solution.y
        np.testing.assert_array_almost_equal(
            model.variables["var1"].evaluate(T, Y),
            np.ones((N, T.size)) * np.exp(T[np.newaxis, :]),
        )
        np.testing.assert_array_almost_equal(
            model.variables["var2"].evaluate(T, Y),
            np.ones(
                (N, T.size)) * (T[np.newaxis, :] - np.exp(T[np.newaxis, :])),
        )
Exemplo n.º 16
0
    def test_definite_integral_vector(self):
        mesh = get_mesh_for_testing()
        spatial_methods = {
            "macroscale": pybamm.FiniteVolume(),
            "negative particle": pybamm.FiniteVolume(),
            "positive particle": pybamm.FiniteVolume(),
        }
        disc = pybamm.Discretisation(mesh, spatial_methods)
        var = pybamm.Variable("var", domain="negative electrode")
        disc.set_variable_slices([var])

        # row (default)
        vec = pybamm.DefiniteIntegralVector(var)
        vec_disc = disc.process_symbol(vec)
        self.assertEqual(vec_disc.shape[0], 1)
        self.assertEqual(vec_disc.shape[1], mesh["negative electrode"][0].npts)

        # column
        vec = pybamm.DefiniteIntegralVector(var, vector_type="column")
        vec_disc = disc.process_symbol(vec)
        self.assertEqual(vec_disc.shape[0], mesh["negative electrode"][0].npts)
        self.assertEqual(vec_disc.shape[1], 1)
Exemplo n.º 17
0
    def test_p2d_with_x_dep_bcs_spherical_convergence(self):
        # test div_r( (r**2 * sin(r)) * x ) == (4*r*sin(r) - r**2*cos(r)) * x
        spatial_methods = {
            "negative particle": pybamm.FiniteVolume(),
            "negative electrode": pybamm.FiniteVolume(),
        }

        # Function for convergence testing
        def get_error(m):
            # create mesh and discretisation p2d, x-dependent
            mesh = get_p2d_mesh_for_testing(6, m)
            disc = pybamm.Discretisation(mesh, spatial_methods)
            submesh_r = mesh["negative particle"]
            r = submesh_r[0].nodes
            r_edge = pybamm.standard_spatial_vars.r_n_edge
            x = pybamm.standard_spatial_vars.x_n

            N = pybamm.PrimaryBroadcast(x, "negative particle") * (
                r_edge ** 2 * pybamm.sin(r_edge)
            )
            div_eqn = pybamm.div(N)
            # Define exact solutions
            # N = r**2*sin(r) --> div(N) = 4*r*sin(r) - r**2*cos(r)
            div_exact = 4 * r * np.sin(r) + r ** 2 * np.cos(r)
            div_exact = np.kron(mesh["negative electrode"][0].nodes, div_exact)

            # Discretise and evaluate
            div_eqn_disc = disc.process_symbol(div_eqn)
            div_approx = div_eqn_disc.evaluate()

            return div_approx[:, 0] - div_exact

        # Get errors
        ns = 10 * 2 ** np.arange(6)
        errs = {n: get_error(int(n)) for n in ns}
        # expect quadratic convergence everywhere
        err_norm = np.array([np.linalg.norm(errs[n], np.inf) for n in ns])
        rates = np.log2(err_norm[:-1] / err_norm[1:])
        np.testing.assert_array_less(1.99 * np.ones_like(rates), rates)
Exemplo n.º 18
0
    def test_cartesian_spherical_grad_convergence(self):
        # note that grad function is the same for cartesian and spherical
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        whole_cell = ["negative electrode", "separator", "positive electrode"]

        # Define variable
        var = pybamm.Variable("var", domain=whole_cell)
        grad_eqn = pybamm.grad(var)
        boundary_conditions = {
            var.id: {
                "left": (pybamm.Scalar(0), "Dirichlet"),
                "right": (pybamm.Scalar(np.sin(1)**2), "Dirichlet"),
            }
        }

        # Function for convergence testing
        def get_error(n):
            # create mesh and discretisation
            mesh = get_mesh_for_testing(n)
            disc = pybamm.Discretisation(mesh, spatial_methods)
            disc.bcs = boundary_conditions
            disc.set_variable_slices([var])

            # Define exact solutions
            combined_submesh = mesh.combine_submeshes(*whole_cell)
            x = combined_submesh.nodes
            y = np.sin(x)**2
            # var = sin(x)**2 --> dvardx = 2*sin(x)*cos(x)
            x_edge = combined_submesh.edges
            grad_exact = 2 * np.sin(x_edge) * np.cos(x_edge)

            # Discretise and evaluate
            grad_eqn_disc = disc.process_symbol(grad_eqn)
            grad_approx = grad_eqn_disc.evaluate(y=y)

            # Return difference between approx and exact
            return grad_approx[:, 0] - grad_exact

        # Get errors
        ns = 100 * 2**np.arange(6)
        errs = {n: get_error(int(n)) for n in ns}
        # expect quadratic convergence at internal points
        errs_internal = np.array(
            [np.linalg.norm(errs[n][1:-1], np.inf) for n in ns])
        rates = np.log2(errs_internal[:-1] / errs_internal[1:])
        np.testing.assert_array_less(1.99 * np.ones_like(rates), rates)
        # expect linear convergence at the boundaries
        for idx in [0, -1]:
            err_boundary = np.array([errs[n][idx] for n in ns])
            rates = np.log2(err_boundary[:-1] / err_boundary[1:])
            np.testing.assert_array_less(0.98 * np.ones_like(rates), rates)
Exemplo n.º 19
0
    def test_p2d_spherical_grad_div_shapes_Neumann_bcs(self):
        """
        Test grad and div with Dirichlet boundary conditions (applied by grad on var)
        in the pseudo 2-dimensional case
        """

        mesh = get_p2d_mesh_for_testing()
        spatial_methods = {"negative particle": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)

        n_mesh = mesh["negative particle"]

        mesh.add_ghost_meshes()
        disc.mesh.add_ghost_meshes()

        # test grad
        var = pybamm.Variable("var", domain=["negative particle"])
        grad_eqn = pybamm.grad(var)
        disc.set_variable_slices([var])
        grad_eqn_disc = disc.process_symbol(grad_eqn)

        prim_pts = n_mesh[0].npts
        sec_pts = len(n_mesh)
        constant_y = np.kron(np.ones(sec_pts), np.ones(prim_pts))

        grad_eval = grad_eqn_disc.evaluate(None, constant_y)
        grad_eval = np.reshape(grad_eval, [sec_pts, prim_pts - 1])

        np.testing.assert_array_equal(grad_eval,
                                      np.zeros([sec_pts, prim_pts - 1]))

        # div
        # div (grad r^2) = 6, N_left = N_right = 0
        N = pybamm.grad(var)
        div_eqn = pybamm.div(N)
        boundary_conditions = {
            var.id: {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (pybamm.Scalar(0), "Neumann"),
            }
        }
        disc.bcs = boundary_conditions
        div_eqn_disc = disc.process_symbol(div_eqn)

        const = 6 * np.ones(sec_pts * prim_pts)
        div_eval = div_eqn_disc.evaluate(None, const)
        div_eval = np.reshape(div_eval, [sec_pts, prim_pts])
        np.testing.assert_array_almost_equal(div_eval,
                                             np.zeros([sec_pts, prim_pts]))
Exemplo n.º 20
0
    def test_grad_div_broadcast(self):
        # create mesh and discretisation
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        mesh = get_mesh_for_testing()
        disc = pybamm.Discretisation(mesh, spatial_methods)

        a = pybamm.PrimaryBroadcast(1, "negative electrode")
        grad_a = disc.process_symbol(pybamm.grad(a))
        np.testing.assert_array_equal(grad_a.evaluate(), 0)

        a_edge = pybamm.PrimaryBroadcastToEdges(1, "negative electrode")
        div_a = disc.process_symbol(pybamm.div(a_edge))
        np.testing.assert_array_equal(div_a.evaluate(), 0)

        div_grad_a = disc.process_symbol(pybamm.div(pybamm.grad(a)))
        np.testing.assert_array_equal(div_grad_a.evaluate(), 0)
Exemplo n.º 21
0
    def test_solver(self):
        # Create model
        model = pybamm.BaseModel()
        model.convert_to_format = "jax"
        domain = ["negative electrode", "separator", "positive electrode"]
        var = pybamm.Variable("var", domain=domain)
        model.rhs = {var: 0.1 * var}
        model.initial_conditions = {var: 1}
        # No need to set parameters; can use base discretisation (no spatial operators)

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        # Solve
        t_eval = np.linspace(0.0, 1.0, 80)
        y0 = model.concatenated_initial_conditions.evaluate().reshape(-1)
        rhs = pybamm.EvaluatorJax(model.concatenated_rhs)

        def fun(y, t):
            return rhs.evaluate(t=t, y=y).reshape(-1)

        t0 = time.perf_counter()
        y = pybamm.jax_bdf_integrate(fun, y0, t_eval, rtol=1e-8, atol=1e-8)
        t1 = time.perf_counter() - t0

        # test accuracy
        np.testing.assert_allclose(y[:, 0],
                                   np.exp(0.1 * t_eval),
                                   rtol=1e-6,
                                   atol=1e-6)

        t0 = time.perf_counter()
        y = pybamm.jax_bdf_integrate(fun, y0, t_eval, rtol=1e-8, atol=1e-8)
        t2 = time.perf_counter() - t0

        # second run should be much quicker
        self.assertLess(t2, t1)

        # test second run is accurate
        np.testing.assert_allclose(y[:, 0],
                                   np.exp(0.1 * t_eval),
                                   rtol=1e-6,
                                   atol=1e-6)
Exemplo n.º 22
0
    def test_spherical_grad_div_shapes_Neumann_bcs(self):
        """Test grad and div with Neumann boundary conditions (applied by div on N)"""

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"negative particle": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)

        combined_submesh = mesh.combine_submeshes("negative particle")

        # grad
        var = pybamm.Variable("var", domain="negative particle")
        grad_eqn = pybamm.grad(var)
        disc.set_variable_slices([var])
        grad_eqn_disc = disc.process_symbol(grad_eqn)

        constant_y = np.ones_like(combined_submesh[0].nodes[:, np.newaxis])
        np.testing.assert_array_equal(
            grad_eqn_disc.evaluate(None, constant_y),
            np.zeros_like(combined_submesh[0].edges[1:-1][:, np.newaxis]),
        )

        linear_y = combined_submesh[0].nodes
        np.testing.assert_array_almost_equal(
            grad_eqn_disc.evaluate(None, linear_y),
            np.ones_like(combined_submesh[0].edges[1:-1][:, np.newaxis]),
        )
        # div
        # div ( grad(r^2) ) == 6 , N_left = N_right = 0
        N = pybamm.grad(var)
        div_eqn = pybamm.div(N)
        boundary_conditions = {
            var.id: {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (pybamm.Scalar(0), "Neumann"),
            }
        }
        disc.bcs = boundary_conditions
        div_eqn_disc = disc.process_symbol(div_eqn)

        linear_y = combined_submesh[0].nodes
        const = 6 * np.ones(combined_submesh[0].npts)

        np.testing.assert_array_almost_equal(
            div_eqn_disc.evaluate(None, const),
            np.zeros((combined_submesh[0].npts, 1)))
Exemplo n.º 23
0
    def test_pure_neumann_poisson(self):
        # grad^2 u = 1, du/dz = 1 at z = 1, du/dn = 0 elsewhere, u has zero average
        u = pybamm.Variable("u", domain="current collector")
        c = pybamm.Variable("c")  # lagrange multiplier
        y = pybamm.SpatialVariable("y", ["current collector"])
        z = pybamm.SpatialVariable("z", ["current collector"])

        model = pybamm.BaseModel()
        # 0*c hack otherwise gives KeyError
        model.algebraic = {
            u:
            pybamm.laplacian(u) - pybamm.source(1, u) +
            c * pybamm.DefiniteIntegralVector(u, vector_type="column"),
            c:
            pybamm.Integral(u, [y, z]) + 0 * c,
        }
        model.initial_conditions = {u: pybamm.Scalar(0), c: pybamm.Scalar(0)}
        # set boundary conditions ("negative tab" = bottom of unit square,
        # "positive tab" = top of unit square, elsewhere normal derivative is zero)
        model.boundary_conditions = {
            u: {
                "negative tab": (0, "Neumann"),
                "positive tab": (1, "Neumann")
            }
        }
        model.variables = {"c": c, "u": u}
        # create discretisation
        mesh = get_unit_2p1D_mesh_for_testing(ypts=32,
                                              zpts=32,
                                              include_particles=False)
        spatial_methods = {
            "macroscale": pybamm.FiniteVolume(),
            "current collector": pybamm.ScikitFiniteElement(),
        }
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        # solve model
        solver = pybamm.AlgebraicSolver()
        solution = solver.solve(model)

        z = mesh["current collector"].coordinates[1, :][:, np.newaxis]
        u_exact = z**2 / 2 - 1 / 6
        np.testing.assert_array_almost_equal(solution.y[:-1],
                                             u_exact,
                                             decimal=1)
Exemplo n.º 24
0
    def test_semi_explicit_model(self):
        # Create model
        model = pybamm.BaseModel()
        model.convert_to_format = "jax"
        domain = ["negative electrode", "separator", "positive electrode"]
        var = pybamm.Variable("var", domain=domain)
        var2 = pybamm.Variable("var2", domain=domain)
        model.rhs = {var: 0.1 * var}
        model.algebraic = {var2: var2 - 2.0 * var}
        # give inconsistent initial conditions, should calculate correct ones
        model.initial_conditions = {var: 1.0, var2: 1.0}
        # No need to set parameters; can use base discretisation (no spatial operators)

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        # Solve
        solver = pybamm.JaxSolver(
            method='BDF', rtol=1e-8, atol=1e-8
        )
        t_eval = np.linspace(0, 1, 80)
        t0 = time.perf_counter()
        solution = solver.solve(model, t_eval)
        t_first_solve = time.perf_counter() - t0
        np.testing.assert_array_equal(solution.t, t_eval)
        soln = np.exp(0.1 * solution.t)
        np.testing.assert_allclose(solution.y[0], soln,
                                   rtol=1e-7, atol=1e-7)
        np.testing.assert_allclose(solution.y[-1], 2 * soln,
                                   rtol=1e-7, atol=1e-7)

        # Test time
        self.assertEqual(
            solution.total_time, solution.solve_time + solution.set_up_time
        )
        self.assertEqual(solution.termination, "final time")

        t0 = time.perf_counter()
        second_solution = solver.solve(model, t_eval)
        t_second_solve = time.perf_counter() - t0

        self.assertLess(t_second_solve, t_first_solve)
        np.testing.assert_array_equal(second_solution.y, solution.y)
Exemplo n.º 25
0
    def test_solver_sensitivities(self):
        # Create model
        model = pybamm.BaseModel()
        model.convert_to_format = "jax"
        domain = ["negative electrode", "separator", "positive electrode"]
        var = pybamm.Variable("var", domain=domain)
        model.rhs = {var: -pybamm.InputParameter("rate") * var}
        model.initial_conditions = {var: 1}

        # create discretisation
        mesh = get_mesh_for_testing(xpts=10)
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        # Solve
        t_eval = np.linspace(0, 10, 4)
        y0 = model.concatenated_initial_conditions.evaluate().reshape(-1)
        rhs = pybamm.EvaluatorJax(model.concatenated_rhs)

        def fun(y, t, inputs):
            return rhs.evaluate(t=t, y=y, inputs=inputs).reshape(-1)

        h = 0.0001
        rate = 0.1

        # create a dummy "model" where we calculate the sum of the time series
        @jax.jit
        def solve_bdf(rate):
            return jax.numpy.sum(
                pybamm.jax_bdf_integrate(fun,
                                         y0,
                                         t_eval, {'rate': rate},
                                         rtol=1e-9,
                                         atol=1e-9))

        # check answers with finite difference
        eval_plus = solve_bdf(rate + h)
        eval_neg = solve_bdf(rate - h)
        grad_num = (eval_plus - eval_neg) / (2 * h)

        grad_solve_bdf = jax.jit(jax.grad(solve_bdf))
        grad_bdf = grad_solve_bdf(rate)

        self.assertAlmostEqual(grad_bdf, grad_num, places=3)
Exemplo n.º 26
0
    def test_grad_div_shapes_Neumann_bcs(self):
        """Test grad and div with Neumann boundary conditions (applied by div on N)"""
        whole_cell = ["negative electrode", "separator", "positive electrode"]

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)

        combined_submesh = mesh.combine_submeshes(*whole_cell)

        # grad
        var = pybamm.Variable("var", domain=whole_cell)
        grad_eqn = pybamm.grad(var)
        disc.set_variable_slices([var])
        grad_eqn_disc = disc.process_symbol(grad_eqn)

        constant_y = np.ones_like(combined_submesh[0].nodes[:, np.newaxis])
        np.testing.assert_array_equal(
            grad_eqn_disc.evaluate(None, constant_y),
            np.zeros_like(combined_submesh[0].edges[1:-1][:, np.newaxis]),
        )

        # div
        N = pybamm.grad(var)
        div_eqn = pybamm.div(N)
        boundary_conditions = {
            var.id: {
                "left": (pybamm.Scalar(1), "Neumann"),
                "right": (pybamm.Scalar(1), "Neumann"),
            }
        }
        disc.bcs = boundary_conditions
        div_eqn_disc = disc.process_symbol(div_eqn)

        # Linear y should have laplacian zero
        linear_y = combined_submesh[0].nodes
        np.testing.assert_array_almost_equal(
            grad_eqn_disc.evaluate(None, linear_y),
            np.ones_like(combined_submesh[0].edges[1:-1][:, np.newaxis]),
        )
        np.testing.assert_array_almost_equal(
            div_eqn_disc.evaluate(None, linear_y),
            np.zeros_like(combined_submesh[0].nodes[:, np.newaxis]),
        )
Exemplo n.º 27
0
    def test_delta_function(self):
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)

        var = pybamm.Variable("var")
        delta_fn_left = pybamm.DeltaFunction(var, "left", "negative electrode")
        delta_fn_right = pybamm.DeltaFunction(var, "right",
                                              "negative electrode")
        disc.set_variable_slices([var])
        delta_fn_left_disc = disc.process_symbol(delta_fn_left)
        delta_fn_right_disc = disc.process_symbol(delta_fn_right)

        # Basic shape and type tests
        y = np.ones_like(mesh["negative electrode"][0].nodes[:, np.newaxis])
        # Left
        self.assertEqual(delta_fn_left_disc.domain, delta_fn_left.domain)
        self.assertEqual(delta_fn_left_disc.auxiliary_domains,
                         delta_fn_left.auxiliary_domains)
        self.assertIsInstance(delta_fn_left_disc, pybamm.Multiplication)
        self.assertIsInstance(delta_fn_left_disc.left, pybamm.Matrix)
        np.testing.assert_array_equal(
            delta_fn_left_disc.left.evaluate()[:, 1:], 0)
        self.assertEqual(delta_fn_left_disc.shape, y.shape)
        # Right
        self.assertEqual(delta_fn_right_disc.domain, delta_fn_right.domain)
        self.assertEqual(delta_fn_right_disc.auxiliary_domains,
                         delta_fn_right.auxiliary_domains)
        self.assertIsInstance(delta_fn_right_disc, pybamm.Multiplication)
        self.assertIsInstance(delta_fn_right_disc.left, pybamm.Matrix)
        np.testing.assert_array_equal(
            delta_fn_right_disc.left.evaluate()[:, :-1], 0)
        self.assertEqual(delta_fn_right_disc.shape, y.shape)

        # Value tests
        # Delta function should integrate to the same thing as variable
        var_disc = disc.process_symbol(var)
        x = pybamm.standard_spatial_vars.x_n
        delta_fn_int_disc = disc.process_symbol(
            pybamm.Integral(delta_fn_left, x))
        np.testing.assert_array_equal(
            var_disc.evaluate(y=y) * mesh["negative electrode"][0].edges[-1],
            np.sum(delta_fn_int_disc.evaluate(y=y)),
        )
Exemplo n.º 28
0
    def test_get_solve(self):
        # Create model
        model = pybamm.BaseModel()
        model.convert_to_format = "jax"
        domain = ["negative electrode", "separator", "positive electrode"]
        var = pybamm.Variable("var", domain=domain)
        model.rhs = {var: -pybamm.InputParameter("rate") * var}
        model.initial_conditions = {var: 1}
        # No need to set parameters; can use base discretisation (no spatial
        # operators)

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        # test that another method string gives error
        with self.assertRaises(ValueError):
            solver = pybamm.JaxSolver(method="not_real")

        # Solve
        solver = pybamm.JaxSolver(rtol=1e-8, atol=1e-8)
        t_eval = np.linspace(0, 5, 80)

        with self.assertRaisesRegex(RuntimeError,
                                    "Model is not set up for solving"):
            solver.get_solve(model, t_eval)

        solver.solve(model, t_eval, inputs={"rate": 0.1})
        solver = solver.get_solve(model, t_eval)
        y = solver({"rate": 0.1})

        np.testing.assert_allclose(y[0],
                                   np.exp(-0.1 * t_eval),
                                   rtol=1e-6,
                                   atol=1e-6)

        y = solver({"rate": 0.2})

        np.testing.assert_allclose(y[0],
                                   np.exp(-0.2 * t_eval),
                                   rtol=1e-6,
                                   atol=1e-6)
Exemplo n.º 29
0
    def test_solver_sensitivities(self):
        # Create model
        model = pybamm.BaseModel()
        model.convert_to_format = "jax"
        domain = ["negative electrode", "separator", "positive electrode"]
        var = pybamm.Variable("var", domain=domain)
        model.rhs = {var: -pybamm.InputParameter("rate") * var}
        model.initial_conditions = {var: 1.0}
        # No need to set parameters; can use base discretisation (no spatial operators)

        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {"macroscale": pybamm.FiniteVolume()}
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        for method in ['RK45', 'BDF']:
            # Solve
            solver = pybamm.JaxSolver(
                method=method, rtol=1e-8, atol=1e-8
            )
            t_eval = np.linspace(0, 1, 80)

            h = 0.0001
            rate = 0.1

            # need to solve the model once to get it set up by the base solver
            solver.solve(model, t_eval, inputs={'rate': rate})
            solve = solver.get_solve(model, t_eval)

            # create a dummy "model" where we calculate the sum of the time series
            def solve_model(rate):
                return jax.numpy.sum(solve({'rate': rate}))

            # check answers with finite difference
            eval_plus = solve_model(rate + h)
            eval_neg = solve_model(rate - h)
            grad_num = (eval_plus - eval_neg) / (2 * h)

            grad_solve = jax.jit(jax.grad(solve_model))
            grad = grad_solve(rate)

            self.assertAlmostEqual(grad, grad_num, places=1)
Exemplo n.º 30
0
 def test_definite_integral(self):
     mesh = get_2p1d_mesh_for_testing()
     spatial_methods = {
         "macroscale": pybamm.FiniteVolume(),
         "current collector": pybamm.ScikitFiniteElement(),
     }
     disc = pybamm.Discretisation(mesh, spatial_methods)
     var = pybamm.Variable("var", domain="current collector")
     y = pybamm.SpatialVariable("y", ["current collector"])
     z = pybamm.SpatialVariable("z", ["current collector"])
     integral_eqn = pybamm.Integral(var, [y, z])
     disc.set_variable_slices([var])
     integral_eqn_disc = disc.process_symbol(integral_eqn)
     y_test = 6 * np.ones(mesh["current collector"][0].npts)
     fem_mesh = mesh["current collector"][0]
     ly = fem_mesh.coordinates[0, -1]
     lz = fem_mesh.coordinates[1, -1]
     np.testing.assert_array_almost_equal(
         integral_eqn_disc.evaluate(None, y_test), 6 * ly * lz)