Beispiel #1
0
    def test_combine_geometries(self):
        geometry1Dmacro = pybamm.Geometry1DMacro()
        geometry1Dmicro = pybamm.Geometry1DMicro()
        geometry = pybamm.Geometry(geometry1Dmacro, geometry1Dmicro)
        self.assertEqual(
            set(geometry.keys()),
            set(
                [
                    "negative electrode",
                    "separator",
                    "positive electrode",
                    "negative particle",
                    "positive particle",
                    "current collector",
                ]
            ),
        )

        # update with custom geometry
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        x = pybamm.SpatialVariable("x", whole_cell)
        custom_geometry = {
            "negative electrode": {
                "primary": {x: {"min": pybamm.Scalar(1), "max": pybamm.Scalar(2)}}
            }
        }
        geometry = pybamm.Geometry(
            geometry1Dmacro, geometry1Dmicro, custom_geometry=custom_geometry
        )
        self.assertEqual(
            geometry["negative electrode"], custom_geometry["negative electrode"]
        )
Beispiel #2
0
 def test_combine_geometries_strings(self):
     geometry = pybamm.Geometry("1D macro", "1D micro")
     self.assertEqual(
         set(geometry.keys()),
         set(
             [
                 "negative electrode",
                 "separator",
                 "positive electrode",
                 "negative particle",
                 "positive particle",
                 "current collector",
             ]
         ),
     )
     geometry = pybamm.Geometry("3D macro", "1D micro")
     self.assertEqual(
         set(geometry.keys()),
         set(
             [
                 "negative electrode",
                 "separator",
                 "positive electrode",
                 "negative particle",
                 "positive particle",
                 "current collector",
             ]
         ),
     )
Beispiel #3
0
 def default_geometry(self):
     if self.options["dimensionality"] == 0:
         return pybamm.Geometry("1D macro", "1+1D micro")
     elif self.options["dimensionality"] == 1:
         return pybamm.Geometry("1+1D macro", "1+1D micro")
     elif self.options["dimensionality"] == 2:
         return pybamm.Geometry("2+1D macro", "1+1D micro")
Beispiel #4
0
 def default_geometry(self):
     dimensionality = self.options["dimensionality"]
     if dimensionality == 0:
         return pybamm.Geometry("1D macro", "1D micro")
     elif dimensionality == 1:
         return pybamm.Geometry("1+1D macro", "(1+0)+1D micro")
     elif dimensionality == 2:
         return pybamm.Geometry("2+1D macro", "(2+0)+1D micro")
    def test_processed_variable_1D_unknown_domain(self):
        x = pybamm.SpatialVariable("x",
                                   domain="SEI layer",
                                   coord_sys="cartesian")
        geometry = pybamm.Geometry({
            "SEI layer": {
                x: {
                    "min": pybamm.Scalar(0),
                    "max": pybamm.Scalar(1)
                }
            }
        })

        submesh_types = {"SEI layer": pybamm.Uniform1DSubMesh}
        var_pts = {x: 100}
        mesh = pybamm.Mesh(geometry, submesh_types, var_pts)

        nt = 100

        y_sol = np.zeros((var_pts[x], nt))
        solution = pybamm.Solution(
            np.linspace(0, 1, nt),
            y_sol,
            pybamm.BaseModel(),
            {},
            np.linspace(0, 1, 1),
            np.zeros((var_pts[x])),
            "test",
        )

        c = pybamm.StateVector(slice(0, var_pts[x]), domain=["SEI layer"])
        c.mesh = mesh["SEI layer"]
        c_casadi = to_casadi(c, y_sol)
        pybamm.ProcessedVariable([c], [c_casadi], solution, warn=False)
Beispiel #6
0
    def test_extrapolate_on_nonuniform_grid(self):
        geometry = pybamm.Geometry("1D micro")

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

        var = pybamm.standard_spatial_vars
        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[0].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[0].nodes
        y_surf = micro_submesh[0].edges[-1]
        np.testing.assert_array_almost_equal(
            surf_eqn_disc.evaluate(None, linear_y), y_surf
        )
    def test_processed_variable_1D_unknown_domain(self):
        x = pybamm.SpatialVariable("x",
                                   domain="SEI layer",
                                   coord_sys="cartesian")
        geometry = pybamm.Geometry()
        geometry.add_domain(
            "SEI layer",
            {
                "primary": {
                    x: {
                        "min": pybamm.Scalar(0),
                        "max": pybamm.Scalar(1)
                    }
                }
            },
        )

        submesh_types = {"SEI layer": pybamm.Uniform1DSubMesh}
        var_pts = {x: 100}
        mesh = pybamm.Mesh(geometry, submesh_types, var_pts)

        nt = 100

        solution = pybamm.Solution(
            np.linspace(0, 1, nt),
            np.zeros((var_pts[x], nt)),
            np.linspace(0, 1, 1),
            np.zeros((var_pts[x])),
            "test",
        )

        c = pybamm.StateVector(slice(0, var_pts[x]), domain=["SEI layer"])
        c.mesh = mesh["SEI layer"]
        pybamm.ProcessedVariable(c, solution)
Beispiel #8
0
 def default_geometry(self):
     var = pybamm.standard_spatial_vars
     geometry = {
         "current collector": {
             var.y: {
                 "min": 0,
                 "max": pybamm.geometric_parameters.l_y
             },
             var.z: {
                 "min": 0,
                 "max": pybamm.geometric_parameters.l_z
             },
             "tabs": {
                 "negative": {
                     "y_centre": pybamm.geometric_parameters.centre_y_tab_n,
                     "z_centre": pybamm.geometric_parameters.centre_z_tab_n,
                     "width": pybamm.geometric_parameters.l_tab_n,
                 },
                 "positive": {
                     "y_centre": pybamm.geometric_parameters.centre_y_tab_p,
                     "z_centre": pybamm.geometric_parameters.centre_z_tab_p,
                     "width": pybamm.geometric_parameters.l_tab_p,
                 },
             },
         }
     }
     return pybamm.Geometry(geometry)
Beispiel #9
0
def get_1p1d_mesh_for_testing(
    xpts=None, zpts=15, cc_submesh=pybamm.MeshGenerator(pybamm.Uniform1DSubMesh)
):
    geometry = pybamm.Geometry("1+1D macro")
    return get_mesh_for_testing(
        xpts=xpts, zpts=zpts, geometry=geometry, cc_submesh=cc_submesh
    )
Beispiel #10
0
 def default_geometry(self):
     geometry = {}
     var = pybamm.standard_spatial_vars
     if self.options["dimensionality"] == 1:
         geometry["current collector"] = {
             var.z: {"min": 0, "max": 1},
             "tabs": {
                 "negative": {
                     "z_centre": pybamm.geometric_parameters.centre_z_tab_n
                 },
                 "positive": {
                     "z_centre": pybamm.geometric_parameters.centre_z_tab_p
                 },
             },
         }
     elif self.options["dimensionality"] == 2:
         geometry["current collector"] = {
             var.y: {"min": 0, "max": pybamm.geometric_parameters.l_y},
             var.z: {"min": 0, "max": pybamm.geometric_parameters.l_z},
             "tabs": {
                 "negative": {
                     "y_centre": pybamm.geometric_parameters.centre_y_tab_n,
                     "z_centre": pybamm.geometric_parameters.centre_z_tab_n,
                     "width": pybamm.geometric_parameters.l_tab_n,
                 },
                 "positive": {
                     "y_centre": pybamm.geometric_parameters.centre_y_tab_p,
                     "z_centre": pybamm.geometric_parameters.centre_z_tab_p,
                     "width": pybamm.geometric_parameters.l_tab_p,
                 },
             },
         }
     return pybamm.Geometry(geometry)
Beispiel #11
0
def get_2p1d_mesh_for_testing(
    xpts=None,
    ypts=15,
    zpts=15,
    cc_submesh=pybamm.MeshGenerator(pybamm.ScikitUniform2DSubMesh),
):
    geometry = pybamm.Geometry("2+1D macro")
    return get_mesh_for_testing(
        xpts=xpts, zpts=zpts, geometry=geometry, cc_submesh=cc_submesh
    )
Beispiel #12
0
    def test_combine_geometries_3D(self):
        geometry3Dmacro = pybamm.Geometry3DMacro()
        geometry1Dmicro = pybamm.Geometry1DMicro()
        geometry = pybamm.Geometry(geometry3Dmacro, geometry1Dmicro)
        self.assertEqual(
            set(geometry.keys()),
            set([
                "negative electrode",
                "separator",
                "positive electrode",
                "negative particle",
                "positive particle",
                "current collector",
            ]),
        )

        with self.assertRaises(ValueError):
            geometry1Dmacro = pybamm.Geometry1DMacro()
            geometry = pybamm.Geometry(geometry3Dmacro, geometry1Dmacro)
Beispiel #13
0
def get_mesh_for_testing(xpts=None,
                         rpts=10,
                         ypts=15,
                         zpts=15,
                         geometry=None,
                         cc_submesh=None):
    param = pybamm.ParameterValues(
        values={
            "Electrode width [m]": 0.4,
            "Electrode height [m]": 0.5,
            "Negative tab width [m]": 0.1,
            "Negative tab centre y-coordinate [m]": 0.1,
            "Negative tab centre z-coordinate [m]": 0.0,
            "Positive tab width [m]": 0.1,
            "Positive tab centre y-coordinate [m]": 0.3,
            "Positive tab centre z-coordinate [m]": 0.5,
            "Negative electrode thickness [m]": 0.3,
            "Separator thickness [m]": 0.3,
            "Positive electrode thickness [m]": 0.3,
        })

    if geometry is None:
        geometry = pybamm.Geometry("1D macro", "1D micro")
    param.process_geometry(geometry)

    submesh_types = {
        "negative electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
        "separator": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
        "positive electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
        "negative particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
        "positive particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
        "current collector": pybamm.MeshGenerator(pybamm.SubMesh0D),
    }
    if cc_submesh:
        submesh_types["current collector"] = cc_submesh

    if xpts is None:
        xn_pts, xs_pts, xp_pts = 40, 25, 35
    else:
        xn_pts, xs_pts, xp_pts = xpts, xpts, xpts
    var = pybamm.standard_spatial_vars
    var_pts = {
        var.x_n: xn_pts,
        var.x_s: xs_pts,
        var.x_p: xp_pts,
        var.r_n: rpts,
        var.r_p: rpts,
        var.y: ypts,
        var.z: zpts,
    }

    return pybamm.Mesh(geometry, submesh_types, var_pts)
Beispiel #14
0
    def test_multiple_meshes_macro(self):
        param = pybamm.ParameterValues(
            values={
                "Negative electrode thickness [m]": 0.1,
                "Separator thickness [m]": 0.2,
                "Positive electrode thickness [m]": 0.3,
                "Electrode height [m]": 0.4,
                "Negative tab centre z-coordinate [m]": 0.0,
                "Positive tab centre z-coordinate [m]": 0.4,
            })

        geometry = pybamm.Geometry("1+1D macro")
        param.process_geometry(geometry)
        # provide mesh properties

        var = pybamm.standard_spatial_vars
        var_pts = {var.x_n: 10, var.x_s: 15, var.x_p: 20, var.z: 5}
        submesh_types = {
            "negative electrode":
            pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "separator": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "positive electrode":
            pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "current collector": pybamm.MeshGenerator(pybamm.SubMesh0D),
        }

        mesh = pybamm.Mesh(geometry, submesh_types, var_pts)

        # check types
        self.assertIsInstance(mesh["negative electrode"], list)
        self.assertIsInstance(mesh["separator"], list)
        self.assertIsInstance(mesh["positive electrode"], list)
        self.assertEqual(len(mesh["negative electrode"]), 5)
        self.assertEqual(len(mesh["separator"]), 5)
        self.assertEqual(len(mesh["positive electrode"]), 5)

        for i in range(5):
            self.assertIsInstance(mesh["negative electrode"][i],
                                  pybamm.Uniform1DSubMesh)
            self.assertIsInstance(mesh["separator"][i],
                                  pybamm.Uniform1DSubMesh)
            self.assertIsInstance(mesh["positive electrode"][i],
                                  pybamm.Uniform1DSubMesh)
            self.assertEqual(mesh["negative electrode"][i].npts, 10)
            self.assertEqual(mesh["separator"][i].npts, 15)
            self.assertEqual(mesh["positive electrode"][i].npts, 20)
Beispiel #15
0
    def test_add_domain(self):
        L = pybamm.Parameter("L")
        zero = pybamm.Scalar(0)
        x = pybamm.SpatialVariable("x", domain=["negative electrode"])
        geometry = pybamm.Geometry()

        geometry.add_domain("name_of_domain", {"primary": {x: {"min": zero, "max": L}}})

        geometry.add_domain("name_of_domain", {"tabs": {"negative": {"z_centre": L}}})

        # name must be a string
        with self.assertRaisesRegex(ValueError, "name must be a string"):
            geometry.add_domain(123, {"primary": {x: {"min": zero, "max": L}}})

        # keys of geometry must be either \"primary\" or \"secondary\"
        with self.assertRaisesRegex(ValueError, "primary.*secondary"):
            geometry.add_domain(
                "name_of_domain", {"primaryy": {x: {"min": zero, "max": L}}}
            )

        # inner dict of geometry must have pybamm.SpatialVariable as keys
        with self.assertRaisesRegex(ValueError, "pybamm\.SpatialVariable as keys"):
            geometry.add_domain(
                "name_of_domain", {"primary": {L: {"min": zero, "max": L}}}
            )

        # no minimum extents for variable
        with self.assertRaisesRegex(ValueError, "minimum"):
            geometry.add_domain("name_of_domain", {"primary": {x: {"max": L}}})

        # no maximum extents for variable
        with self.assertRaisesRegex(ValueError, "maximum"):
            geometry.add_domain("name_of_domain", {"primary": {x: {"min": zero}}})

        # tabs region must be \"negative\" or \"positive\"
        with self.assertRaisesRegex(ValueError, "negative.*positive"):
            geometry.add_domain(
                "name_of_domain", {"tabs": {"negativee": {"z_centre": L}}}
            )

        # tabs region params must be \"y_centre\", "\"z_centre\" or \"width\"
        with self.assertRaisesRegex(ValueError, "y_centre.*z_centre.*width"):
            geometry.add_domain(
                "name_of_domain", {"tabs": {"negative": {"z_centree": L}}}
            )
Beispiel #16
0
    def test_read_parameters(self):
        L_n = pybamm.geometric_parameters.L_n
        L_s = pybamm.geometric_parameters.L_s
        L_p = pybamm.geometric_parameters.L_p
        L_y = pybamm.geometric_parameters.L_y
        L_z = pybamm.geometric_parameters.L_z
        tab_n_y = pybamm.geometric_parameters.Centre_y_tab_n
        tab_n_z = pybamm.geometric_parameters.Centre_z_tab_n
        L_tab_n = pybamm.geometric_parameters.L_tab_n
        tab_p_y = pybamm.geometric_parameters.Centre_y_tab_p
        tab_p_z = pybamm.geometric_parameters.Centre_z_tab_p
        L_tab_p = pybamm.geometric_parameters.L_tab_p

        geometry = pybamm.Geometry("2+1D macro", "(2+1)+1D micro")

        self.assertEqual(
            set([x.name for x in geometry.parameters]),
            set(
                [
                    x.name
                    for x in [
                        L_n,
                        L_s,
                        L_p,
                        L_y,
                        L_z,
                        tab_n_y,
                        tab_n_z,
                        L_tab_n,
                        tab_p_y,
                        tab_p_z,
                        L_tab_p,
                    ]
                ]
            ),
        )
        self.assertTrue(
            all(isinstance(x, pybamm.Parameter) for x in geometry.parameters)
        )
Beispiel #17
0
    def test_multiple_meshes(self):
        param = pybamm.ParameterValues(
            values={
                "Negative electrode thickness [m]": 0.1,
                "Separator thickness [m]": 0.2,
                "Positive electrode thickness [m]": 0.3,
            })

        geometry = pybamm.Geometry("1+1D micro")
        param.process_geometry(geometry)

        # provide mesh properties

        var = pybamm.standard_spatial_vars
        var_pts = {var.x_n: 10, var.x_p: 10, var.r_n: 5, var.r_p: 6}
        submesh_types = {
            "negative particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "positive particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "current collector": pybamm.MeshGenerator(pybamm.SubMesh0D),
        }

        mesh = pybamm.Mesh(geometry, submesh_types, var_pts)

        # check types
        self.assertIsInstance(mesh["negative particle"], list)
        self.assertIsInstance(mesh["positive particle"], list)
        self.assertEqual(len(mesh["negative particle"]), 10)
        self.assertEqual(len(mesh["positive particle"]), 10)

        for i in range(10):
            self.assertIsInstance(mesh["negative particle"][i],
                                  pybamm.Uniform1DSubMesh)
            self.assertIsInstance(mesh["positive particle"][i],
                                  pybamm.Uniform1DSubMesh)
            self.assertEqual(mesh["negative particle"][i].npts, 5)
            self.assertEqual(mesh["positive particle"][i].npts, 6)
Beispiel #18
0
    "positive interface current"] = pybamm.interface.CurrentForInverseButlerVolmer(
        model.param, "Positive", "lithium-ion main")
model.submodels[
    "electrolyte diffusion"] = pybamm.electrolyte_diffusion.ConstantConcentration(
        model.param)
model.submodels[
    "electrolyte conductivity"] = pybamm.electrolyte_conductivity.LeadingOrder(
        model.param)
model.submodels["negative sei"] = pybamm.sei.NoSEI(model.param, "Negative")
model.submodels["positive sei"] = pybamm.sei.NoSEI(model.param, "Positive")

# build model
model.build_model()

# create geometry
geometry = pybamm.Geometry("1D macro", "1D micro")

# process model and geometry
param = model.default_parameter_values
param.process_model(model)
param.process_geometry(geometry)

# set mesh
# Note: li-ion base model has defaults for mesh and var_pts
mesh = pybamm.Mesh(geometry, model.default_submesh_types,
                   model.default_var_pts)

# discretise model
# Note: li-ion base model has default spatial methods
disc = pybamm.Discretisation(mesh, model.default_spatial_methods)
disc.process_model(model)
Beispiel #19
0
def battery_geometry(include_particles=True, current_collector_dimension=0):
    """
    A convenience function to create battery geometries.

    Parameters
    ----------
    include_particles : bool
        Whether to include particle domains
    current_collector_dimensions : int, default
        The dimensions of the current collector. Should be 0 (default), 1 or 2

    Returns
    -------
    :class:`pybamm.Geometry`
        A geometry class for the battery

    """
    var = pybamm.standard_spatial_vars
    geo = pybamm.geometric_parameters
    l_n = geo.l_n
    l_s = geo.l_s

    geometry = {
        "negative electrode": {
            var.x_n: {
                "min": 0,
                "max": l_n
            }
        },
        "separator": {
            var.x_s: {
                "min": l_n,
                "max": l_n + l_s
            }
        },
        "positive electrode": {
            var.x_p: {
                "min": l_n + l_s,
                "max": 1
            }
        },
    }
    # Add particle domains
    if include_particles is True:
        geometry.update({
            "negative particle": {
                var.r_n: {
                    "min": 0,
                    "max": 1
                }
            },
            "positive particle": {
                var.r_p: {
                    "min": 0,
                    "max": 1
                }
            },
        })

    if current_collector_dimension == 0:
        geometry["current collector"] = {var.z: {"position": 1}}
    elif current_collector_dimension == 1:
        geometry["current collector"] = {
            var.z: {
                "min": 0,
                "max": 1
            },
            "tabs": {
                "negative": {
                    "z_centre": geo.centre_z_tab_n
                },
                "positive": {
                    "z_centre": geo.centre_z_tab_p
                },
            },
        }
    elif current_collector_dimension == 2:
        geometry["current collector"] = {
            var.y: {
                "min": 0,
                "max": geo.l_y
            },
            var.z: {
                "min": 0,
                "max": geo.l_z
            },
            "tabs": {
                "negative": {
                    "y_centre": geo.centre_y_tab_n,
                    "z_centre": geo.centre_z_tab_n,
                    "width": geo.l_tab_n,
                },
                "positive": {
                    "y_centre": geo.centre_y_tab_p,
                    "z_centre": geo.centre_z_tab_p,
                    "width": geo.l_tab_p,
                },
            },
        }
    else:
        raise pybamm.GeometryError(
            "Invalid current collector dimension '{}' (should be 0, 1 or 2)".
            format(current_collector_dimension))

    return pybamm.Geometry(geometry)
 def default_geometry(self):
     return pybamm.Geometry("2D current collector")
Beispiel #21
0
def get_p2d_mesh_for_testing(xpts=None, rpts=10):
    geometry = pybamm.Geometry("1D macro", "1+1D micro")
    return get_mesh_for_testing(xpts=xpts, rpts=rpts, geometry=geometry)
Beispiel #22
0
 def default_geometry(self):
     return pybamm.Geometry("1D macro", "1+1D micro")
def half_cell_geometry(
    include_particles=True, current_collector_dimension=0, working_electrode="positive"
):
    """
    A convenience function to create battery geometries.

    Parameters
    ----------
    include_particles : bool
        Whether to include particle domains
    current_collector_dimensions : int, default
        The dimensions of the current collector. Should be 0 (default), 1 or 2
    current_collector_dimensions : string
        The electrode taking as working electrode. Should be "positive" or "negative"

    Returns
    -------
    :class:`pybamm.Geometry`
        A geometry class for the battery

    """
    var = half_cell_spatial_vars
    geo = pybamm.geometric_parameters
    if working_electrode == "positive":
        l_w = geo.l_p
    elif working_electrode == "negative":
        l_w = geo.l_n
    else:
        raise ValueError(
            "The option 'working_electrode' should be either 'positive'"
            " or 'negative'"
        )
    l_Li = geo.l_Li
    l_s = geo.l_s

    geometry = {
        "lithium counter electrode": {var.x_Li: {"min": 0, "max": l_Li}},
        "separator": {var.x_s: {"min": l_Li, "max": l_Li + l_s}},
        "working electrode": {var.x_w: {"min": l_Li + l_s, "max": l_Li + l_s + l_w}},
    }
    # Add particle domains
    if include_particles is True:
        geometry.update({"working particle": {var.r_w: {"min": 0, "max": 1}}})

    if current_collector_dimension == 0:
        geometry["current collector"] = {var.z: {"position": 1}}
    elif current_collector_dimension == 1:
        geometry["current collector"] = {
            var.z: {"min": 0, "max": 1},
            "tabs": {
                "negative": {"z_centre": geo.centre_z_tab_n},
                "positive": {"z_centre": geo.centre_z_tab_p},
            },
        }
    elif current_collector_dimension == 2:
        geometry["current collector"] = {
            var.y: {"min": 0, "max": geo.l_y},
            var.z: {"min": 0, "max": geo.l_z},
            "tabs": {
                "negative": {
                    "y_centre": geo.centre_y_tab_n,
                    "z_centre": geo.centre_z_tab_n,
                    "width": geo.l_tab_n,
                },
                "positive": {
                    "y_centre": geo.centre_y_tab_p,
                    "z_centre": geo.centre_z_tab_p,
                    "width": geo.l_tab_p,
                },
            },
        }
    else:
        raise pybamm.GeometryError(
            "Invalid current collector dimension '{}' (should be 0, 1 or 2)".format(
                current_collector_dimension
            )
        )

    return pybamm.Geometry(geometry)
Beispiel #24
0
    def __init__(self, param=None):
        # Set fixed parameters here
        if param is None:
            param = pybamm.ParameterValues({
                "Far-field concentration of S(soln) [mol cm-3]":
                1e-6,
                "Diffusion Constant [cm2 s-1]":
                7.2e-6,
                "Faraday Constant [C mol-1]":
                96485.3328959,
                "Gas constant [J K-1 mol-1]":
                8.314459848,
                "Electrode Area [cm2]":
                0.07,
                "Temperature [K]":
                273.0,
                "Voltage frequency [rad s-1]":
                9.0152,
                "Voltage start [V]":
                0.4,
                "Voltage reverse [V]":
                -0.4,
                "Voltage amplitude [V]":
                0.0,  # 0.05,
                "Scan Rate [V s-1]":
                0.05,
                "Electrode Coverage [mol cm2]":
                6.5e-12,
            })

        # Create dimensional fixed parameters
        D = pybamm.Parameter("Diffusion Constant [cm2 s-1]")
        F = pybamm.Parameter("Faraday Constant [C mol-1]")
        R = pybamm.Parameter("Gas constant [J K-1 mol-1]")
        a = pybamm.Parameter("Electrode Area [cm2]")
        T = pybamm.Parameter("Temperature [K]")
        omega_d = pybamm.Parameter("Voltage frequency [rad s-1]")
        E_start_d = pybamm.Parameter("Voltage start [V]")
        E_reverse_d = pybamm.Parameter("Voltage reverse [V]")
        deltaE_d = pybamm.Parameter("Voltage amplitude [V]")
        v = pybamm.Parameter("Scan Rate [V s-1]")
        Gamma = pybamm.Parameter("Electrode Coverage [mol cm2]")

        # Create dimensional input parameters
        E0_d = pybamm.InputParameter("Reversible Potential [V]")
        k0_d = pybamm.InputParameter("Redox Rate [s-1]")
        kcat_d = pybamm.InputParameter("Catalytic Rate [cm3 mol-l s-1]")
        alpha = pybamm.InputParameter("Symmetry factor [non-dim]")
        Cdl_d = pybamm.InputParameter("Capacitance [F]")
        Ru_d = pybamm.InputParameter("Uncompensated Resistance [Ohm]")

        # Create scaling factors for non-dimensionalisation
        E_0 = R * T / F
        T_0 = E_0 / v
        I_0 = F * a * Gamma / T
        L_0 = pybamm.sqrt(D * T_0)

        # Non-dimensionalise parameters
        E0 = E0_d / E_0
        k0 = k0_d * T_0
        kcat = kcat_d * Gamma * L_0 / D
        Cdl = Cdl_d * a * E_0 / (I_0 * T_0)
        Ru = Ru_d * I_0 / E_0

        omega = 2 * np.pi * omega_d * T_0
        E_start = E_start_d / E_0
        E_reverse = E_reverse_d / E_0
        t_reverse = E_start - E_reverse
        deltaE = deltaE_d / E_0

        # Input voltage protocol
        Edc_forward = -pybamm.t
        Edc_backwards = pybamm.t - 2 * t_reverse
        Eapp = E_start + \
            (pybamm.t <= t_reverse) * Edc_forward + \
            (pybamm.t > t_reverse) * Edc_backwards + \
            deltaE * pybamm.sin(omega * pybamm.t)

        # create PyBaMM model object
        model = pybamm.BaseModel()

        # Create state variables for model
        theta = pybamm.Variable("O(surf) [non-dim]")
        c = pybamm.Variable("S(soln) [non-dim]", domain="solution")
        i = pybamm.Variable("Current [non-dim]")

        # Effective potential
        Eeff = Eapp - i * Ru

        # Faridaic current (Butler Volmer)
        i_f = k0 * ((1 - theta) * pybamm.exp(
            (1 - alpha) * (Eeff - E0)) - theta * pybamm.exp(-alpha *
                                                            (Eeff - E0)))

        c_at_electrode = pybamm.BoundaryValue(c, "left")

        # Catalytic current
        i_cat = kcat * c_at_electrode * (1 - theta)

        # ODE equations
        model.rhs = {
            theta: i_f + i_cat,
            i: 1 / (Cdl * Ru) * (i_f + Cdl * Eapp.diff(pybamm.t) - i - i_cat),
            c: pybamm.div(pybamm.grad(c)),
        }

        # algebraic equations (none)
        model.algebraic = {}

        # Boundary and initial conditions
        model.boundary_conditions = {
            c: {
                "right": (pybamm.Scalar(1), "Dirichlet"),
                "left": (i_cat, "Neumann"),
            }
        }

        model.initial_conditions = {
            theta: pybamm.Scalar(1),
            i: Cdl * Eapp.diff(pybamm.t),
            c: pybamm.Scalar(1),
        }

        # set spatial variables and solution domain geometry
        x = pybamm.SpatialVariable('x', domain="solution")
        model.geometry = pybamm.Geometry({
            "solution": {
                x: {
                    "min": pybamm.Scalar(0),
                    "max": pybamm.Scalar(20)
                }
            }
        })
        model.var_pts = {x: 100}

        # Using Finite Volume discretisation on an expanding 1D grid
        model.submesh_types = {
            "solution":
            pybamm.MeshGenerator(pybamm.Exponential1DSubMesh, {'side': 'left'})
        }
        model.spatial_methods = {"solution": pybamm.FiniteVolume()}

        # model variables
        model.variables = {
            "Current [non-dim]": i,
            "O(surf) [non-dim]": theta,
            "S(soln) at electrode [non-dim]": c_at_electrode,
            "Applied Voltage [non-dim]": Eapp,
        }
        # --------------------------------

        # Set model parameters
        param.process_model(model)
        geometry = model.geometry
        param.process_geometry(geometry)

        # Create mesh and discretise model
        mesh = pybamm.Mesh(geometry, model.submesh_types, model.var_pts)
        disc = pybamm.Discretisation(mesh, model.spatial_methods)
        disc.process_model(model)

        # Create solver
        model.convert_to_format = 'python'
        solver = pybamm.ScipySolver(method='BDF', rtol=1e-6, atol=1e-6)
        #solver = pybamm.CasadiSolver(mode='fast', rtol=1e-8, atol=1e-10)

        # Store discretised model and solver
        self._model = model
        self._param = param
        self._solver = solver
        self._omega_d = param.process_symbol(omega_d).evaluate()
        self._I_0 = param.process_symbol(I_0).evaluate()
        self._T_0 = param.process_symbol(T_0).evaluate()
Beispiel #25
0
    def test_combine_submeshes(self):
        param = pybamm.ParameterValues(
            values={
                "Negative electrode thickness [m]": 0.1,
                "Separator thickness [m]": 0.2,
                "Positive electrode thickness [m]": 0.3,
            })

        geometry = pybamm.Geometry("1D macro", "1D micro")
        param.process_geometry(geometry)

        # provide mesh properties
        var = pybamm.standard_spatial_vars
        var_pts = {
            var.x_n: 10,
            var.x_s: 10,
            var.x_p: 12,
            var.r_n: 5,
            var.r_p: 6
        }
        submesh_types = {
            "negative electrode":
            pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "separator": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "positive electrode":
            pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "negative particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "positive particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh),
            "current collector": pybamm.MeshGenerator(pybamm.SubMesh0D),
        }

        mesh_type = pybamm.Mesh

        # create mesh
        mesh = mesh_type(geometry, submesh_types, var_pts)

        # create submesh
        submesh = mesh.combine_submeshes("negative electrode", "separator")
        self.assertEqual(submesh[0].edges[0], 0)
        self.assertEqual(submesh[0].edges[-1], mesh["separator"][0].edges[-1])
        np.testing.assert_almost_equal(
            submesh[0].nodes - np.concatenate([
                mesh["negative electrode"][0].nodes, mesh["separator"][0].nodes
            ]),
            0,
        )
        np.testing.assert_almost_equal(submesh[0].internal_boundaries,
                                       [0.1 / 0.6])
        with self.assertRaises(pybamm.DomainError):
            mesh.combine_submeshes("negative electrode", "positive electrode")

        # test errors
        geometry = {
            "negative electrode": {
                "primary": {
                    var.x_n: {
                        "min": pybamm.Scalar(0),
                        "max": pybamm.Scalar(0.5)
                    }
                }
            },
            "negative particle": {
                "primary": {
                    var.r_n: {
                        "min": pybamm.Scalar(0.5),
                        "max": pybamm.Scalar(1)
                    }
                }
            },
        }
        param.process_geometry(geometry)

        mesh = pybamm.Mesh(geometry, submesh_types, var_pts)

        with self.assertRaisesRegex(pybamm.DomainError, "trying"):
            mesh.combine_submeshes("negative electrode", "negative particle")

        with self.assertRaisesRegex(
                ValueError, "Submesh domains being combined cannot be empty"):
            mesh.combine_submeshes()
Beispiel #26
0
    def test_1D_different_domains(self):
        # Negative electrode domain
        var = pybamm.Variable("var", domain=["negative electrode"])
        x = pybamm.SpatialVariable("x", domain=["negative electrode"])

        disc = tests.get_discretisation_for_testing()
        disc.set_variable_slices([var])
        x_sol = disc.process_symbol(x).entries[:, 0]
        var_sol = disc.process_symbol(var)

        t_sol = [0]
        y_sol = np.ones_like(x_sol)[:, np.newaxis] * 5
        sol = pybamm.Solution(t_sol, y_sol)
        pybamm.ProcessedSymbolicVariable(var_sol, sol)

        # Particle domain
        var = pybamm.Variable("var", domain=["negative particle"])
        r = pybamm.SpatialVariable("r", domain=["negative particle"])

        disc = tests.get_discretisation_for_testing()
        disc.set_variable_slices([var])
        r_sol = disc.process_symbol(r).entries[:, 0]
        var_sol = disc.process_symbol(var)

        t_sol = [0]
        y_sol = np.ones_like(r_sol)[:, np.newaxis] * 5
        sol = pybamm.Solution(t_sol, y_sol)
        pybamm.ProcessedSymbolicVariable(var_sol, sol)

        # Current collector domain
        var = pybamm.Variable("var", domain=["current collector"])
        z = pybamm.SpatialVariable("z", domain=["current collector"])

        disc = tests.get_1p1d_discretisation_for_testing()
        disc.set_variable_slices([var])
        z_sol = disc.process_symbol(z).entries[:, 0]
        var_sol = disc.process_symbol(var)

        t_sol = [0]
        y_sol = np.ones_like(z_sol)[:, np.newaxis] * 5
        sol = pybamm.Solution(t_sol, y_sol)
        pybamm.ProcessedSymbolicVariable(var_sol, sol)

        # Other domain
        var = pybamm.Variable("var", domain=["line"])
        x = pybamm.SpatialVariable("x", domain=["line"])

        geometry = pybamm.Geometry(
            {"line": {x: {"min": pybamm.Scalar(0), "max": pybamm.Scalar(1)}}}
        )
        submesh_types = {"line": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh)}
        var_pts = {x: 10}
        mesh = pybamm.Mesh(geometry, submesh_types, var_pts)
        disc = pybamm.Discretisation(mesh, {"line": pybamm.FiniteVolume()})
        disc.set_variable_slices([var])
        x_sol = disc.process_symbol(x).entries[:, 0]
        var_sol = disc.process_symbol(var)

        t_sol = [0]
        y_sol = np.ones_like(x_sol)[:, np.newaxis] * 5
        sol = pybamm.Solution(t_sol, y_sol)
        pybamm.ProcessedSymbolicVariable(var_sol, sol)

        # 2D fails
        var = pybamm.Variable(
            "var",
            domain=["negative particle"],
            auxiliary_domains={"secondary": "negative electrode"},
        )
        r = pybamm.SpatialVariable(
            "r",
            domain=["negative particle"],
            auxiliary_domains={"secondary": "negative electrode"},
        )

        disc = tests.get_p2d_discretisation_for_testing()
        disc.set_variable_slices([var])
        r_sol = disc.process_symbol(r).entries[:, 0]
        var_sol = disc.process_symbol(var)

        t_sol = [0]
        y_sol = np.ones_like(r_sol)[:, np.newaxis] * 5
        sol = pybamm.Solution(t_sol, y_sol)
        with self.assertRaisesRegex(NotImplementedError, "Shape not recognized"):
            pybamm.ProcessedSymbolicVariable(var_sol, sol)
Beispiel #27
0
    def __init__(self, n=100, max_x=10, param=None):
        # Set fixed parameters here
        if param is None:
            param = pybamm.ParameterValues({
                "Far-field concentration of A [mol cm-3]":
                1e-6,
                "Diffusion Constant [cm2 s-1]":
                7.2e-6,
                "Faraday Constant [C mol-1]":
                96485.3328959,
                "Gas constant [J K-1 mol-1]":
                8.314459848,
                "Electrode Area [cm2]":
                0.07,
                "Temperature [K]":
                297.0,
                "Voltage frequency [rad s-1]":
                9.0152,
                "Voltage start [V]":
                0.5,
                "Voltage reverse [V]":
                -0.1,
                "Voltage amplitude [V]":
                0.08,
                "Scan Rate [V s-1]":
                0.08941,
            })

        # Create dimensional fixed parameters
        c_inf = pybamm.Parameter("Far-field concentration of A [mol cm-3]")
        D = pybamm.Parameter("Diffusion Constant [cm2 s-1]")
        F = pybamm.Parameter("Faraday Constant [C mol-1]")
        R = pybamm.Parameter("Gas constant [J K-1 mol-1]")
        S = pybamm.Parameter("Electrode Area [cm2]")
        T = pybamm.Parameter("Temperature [K]")

        E_start_d = pybamm.Parameter("Voltage start [V]")
        E_reverse_d = pybamm.Parameter("Voltage reverse [V]")
        deltaE_d = pybamm.Parameter("Voltage amplitude [V]")
        v = pybamm.Parameter("Scan Rate [V s-1]")

        # Create dimensional input parameters
        E0 = pybamm.InputParameter("Reversible Potential [non-dim]")
        k0 = pybamm.InputParameter("Reaction Rate [non-dim]")
        alpha = pybamm.InputParameter("Symmetry factor [non-dim]")
        Cdl = pybamm.InputParameter("Capacitance [non-dim]")
        Ru = pybamm.InputParameter("Uncompensated Resistance [non-dim]")
        omega_d = pybamm.InputParameter("Voltage frequency [rad s-1]")

        E0_d = pybamm.InputParameter("Reversible Potential [V]")
        k0_d = pybamm.InputParameter("Reaction Rate [s-1]")
        alpha = pybamm.InputParameter("Symmetry factor [non-dim]")
        Cdl_d = pybamm.InputParameter("Capacitance [F]")
        Ru_d = pybamm.InputParameter("Uncompensated Resistance [Ohm]")

        # Create scaling factors for non-dimensionalisation
        E_0 = R * T / F
        T_0 = E_0 / v
        L_0 = pybamm.sqrt(D * T_0)
        I_0 = D * F * S * c_inf / L_0

        # Non-dimensionalise parameters
        E0 = E0_d / E_0
        k0 = k0_d * L_0 / D
        Cdl = Cdl_d * S * E_0 / (I_0 * T_0)
        Ru = Ru_d * I_0 / E_0
        omega = 2 * np.pi * omega_d * T_0

        E_start = E_start_d / E_0
        E_reverse = E_reverse_d / E_0
        t_reverse = E_start - E_reverse
        deltaE = deltaE_d / E_0

        # Input voltage protocol
        Edc_forward = -pybamm.t
        Edc_backwards = pybamm.t - 2 * t_reverse
        Eapp = E_start + \
            (pybamm.t <= t_reverse) * Edc_forward + \
            (pybamm.t > t_reverse) * Edc_backwards + \
            deltaE * pybamm.sin(omega * pybamm.t)

        # create PyBaMM model object
        model = pybamm.BaseModel()

        # Create state variables for model
        theta = pybamm.Variable("ratio_A", domain="solution")
        i = pybamm.Variable("Current")

        # Effective potential
        Eeff = Eapp - i * Ru

        # Faradaic current
        i_f = pybamm.BoundaryGradient(theta, "left")

        # ODE equations
        model.rhs = {
            theta: pybamm.div(pybamm.grad(theta)),
            i: 1 / (Cdl * Ru) * (-i_f + Cdl * Eapp.diff(pybamm.t) - i),
        }

        # algebraic equations (none)
        model.algebraic = {}

        # Butler-volmer boundary condition at electrode
        theta_at_electrode = pybamm.BoundaryValue(theta, "left")
        butler_volmer = k0 * (theta_at_electrode * pybamm.exp(-alpha *
                                                              (Eeff - E0)) -
                              (1 - theta_at_electrode) * pybamm.exp(
                                  (1 - alpha) * (Eeff - E0)))

        # Boundary and initial conditions
        model.boundary_conditions = {
            theta: {
                "right": (pybamm.Scalar(1), "Dirichlet"),
                "left": (butler_volmer, "Neumann"),
            }
        }

        model.initial_conditions = {
            theta: pybamm.Scalar(1),
            i: Cdl * (-1.0 + deltaE * omega),
        }

        # set spatial variables and solution domain geometry
        x = pybamm.SpatialVariable('x', domain="solution")
        default_geometry = pybamm.Geometry({
            "solution": {
                x: {
                    "min": pybamm.Scalar(0),
                    "max": pybamm.Scalar(max_x)
                }
            }
        })

        default_var_pts = {x: n}

        # Using Finite Volume discretisation on an expanding 1D grid for solution
        default_submesh_types = {
            "solution":
            pybamm.MeshGenerator(pybamm.Exponential1DSubMesh, {'side': 'left'})
        }
        default_spatial_methods = {"solution": pybamm.FiniteVolume()}

        # model variables
        model.variables = {
            "Current [non-dim]": i,
        }

        #--------------------------------

        # Set model parameters
        param.process_model(model)
        geometry = default_geometry
        param.process_geometry(geometry)

        # Create mesh and discretise model
        mesh = pybamm.Mesh(geometry, default_submesh_types, default_var_pts)
        disc = pybamm.Discretisation(mesh, default_spatial_methods)
        disc.process_model(model)

        # Create solver
        solver = pybamm.CasadiSolver(
            mode="fast",
            rtol=1e-9,
            atol=1e-9,
            extra_options_setup={'print_stats': False})
        #model.convert_to_format = 'jax'
        #solver = pybamm.JaxSolver(method='BDF')
        #model.convert_to_format = 'python'
        #solver = pybamm.ScipySolver(method='BDF')

        # Store discretised model and solver
        self._model = model
        self._solver = solver
        self._fast_solver = None
        self._omega_d = param["Voltage frequency [rad s-1]"]

        self._I_0 = param.process_symbol(I_0).evaluate()
        self._T_0 = param.process_symbol(T_0).evaluate()
        self._E_0 = param.process_symbol(E_0).evaluate()
        self._L_0 = param.process_symbol(L_0).evaluate()
        self._S = param.process_symbol(S).evaluate()
        self._D = param.process_symbol(D).evaluate()
        self._default_var_points = default_var_pts