def test_processed_variable_ode_pde_solution(self):
        # without space
        model = pybamm.BaseBatteryModel()
        c = pybamm.Variable("conc")
        model.rhs = {c: -c}
        model.initial_conditions = {c: 1}
        model.variables = {"c": c}
        modeltest = tests.StandardModelTest(model)
        modeltest.test_all()
        t_sol, y_sol = modeltest.solution.t, modeltest.solution.y
        processed_vars = pybamm.post_process_variables(model.variables, t_sol,
                                                       y_sol)
        np.testing.assert_array_almost_equal(processed_vars["c"](t_sol),
                                             np.exp(-t_sol))

        # with space
        # set up and solve model
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        model = pybamm.BaseBatteryModel()
        c = pybamm.Variable("conc", domain=whole_cell)
        c_s = pybamm.Variable(
            "particle conc",
            domain="negative particle",
            auxiliary_domains={"secondary": ["negative electrode"]},
        )
        model.rhs = {c: -c, c_s: 1 - c_s}
        model.initial_conditions = {c: 1, c_s: 0.5}
        model.boundary_conditions = {
            c: {
                "left": (0, "Neumann"),
                "right": (0, "Neumann")
            },
            c_s: {
                "left": (0, "Neumann"),
                "right": (0, "Neumann")
            },
        }
        model.variables = {
            "c": c,
            "N": pybamm.grad(c),
            "c_s": c_s,
            "N_s": pybamm.grad(c_s),
        }
        modeltest = tests.StandardModelTest(model)
        modeltest.test_all()
        # set up testing
        t_sol, y_sol = modeltest.solution.t, modeltest.solution.y
        x = pybamm.SpatialVariable("x", domain=whole_cell)
        x_sol = modeltest.disc.process_symbol(x).entries[:, 0]
        processed_vars = pybamm.post_process_variables(model.variables, t_sol,
                                                       y_sol,
                                                       modeltest.disc.mesh)

        # test
        np.testing.assert_array_almost_equal(
            processed_vars["c"](t_sol, x_sol),
            np.ones_like(x_sol)[:, np.newaxis] * np.exp(-t_sol),
        )
示例#2
0
    def test_default_geometry(self):
        var = pybamm.standard_spatial_vars

        model = pybamm.BaseBatteryModel({"dimensionality": 0})
        self.assertEqual(
            model.default_geometry["current collector"][var.z]["position"], 1
        )
        model = pybamm.BaseBatteryModel({"dimensionality": 1})
        self.assertEqual(model.default_geometry["current collector"][var.z]["min"], 0)
        model = pybamm.BaseBatteryModel({"dimensionality": 2})
        self.assertEqual(model.default_geometry["current collector"][var.y]["min"], 0)
示例#3
0
    def test_default_parameters(self):
        # check parameters are read in ok
        model = pybamm.BaseBatteryModel()
        self.assertEqual(
            model.default_parameter_values["Reference temperature [K]"],
            298.15)

        # change path and try again

        cwd = os.getcwd()
        os.chdir("..")
        model = pybamm.BaseBatteryModel()
        self.assertEqual(
            model.default_parameter_values["Reference temperature [K]"],
            298.15)
        os.chdir(cwd)
示例#4
0
def errors(pts, function, method_options, bcs=None):

    domain = "test"
    x = pybamm.SpatialVariable("x", domain=domain)
    geometry = {
        domain: {"primary": {x: {"min": pybamm.Scalar(0), "max": pybamm.Scalar(1)}}}
    }
    submesh_types = {domain: pybamm.MeshGenerator(pybamm.Uniform1DSubMesh)}
    var_pts = {x: pts}
    mesh = pybamm.Mesh(geometry, submesh_types, var_pts)

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

    var = pybamm.Variable("var", domain="test")
    left_extrap = pybamm.BoundaryValue(var, "left")
    right_extrap = pybamm.BoundaryValue(var, "right")

    if bcs:
        model = pybamm.BaseBatteryModel()
        bc_dict = {var: bcs}
        model.boundary_conditions = bc_dict
        disc.bcs = disc.process_boundary_conditions(model)

    submesh = mesh["test"]
    y, l_true, r_true = function(submesh[0].nodes)

    disc.set_variable_slices([var])
    left_extrap_processed = disc.process_symbol(left_extrap)
    right_extrap_processed = disc.process_symbol(right_extrap)

    l_error = np.abs(l_true - left_extrap_processed.evaluate(None, y))
    r_error = np.abs(r_true - right_extrap_processed.evaluate(None, y))

    return l_error, r_error
示例#5
0
 def test_default_spatial_methods(self):
     model = pybamm.BaseBatteryModel({"dimensionality": 0})
     self.assertTrue(
         isinstance(
             model.default_spatial_methods["current collector"],
             pybamm.ZeroDimensionalSpatialMethod,
         ))
     model = pybamm.BaseBatteryModel({"dimensionality": 1})
     self.assertTrue(
         isinstance(model.default_spatial_methods["current collector"],
                    pybamm.FiniteVolume))
     model = pybamm.BaseBatteryModel({"dimensionality": 2})
     self.assertTrue(
         isinstance(
             model.default_spatial_methods["current collector"],
             pybamm.ScikitFiniteElement,
         ))
示例#6
0
    def test_default_var_pts(self):
        var = pybamm.standard_spatial_vars
        var_pts = {
            var.x_n: 20,
            var.x_s: 20,
            var.x_p: 20,
            var.r_n: 30,
            var.r_p: 30,
            var.y: 10,
            var.z: 10,
        }
        model = pybamm.BaseBatteryModel({"dimensionality": 0})
        self.assertDictEqual(var_pts, model.default_var_pts)

        var_pts.update({var.x_n: 10, var.x_s: 10, var.x_p: 10})
        model = pybamm.BaseBatteryModel({"dimensionality": 2})
        self.assertDictEqual(var_pts, model.default_var_pts)
示例#7
0
 def test_default_submesh_types(self):
     model = pybamm.BaseBatteryModel({"dimensionality": 0})
     self.assertTrue(
         issubclass(
             model.default_submesh_types["current collector"].submesh_type,
             pybamm.SubMesh0D,
         ))
     model = pybamm.BaseBatteryModel({"dimensionality": 1})
     self.assertTrue(
         issubclass(
             model.default_submesh_types["current collector"].submesh_type,
             pybamm.Uniform1DSubMesh,
         ))
     model = pybamm.BaseBatteryModel({"dimensionality": 2})
     self.assertTrue(
         issubclass(
             model.default_submesh_types["current collector"].submesh_type,
             pybamm.ScikitUniform2DSubMesh,
         ))
示例#8
0
    def test_default_solver(self):
        model = pybamm.BaseBatteryModel()
        self.assertIsInstance(
            model.default_solver, (pybamm.ScipySolver, pybamm.ScikitsOdeSolver)
        )

        # check that default_solver gives you a new solver, not an internal object
        solver = model.default_solver
        solver = pybamm.BaseModel()
        self.assertIsInstance(
            model.default_solver, (pybamm.ScipySolver, pybamm.ScikitsOdeSolver)
        )
        self.assertIsInstance(solver, pybamm.BaseModel)
    def test_default_solver(self):
        model = pybamm.BaseBatteryModel()
        self.assertIsInstance(model.default_solver, pybamm.CasadiSolver)

        # check that default_solver gives you a new solver, not an internal object
        solver = model.default_solver
        solver = pybamm.BaseModel()
        self.assertIsInstance(model.default_solver, pybamm.CasadiSolver)
        self.assertIsInstance(solver, pybamm.BaseModel)

        # check that adding algebraic variables gives algebraic solver
        a = pybamm.Variable("a")
        model.algebraic = {a: a - 1}
        self.assertIsInstance(model.default_solver,
                              pybamm.CasadiAlgebraicSolver)
示例#10
0
 def test_bad_options(self):
     with self.assertRaisesRegex(pybamm.OptionError, "option"):
         pybamm.BaseBatteryModel({"bad option": "bad option"})
     with self.assertRaisesRegex(pybamm.OptionError, "current collector model"):
         pybamm.BaseBatteryModel({"current collector": "bad current collector"})
     with self.assertRaisesRegex(pybamm.OptionError, "thermal model"):
         pybamm.BaseBatteryModel({"thermal": "bad thermal"})
     with self.assertRaisesRegex(
         pybamm.OptionError, "Dimension of current collectors"
     ):
         pybamm.BaseBatteryModel({"dimensionality": 5})
     with self.assertRaisesRegex(pybamm.OptionError, "surface form"):
         pybamm.BaseBatteryModel({"surface form": "bad surface form"})
     with self.assertRaisesRegex(pybamm.OptionError, "particle model"):
         pybamm.BaseBatteryModel({"particle": "bad particle"})
     with self.assertRaisesRegex(pybamm.OptionError, "option set external"):
         pybamm.BaseBatteryModel({"current collector": "set external potential"})
     with self.assertRaisesRegex(pybamm.OptionError, "operating mode"):
         pybamm.BaseBatteryModel({"operating mode": "bad operating mode"})
示例#11
0
 def test_bad_options(self):
     with self.assertRaisesRegex(pybamm.OptionError, "Option"):
         pybamm.BaseBatteryModel({"bad option": "bad option"})
     with self.assertRaisesRegex(pybamm.OptionError, "current collector model"):
         pybamm.BaseBatteryModel({"current collector": "bad current collector"})
     with self.assertRaisesRegex(pybamm.OptionError, "thermal model"):
         pybamm.BaseBatteryModel({"thermal": "bad thermal"})
     with self.assertRaisesRegex(
         pybamm.OptionError, "Dimension of current collectors"
     ):
         pybamm.BaseBatteryModel({"dimensionality": 5})
     with self.assertRaisesRegex(pybamm.OptionError, "surface form"):
         pybamm.BaseBatteryModel({"surface form": "bad surface form"})
     with self.assertRaisesRegex(pybamm.OptionError, "convection option"):
         pybamm.BaseBatteryModel({"convection": "bad convection"})
     with self.assertRaisesRegex(
         pybamm.OptionError, "cannot have transverse convection in 0D model"
     ):
         pybamm.BaseBatteryModel({"convection": "full transverse"})
     with self.assertRaisesRegex(pybamm.OptionError, "particle model"):
         pybamm.BaseBatteryModel({"particle": "bad particle"})
     with self.assertRaisesRegex(pybamm.OptionError, "operating mode"):
         pybamm.BaseBatteryModel({"operating mode": "bad operating mode"})
示例#12
0
    def test_default_solver(self):
        model = pybamm.BaseBatteryModel()
        self.assertIsInstance(model.default_solver, pybamm.CasadiSolver)

        # check that default_solver gives you a new solver, not an internal object
        solver = model.default_solver
        solver = pybamm.BaseModel()
        self.assertIsInstance(model.default_solver, pybamm.CasadiSolver)
        self.assertIsInstance(solver, pybamm.BaseModel)

        # check that adding algebraic variables gives DAE solver
        a = pybamm.Variable("a")
        model.algebraic = {a: a - 1}
        self.assertIsInstance(
            model.default_solver, (pybamm.IDAKLUSolver, pybamm.CasadiSolver)
        )

        # Check that turning off jacobian gives casadi solver
        model.use_jacobian = False
        self.assertIsInstance(model.default_solver, pybamm.CasadiSolver)
示例#13
0
    def test_options(self):
        with self.assertRaisesRegex(pybamm.OptionError, "Option"):
            pybamm.BaseBatteryModel({"bad option": "bad option"})
        with self.assertRaisesRegex(pybamm.OptionError,
                                    "current collector model"):
            pybamm.BaseBatteryModel(
                {"current collector": "bad current collector"})
        with self.assertRaisesRegex(pybamm.OptionError, "thermal"):
            pybamm.BaseBatteryModel({"thermal": "bad thermal"})
        with self.assertRaisesRegex(pybamm.OptionError, "cell geometry"):
            pybamm.BaseBatteryModel({"cell geometry": "bad geometry"})
        with self.assertRaisesRegex(pybamm.OptionError, "dimensionality"):
            pybamm.BaseBatteryModel({"dimensionality": 5})
        with self.assertRaisesRegex(pybamm.OptionError, "current collector"):
            pybamm.BaseBatteryModel({
                "dimensionality": 1,
                "current collector": "bad option"
            })
        with self.assertRaisesRegex(pybamm.OptionError, "surface form"):
            pybamm.BaseBatteryModel({"surface form": "bad surface form"})
        with self.assertRaisesRegex(pybamm.OptionError, "convection"):
            pybamm.BaseBatteryModel({"convection": "bad convection"})
        with self.assertRaisesRegex(
                pybamm.OptionError,
                "cannot have transverse convection in 0D model"):
            pybamm.BaseBatteryModel({"convection": "full transverse"})
        with self.assertRaisesRegex(pybamm.OptionError, "particle"):
            pybamm.BaseBatteryModel({"particle": "bad particle"})
        with self.assertRaisesRegex(NotImplementedError,
                                    "The 'fast diffusion'"):
            pybamm.BaseBatteryModel({"particle": "fast diffusion"})
        with self.assertRaisesRegex(pybamm.OptionError, "particle shape"):
            pybamm.BaseBatteryModel({"particle shape": "bad particle shape"})
        with self.assertRaisesRegex(pybamm.OptionError, "operating mode"):
            pybamm.BaseBatteryModel({"operating mode": "bad operating mode"})
        with self.assertRaisesRegex(pybamm.OptionError,
                                    "electrolyte conductivity"):
            pybamm.BaseBatteryModel(
                {"electrolyte conductivity": "bad electrolyte conductivity"})

        # SEI options
        with self.assertRaisesRegex(pybamm.OptionError, "SEI"):
            pybamm.BaseBatteryModel({"SEI": "bad sei"})
        with self.assertRaisesRegex(pybamm.OptionError, "SEI film resistance"):
            pybamm.BaseBatteryModel(
                {"SEI film resistance": "bad SEI film resistance"})
        with self.assertRaisesRegex(pybamm.OptionError, "SEI porosity change"):
            pybamm.BaseBatteryModel(
                {"SEI porosity change": "bad SEI porosity change"})
        with self.assertRaisesRegex(
                pybamm.OptionError,
                "SEI porosity change must now be given in string format"):
            pybamm.BaseBatteryModel({"SEI porosity change": True})
        # changing defaults based on other options
        model = pybamm.BaseBatteryModel()
        self.assertEqual(model.options["SEI film resistance"], "none")
        model = pybamm.BaseBatteryModel({"SEI": "constant"})
        self.assertEqual(model.options["SEI film resistance"], "distributed")
        self.assertEqual(
            model.options["total interfacial current density as a state"],
            "true")
        with self.assertRaisesRegex(pybamm.OptionError, "must be 'true'"):
            model = pybamm.BaseBatteryModel({
                "SEI film resistance":
                "distributed",
                "total interfacial current density as a state":
                "false",
            })

        # loss of active material model
        with self.assertRaisesRegex(pybamm.OptionError,
                                    "loss of active material"):
            model = pybamm.BaseBatteryModel(
                {"loss of active material": "bad LAM model"})

        # crack model
        with self.assertRaisesRegex(pybamm.OptionError, "particle cracking"):
            pybamm.BaseBatteryModel(
                {"particle cracking": "bad particle cracking"})

        # plating model
        with self.assertRaisesRegex(pybamm.OptionError, "lithium plating"):
            pybamm.BaseBatteryModel({"lithium plating": "bad plating"})

        with self.assertRaisesRegex(pybamm.OptionError,
                                    "lithium plating porosity change"):
            pybamm.BaseBatteryModel({
                "lithium plating porosity change":
                "bad lithium "
                "plating porosity change"
            })
示例#14
0
    def test_simple_ode_model(self):
        model = pybamm.BaseBatteryModel(name="Simple ODE Model")

        whole_cell = ["negative electrode", "separator", "positive electrode"]
        # Create variables: domain is explicitly empty since these variables are only
        # functions of time
        a = pybamm.Variable("a", domain=[])
        b = pybamm.Variable("b", domain=[])
        c = pybamm.Variable("c", domain=[])

        # Simple ODEs
        model.rhs = {a: pybamm.Scalar(2), b: pybamm.Scalar(0), c: -c}

        # Simple initial conditions
        model.initial_conditions = {
            a: pybamm.Scalar(0),
            b: pybamm.Scalar(1),
            c: pybamm.Scalar(1),
        }
        # no boundary conditions for an ODE model
        # Broadcast some of the variables
        model.variables = {
            "a": a,
            "b broadcasted": pybamm.FullBroadcast(b, whole_cell, "current collector"),
            "c broadcasted": pybamm.FullBroadcast(
                c, ["negative electrode", "separator"], "current collector"
            ),
        }

        # ODEs only (don't use jacobian)
        model.use_jacobian = False

        # Process and solve
        geometry = model.default_geometry
        param = model.default_parameter_values
        param.process_model(model)
        param.process_geometry(geometry)
        mesh = pybamm.Mesh(geometry, model.default_submesh_types, model.default_var_pts)
        disc = pybamm.Discretisation(mesh, model.default_spatial_methods)
        disc.process_model(model)
        solver = model.default_solver
        t_eval = np.linspace(0, 2, 100)
        solution = solver.solve(model, t_eval)
        quick_plot = pybamm.QuickPlot(model, mesh, solution)
        quick_plot.plot(0)

        # update the axis
        new_axis = [0, 0.5, 0, 1]
        quick_plot.axis.update({("a",): new_axis})
        self.assertEqual(quick_plot.axis[("a",)], new_axis)

        # and now reset them
        quick_plot.reset_axis()
        self.assertNotEqual(quick_plot.axis[("a",)], new_axis)

        # check dynamic plot loads
        quick_plot.dynamic_plot(testing=True)

        quick_plot.update(0.01)

        # Test with different output variables
        quick_plot = pybamm.QuickPlot(model, mesh, solution, ["b broadcasted"])
        self.assertEqual(len(quick_plot.axis), 1)
        quick_plot.plot(0)

        quick_plot = pybamm.QuickPlot(
            model,
            mesh,
            solution,
            [["a", "a"], ["b broadcasted", "b broadcasted"], "c broadcasted"],
        )
        self.assertEqual(len(quick_plot.axis), 3)
        quick_plot.plot(0)

        # update the axis
        new_axis = [0, 0.5, 0, 1]
        var_key = ("c broadcasted",)
        quick_plot.axis.update({var_key: new_axis})
        self.assertEqual(quick_plot.axis[var_key], new_axis)

        # and now reset them
        quick_plot.reset_axis()
        self.assertNotEqual(quick_plot.axis[var_key], new_axis)

        # check dynamic plot loads
        quick_plot.dynamic_plot(testing=True)

        quick_plot.update(0.01)

        # Test longer name
        model.variables["Variable with a very long name"] = model.variables["a"]
        quick_plot = pybamm.QuickPlot(model, mesh, solution)
        quick_plot.plot(0)

        # Test errors
        with self.assertRaisesRegex(ValueError, "mismatching variable domains"):
            pybamm.QuickPlot(model, mesh, solution, [["a", "b broadcasted"]])
        model.variables["3D variable"] = disc.process_symbol(
            pybamm.Broadcast(1, ["negative particle"])
        )
        with self.assertRaisesRegex(NotImplementedError, "cannot plot 3D variables"):
            pybamm.QuickPlot(model, mesh, solution, ["3D variable"])
示例#15
0
    def test_simple_ode_model(self):
        model = pybamm.BaseBatteryModel(name="Simple ODE Model")

        whole_cell = ["negative electrode", "separator", "positive electrode"]
        # Create variables: domain is explicitly empty since these variables are only
        # functions of time
        a = pybamm.Variable("a", domain=[])
        b = pybamm.Variable("b", domain=[])
        c = pybamm.Variable("c", domain=[])

        # Simple ODEs
        model.rhs = {a: pybamm.Scalar(2), b: pybamm.Scalar(0), c: -c}

        # Simple initial conditions
        model.initial_conditions = {
            a: pybamm.Scalar(0),
            b: pybamm.Scalar(1),
            c: pybamm.Scalar(1),
        }
        # no boundary conditions for an ODE model
        # Broadcast some of the variables
        model.variables = {
            "a":
            a,
            "b broadcasted":
            pybamm.FullBroadcast(b, whole_cell, "current collector"),
            "c broadcasted":
            pybamm.FullBroadcast(c, ["negative electrode", "separator"],
                                 "current collector"),
            "b broadcasted negative electrode":
            pybamm.PrimaryBroadcast(b, "negative particle"),
            "c broadcasted positive electrode":
            pybamm.PrimaryBroadcast(c, "positive particle"),
            "x [m]":
            pybamm.standard_spatial_vars.x,
            "x":
            pybamm.standard_spatial_vars.x,
            "r_n [m]":
            pybamm.standard_spatial_vars.r_n,
            "r_n":
            pybamm.standard_spatial_vars.r_n,
            "r_p [m]":
            pybamm.standard_spatial_vars.r_p,
            "r_p":
            pybamm.standard_spatial_vars.r_p,
        }

        # ODEs only (don't use jacobian)
        model.use_jacobian = False

        # Process and solve
        geometry = model.default_geometry
        param = model.default_parameter_values
        param.process_model(model)
        param.process_geometry(geometry)
        mesh = pybamm.Mesh(geometry, model.default_submesh_types,
                           model.default_var_pts)
        disc = pybamm.Discretisation(mesh, model.default_spatial_methods)
        disc.process_model(model)
        solver = model.default_solver
        t_eval = np.linspace(0, 2, 100)
        solution = solver.solve(model, t_eval)
        quick_plot = pybamm.QuickPlot(
            solution,
            [
                "a",
                "b broadcasted",
                "c broadcasted",
                "b broadcasted negative electrode",
                "c broadcasted positive electrode",
            ],
        )
        quick_plot.plot(0)

        # update the axis
        new_axis = [0, 0.5, 0, 1]
        quick_plot.axis_limits.update({("a", ): new_axis})
        self.assertEqual(quick_plot.axis_limits[("a", )], new_axis)

        # and now reset them
        quick_plot.reset_axis()
        self.assertNotEqual(quick_plot.axis_limits[("a", )], new_axis)

        # check dynamic plot loads
        quick_plot.dynamic_plot(testing=True)

        quick_plot.slider_update(0.01)

        # Test with different output variables
        quick_plot = pybamm.QuickPlot(solution, ["b broadcasted"])
        self.assertEqual(len(quick_plot.axis_limits), 1)
        quick_plot.plot(0)

        quick_plot = pybamm.QuickPlot(
            solution,
            [
                ["a", "a"],
                ["b broadcasted", "b broadcasted"],
                "c broadcasted",
                "b broadcasted negative electrode",
                "c broadcasted positive electrode",
            ],
        )
        self.assertEqual(len(quick_plot.axis_limits), 5)
        quick_plot.plot(0)

        # update the axis
        new_axis = [0, 0.5, 0, 1]
        var_key = ("c broadcasted", )
        quick_plot.axis_limits.update({var_key: new_axis})
        self.assertEqual(quick_plot.axis_limits[var_key], new_axis)

        # and now reset them
        quick_plot.reset_axis()
        self.assertNotEqual(quick_plot.axis_limits[var_key], new_axis)

        # check dynamic plot loads
        quick_plot.dynamic_plot(testing=True)

        quick_plot.slider_update(0.01)

        # Test longer name
        model.variables["Variable with a very long name"] = model.variables[
            "a"]
        quick_plot = pybamm.QuickPlot(solution,
                                      ["Variable with a very long name"])
        quick_plot.plot(0)

        # Test different inputs
        quick_plot = pybamm.QuickPlot(
            [solution, solution],
            ["a"],
            colors=["r", "g", "b"],
            linestyles=["-", "--"],
            figsize=(1, 2),
            labels=["sol 1", "sol 2"],
        )
        self.assertEqual(quick_plot.colors, ["r", "g", "b"])
        self.assertEqual(quick_plot.linestyles, ["-", "--"])
        self.assertEqual(quick_plot.figsize, (1, 2))
        self.assertEqual(quick_plot.labels, ["sol 1", "sol 2"])

        # Test different time units
        quick_plot = pybamm.QuickPlot(solution, ["a"])
        self.assertEqual(quick_plot.time_scaling_factor, 1)
        quick_plot = pybamm.QuickPlot(solution, ["a"], time_unit="seconds")
        quick_plot.plot(0)
        self.assertEqual(quick_plot.time_scaling_factor, 1)
        np.testing.assert_array_almost_equal(
            quick_plot.plots[("a", )][0][0].get_xdata(), t_eval)
        np.testing.assert_array_almost_equal(
            quick_plot.plots[("a", )][0][0].get_ydata(), 2 * t_eval)
        quick_plot = pybamm.QuickPlot(solution, ["a"], time_unit="minutes")
        quick_plot.plot(0)
        self.assertEqual(quick_plot.time_scaling_factor, 60)
        np.testing.assert_array_almost_equal(
            quick_plot.plots[("a", )][0][0].get_xdata(), t_eval / 60)
        np.testing.assert_array_almost_equal(
            quick_plot.plots[("a", )][0][0].get_ydata(), 2 * t_eval)
        quick_plot = pybamm.QuickPlot(solution, ["a"], time_unit="hours")
        quick_plot.plot(0)
        self.assertEqual(quick_plot.time_scaling_factor, 3600)
        np.testing.assert_array_almost_equal(
            quick_plot.plots[("a", )][0][0].get_xdata(), t_eval / 3600)
        np.testing.assert_array_almost_equal(
            quick_plot.plots[("a", )][0][0].get_ydata(), 2 * t_eval)
        with self.assertRaisesRegex(ValueError, "time unit"):
            pybamm.QuickPlot(solution, ["a"], time_unit="bad unit")
        # long solution defaults to hours instead of seconds
        solution_long = solver.solve(model, np.linspace(0, 1e5))
        quick_plot = pybamm.QuickPlot(solution_long, ["a"])
        self.assertEqual(quick_plot.time_scaling_factor, 3600)

        # Test different spatial units
        quick_plot = pybamm.QuickPlot(solution, ["a"])
        self.assertEqual(quick_plot.spatial_unit, "$\mu m$")
        quick_plot = pybamm.QuickPlot(solution, ["a"], spatial_unit="m")
        self.assertEqual(quick_plot.spatial_unit, "m")
        quick_plot = pybamm.QuickPlot(solution, ["a"], spatial_unit="mm")
        self.assertEqual(quick_plot.spatial_unit, "mm")
        quick_plot = pybamm.QuickPlot(solution, ["a"], spatial_unit="um")
        self.assertEqual(quick_plot.spatial_unit, "$\mu m$")
        with self.assertRaisesRegex(ValueError, "spatial unit"):
            pybamm.QuickPlot(solution, ["a"], spatial_unit="bad unit")

        # Test 2D variables
        model.variables["2D variable"] = disc.process_symbol(
            pybamm.FullBroadcast(1, "negative particle",
                                 {"secondary": "negative electrode"}))
        quick_plot = pybamm.QuickPlot(solution, ["2D variable"])
        quick_plot.plot(0)
        quick_plot.dynamic_plot(testing=True)
        quick_plot.slider_update(0.01)

        with self.assertRaisesRegex(NotImplementedError,
                                    "Cannot plot 2D variables"):
            pybamm.QuickPlot([solution, solution], ["2D variable"])

        # Test different variable limits
        quick_plot = pybamm.QuickPlot(
            solution, ["a", ["c broadcasted", "c broadcasted"]],
            variable_limits="tight")
        self.assertEqual(quick_plot.axis_limits[("a", )][2:], [None, None])
        self.assertEqual(
            quick_plot.axis_limits[("c broadcasted", "c broadcasted")][2:],
            [None, None])
        quick_plot.plot(0)
        quick_plot.slider_update(1)

        quick_plot = pybamm.QuickPlot(solution, ["2D variable"],
                                      variable_limits="tight")
        self.assertEqual(quick_plot.variable_limits[("2D variable", )],
                         (None, None))
        quick_plot.plot(0)
        quick_plot.slider_update(1)

        quick_plot = pybamm.QuickPlot(
            solution,
            ["a", ["c broadcasted", "c broadcasted"]],
            variable_limits={
                "a": [1, 2],
                ("c broadcasted", "c broadcasted"): [3, 4]
            },
        )
        self.assertEqual(quick_plot.axis_limits[("a", )][2:], [1, 2])
        self.assertEqual(
            quick_plot.axis_limits[("c broadcasted", "c broadcasted")][2:],
            [3, 4])
        quick_plot.plot(0)
        quick_plot.slider_update(1)

        quick_plot = pybamm.QuickPlot(solution, ["a", "b broadcasted"],
                                      variable_limits={"a": "tight"})
        self.assertEqual(quick_plot.axis_limits[("a", )][2:], [None, None])
        self.assertNotEqual(quick_plot.axis_limits[("b broadcasted", )][2:],
                            [None, None])
        quick_plot.plot(0)
        quick_plot.slider_update(1)

        with self.assertRaisesRegex(
                TypeError,
                "variable_limits must be 'fixed', 'tight', or a dict"):
            pybamm.QuickPlot(solution, ["a", "b broadcasted"],
                             variable_limits="bad variable limits")

        # Test errors
        with self.assertRaisesRegex(ValueError,
                                    "Mismatching variable domains"):
            pybamm.QuickPlot(solution, [["a", "b broadcasted"]])
        with self.assertRaisesRegex(ValueError, "labels"):
            pybamm.QuickPlot([solution, solution], ["a"],
                             labels=["sol 1", "sol 2", "sol 3"])

        # Remove 'x [m]' from the variables and make sure a key error is raise
        del solution.model.variables["x [m]"]
        with self.assertRaisesRegex(
                KeyError, "Can't find spatial scale for 'negative electrode'"):
            pybamm.QuickPlot(solution, ["b broadcasted"])

        # No variable can be NaN
        model.variables["NaN variable"] = disc.process_symbol(
            pybamm.Scalar(np.nan))
        with self.assertRaisesRegex(
                ValueError, "All-NaN variable 'NaN variable' provided"):
            pybamm.QuickPlot(solution, ["NaN variable"])
    def test_options(self):
        with self.assertRaisesRegex(pybamm.OptionError, "Option"):
            pybamm.BaseBatteryModel({"bad option": "bad option"})
        with self.assertRaisesRegex(pybamm.OptionError,
                                    "current collector model"):
            pybamm.BaseBatteryModel(
                {"current collector": "bad current collector"})
        with self.assertRaisesRegex(pybamm.OptionError, "thermal model"):
            pybamm.BaseBatteryModel({"thermal": "bad thermal"})
        with self.assertRaisesRegex(pybamm.OptionError, "Unknown geometry"):
            pybamm.BaseBatteryModel({"cell geometry": "bad geometry"})
        with self.assertRaisesRegex(pybamm.OptionError,
                                    "Dimension of current collectors"):
            pybamm.BaseBatteryModel({"dimensionality": 5})
        with self.assertRaisesRegex(pybamm.OptionError, "current collector"):
            pybamm.BaseBatteryModel({
                "dimensionality": 1,
                "current collector": "bad option"
            })
        with self.assertRaisesRegex(pybamm.OptionError, "surface form"):
            pybamm.BaseBatteryModel({"surface form": "bad surface form"})
        with self.assertRaisesRegex(pybamm.OptionError, "convection option"):
            pybamm.BaseBatteryModel({"convection": "bad convection"})
        with self.assertRaisesRegex(
                pybamm.OptionError,
                "cannot have transverse convection in 0D model"):
            pybamm.BaseBatteryModel({"convection": "full transverse"})
        with self.assertRaisesRegex(pybamm.OptionError, "particle model"):
            pybamm.BaseBatteryModel({"particle": "bad particle"})
        with self.assertRaisesRegex(NotImplementedError,
                                    "The 'fast diffusion'"):
            pybamm.BaseBatteryModel({"particle": "fast diffusion"})
        with self.assertRaisesRegex(pybamm.OptionError, "particle shape"):
            pybamm.BaseBatteryModel({"particle shape": "bad particle shape"})
        with self.assertRaisesRegex(pybamm.OptionError, "operating mode"):
            pybamm.BaseBatteryModel({"operating mode": "bad operating mode"})

        # SEI options
        with self.assertRaisesRegex(pybamm.OptionError, "sei"):
            pybamm.BaseBatteryModel({"sei": "bad sei"})
        with self.assertRaisesRegex(pybamm.OptionError, "sei film resistance"):
            pybamm.BaseBatteryModel(
                {"sei film resistance": "bad sei film resistance"})
        with self.assertRaisesRegex(pybamm.OptionError, "sei porosity change"):
            pybamm.BaseBatteryModel(
                {"sei porosity change": "bad sei porosity change"})
        # variable defaults
        model = pybamm.BaseBatteryModel()
        self.assertEqual(model.options["sei film resistance"], None)
        model = pybamm.BaseBatteryModel({"sei": "constant"})
        self.assertEqual(model.options["sei film resistance"], "distributed")