Пример #1
0
    def test_to_equation(self):
        a = pybamm.Symbol("a", domain="negative particle")
        b = pybamm.Symbol("b", domain="current collector")
        c = pybamm.Symbol("c", domain="test")

        # Test print_name
        pybamm.Floor.print_name = "test"
        self.assertEqual(pybamm.Floor(-2.5).to_equation(), sympy.symbols("test"))

        # Test Negate
        self.assertEqual(pybamm.Negate(4).to_equation(), -4.0)

        # Test AbsoluteValue
        self.assertEqual(pybamm.AbsoluteValue(-4).to_equation(), 4.0)

        # Test Gradient
        self.assertEqual(pybamm.Gradient(a).to_equation(), sympy_Gradient("a"))

        # Test Divergence
        self.assertEqual(
            pybamm.Divergence(pybamm.Gradient(a)).to_equation(),
            sympy_Divergence(sympy_Gradient(a)),
        )

        # Test BoundaryValue
        self.assertEqual(
            pybamm.BoundaryValue(a, "right").to_equation(), sympy.symbols("a^{surf}")
        )
        self.assertEqual(
            pybamm.BoundaryValue(b, "positive tab").to_equation(), sympy.symbols(str(b))
        )
        self.assertEqual(
            pybamm.BoundaryValue(c, "left").to_equation(), sympy.symbols("c^{left}")
        )
    def set_boundary_conditions(self, variables):
        T_av = variables["X-averaged cell temperature"]
        T_amb = variables["Ambient temperature"]

        # Subtract the edge cooling from the tab portion so as to not double count
        # Note: tab cooling is also only applied on the current collector hence
        # the (l_cn / l) and (l_cp / l) prefactors.
        # We also still have edge cooling on the region: x in (0, 1)
        h_tab_n_corrected = ((self.param.l_cn / self.param.l) *
                             (self.param.h_tab_n - self.param.h_edge) /
                             self.param.delta)
        h_tab_p_corrected = ((self.param.l_cp / self.param.l) *
                             (self.param.h_tab_p - self.param.h_edge) /
                             self.param.delta)

        T_av_n = pybamm.BoundaryValue(T_av, "negative tab")
        T_av_p = pybamm.BoundaryValue(T_av, "positive tab")

        self.boundary_conditions = {
            T_av: {
                "negative tab":
                (-h_tab_n_corrected * (T_av_n - T_amb), "Neumann"),
                "positive tab":
                (-h_tab_p_corrected * (T_av_p - T_amb), "Neumann"),
            }
        }
Пример #3
0
    def get_fundamental_variables(self):
        T = pybamm.standard_variables.T
        T_cn = pybamm.BoundaryValue(T, "left")
        T_cp = pybamm.BoundaryValue(T, "right")

        variables = self._get_standard_fundamental_variables(T, T_cn, T_cp)
        return variables
Пример #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 get_fundamental_variables(self):
        T_n = pybamm.standard_variables.T_n
        T_s = pybamm.standard_variables.T_s
        T_p = pybamm.standard_variables.T_p
        T_cn = pybamm.BoundaryValue(T_n, "left")
        T_cp = pybamm.BoundaryValue(T_p, "right")

        T = pybamm.Concatenation(T_n, T_s, T_p)
        T_x_av = self._x_average(T, T_cn, T_cp)
        T_vol_av = self._yz_average(T_x_av)

        variables = self._get_standard_fundamental_variables(
            T_cn, T_n, T_s, T_p, T_cp, T_x_av, T_vol_av)
        return variables
    def set_boundary_conditions(self, variables):
        if self.domain == "Separator":
            return None

        param = self.param

        conductivity, sigma_eff = self._get_conductivities(variables)
        i_boundary_cc = variables["Current collector current density"]
        c_e = variables[self.domain + " electrolyte concentration"]
        delta_phi = variables[self.domain + " electrode surface potential difference"]

        if self.domain == "Negative":
            c_e_flux = pybamm.BoundaryGradient(c_e, "right")
            flux_left = -i_boundary_cc * pybamm.BoundaryValue(1 / sigma_eff, "left")
            flux_right = (
                (i_boundary_cc / pybamm.BoundaryValue(conductivity, "right"))
                - pybamm.BoundaryValue(param.chi(c_e) / c_e, "right") * c_e_flux
                - i_boundary_cc * pybamm.BoundaryValue(1 / sigma_eff, "right")
            )

            lbc = (flux_left, "Neumann")
            rbc = (flux_right, "Neumann")
            lbc_c_e = (pybamm.Scalar(0), "Neumann")
            rbc_c_e = (c_e_flux, "Neumann")

        elif self.domain == "Positive":
            c_e_flux = pybamm.BoundaryGradient(c_e, "left")
            flux_left = (
                (i_boundary_cc / pybamm.BoundaryValue(conductivity, "left"))
                - pybamm.BoundaryValue(param.chi(c_e) / c_e, "left") * c_e_flux
                - i_boundary_cc * pybamm.BoundaryValue(1 / sigma_eff, "left")
            )
            flux_right = -i_boundary_cc * pybamm.BoundaryValue(1 / sigma_eff, "right")

            lbc = (flux_left, "Neumann")
            rbc = (flux_right, "Neumann")
            lbc_c_e = (c_e_flux, "Neumann")
            rbc_c_e = (pybamm.Scalar(0), "Neumann")

        # TODO: check if we still need the boundary conditions for c_e, once we have
        # internal boundary conditions
        self.boundary_conditions = {
            delta_phi: {"left": lbc, "right": rbc},
            c_e: {"left": lbc_c_e, "right": rbc_c_e},
        }

        if self.domain == "Negative":
            phi_e = variables["Electrolyte potential"]
            self.boundary_conditions.update(
                {
                    phi_e: {
                        "left": (pybamm.Scalar(0), "Neumann"),
                        "right": (pybamm.Scalar(0), "Neumann"),
                    }
                }
            )
Пример #7
0
    def test_neg_pos(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")
        disc.set_variable_slices([var])

        extrap_neg = pybamm.BoundaryValue(var, "negative tab")
        extrap_pos = pybamm.BoundaryValue(var, "positive tab")
        extrap_neg_disc = disc.process_symbol(extrap_neg)
        extrap_pos_disc = disc.process_symbol(extrap_pos)
        # check constant returns constant at tab
        constant_y = np.ones(mesh["current collector"][0].npts)[:, np.newaxis]
        np.testing.assert_array_almost_equal(
            extrap_neg_disc.evaluate(None, constant_y), 1)
        np.testing.assert_array_almost_equal(
            extrap_pos_disc.evaluate(None, constant_y), 1)
Пример #8
0
    def test_symbol_new_copy(self):
        a = pybamm.Parameter("a")
        b = pybamm.Parameter("b")
        v_n = pybamm.Variable("v", "negative electrode")
        x_n = pybamm.standard_spatial_vars.x_n
        v_s = pybamm.Variable("v", "separator")
        vec = pybamm.Vector([1, 2, 3, 4, 5])
        mat = pybamm.Matrix([[1, 2], [3, 4]])
        mesh = get_mesh_for_testing()

        for symbol in [
                a + b,
                a - b,
                a * b,
                a / b,
                a**b,
                -a,
                abs(a),
                pybamm.Function(np.sin, a),
                pybamm.FunctionParameter("function", {"a": a}),
                pybamm.grad(v_n),
                pybamm.div(pybamm.grad(v_n)),
                pybamm.upwind(v_n),
                pybamm.IndefiniteIntegral(v_n, x_n),
                pybamm.BackwardIndefiniteIntegral(v_n, x_n),
                pybamm.BoundaryValue(v_n, "right"),
                pybamm.BoundaryGradient(v_n, "right"),
                pybamm.PrimaryBroadcast(a, "domain"),
                pybamm.SecondaryBroadcast(v_n, "current collector"),
                pybamm.FullBroadcast(a, "domain",
                                     {"secondary": "other domain"}),
                pybamm.concatenation(v_n, v_s),
                pybamm.NumpyConcatenation(a, b, v_s),
                pybamm.DomainConcatenation([v_n, v_s], mesh),
                pybamm.Parameter("param"),
                pybamm.InputParameter("param"),
                pybamm.StateVector(slice(0, 56)),
                pybamm.Matrix(np.ones((50, 40))),
                pybamm.SpatialVariable("x", ["negative electrode"]),
                pybamm.t,
                pybamm.Index(vec, 1),
                pybamm.NotConstant(a),
                pybamm.ExternalVariable(
                    "external variable",
                    20,
                    domain="test",
                    auxiliary_domains={"secondary": "test2"},
                ),
                pybamm.minimum(a, b),
                pybamm.maximum(a, b),
                pybamm.SparseStack(mat, mat),
        ]:
            self.assertEqual(symbol.id, symbol.new_copy().id)
Пример #9
0
    def test_symbol_new_copy(self):
        a = pybamm.Scalar(0)
        b = pybamm.Scalar(1)
        v_n = pybamm.Variable("v", "negative electrode")
        x_n = pybamm.standard_spatial_vars.x_n
        v_s = pybamm.Variable("v", "separator")
        vec = pybamm.Vector(np.array([1, 2, 3, 4, 5]))
        mesh = get_mesh_for_testing()

        for symbol in [
                a + b,
                a - b,
                a * b,
                a / b,
                a**b,
                -a,
                abs(a),
                pybamm.Function(np.sin, a),
                pybamm.FunctionParameter("function", {"a": a}),
                pybamm.grad(v_n),
                pybamm.div(pybamm.grad(v_n)),
                pybamm.Integral(a, pybamm.t),
                pybamm.IndefiniteIntegral(v_n, x_n),
                pybamm.BackwardIndefiniteIntegral(v_n, x_n),
                pybamm.BoundaryValue(v_n, "right"),
                pybamm.BoundaryGradient(v_n, "right"),
                pybamm.PrimaryBroadcast(a, "domain"),
                pybamm.SecondaryBroadcast(v_n, "current collector"),
                pybamm.FullBroadcast(a, "domain",
                                     {"secondary": "other domain"}),
                pybamm.Concatenation(v_n, v_s),
                pybamm.NumpyConcatenation(a, b, v_s),
                pybamm.DomainConcatenation([v_n, v_s], mesh),
                pybamm.Parameter("param"),
                pybamm.InputParameter("param"),
                pybamm.StateVector(slice(0, 56)),
                pybamm.Matrix(np.ones((50, 40))),
                pybamm.SpatialVariable("x", ["negative electrode"]),
                pybamm.t,
                pybamm.Index(vec, 1),
        ]:
            self.assertEqual(symbol.id, symbol.new_copy().id)
Пример #10
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
Пример #11
0
    def test_process_symbol_base(self):
        # create discretisation
        mesh = get_mesh_for_testing()
        spatial_methods = {
            "macroscale": pybamm.SpatialMethod(),
            "negative particle": pybamm.SpatialMethod(),
            "positive particle": pybamm.SpatialMethod(),
            "current collector": pybamm.SpatialMethod(),
        }
        disc = pybamm.Discretisation(mesh, spatial_methods)

        # variable
        var = pybamm.Variable("var")
        var_vec = pybamm.Variable("var vec", domain=["negative electrode"])
        disc.y_slices = {var.id: [slice(53)], var_vec.id: [slice(53, 93)]}
        var_disc = disc.process_symbol(var)
        self.assertIsInstance(var_disc, pybamm.StateVector)
        self.assertEqual(var_disc.y_slices[0], disc.y_slices[var.id][0])

        # variable dot
        var_dot = pybamm.VariableDot("var'")
        var_dot_disc = disc.process_symbol(var_dot)
        self.assertIsInstance(var_dot_disc, pybamm.StateVectorDot)
        self.assertEqual(var_dot_disc.y_slices[0], disc.y_slices[var.id][0])

        # scalar
        scal = pybamm.Scalar(5)
        scal_disc = disc.process_symbol(scal)
        self.assertIsInstance(scal_disc, pybamm.Scalar)
        self.assertEqual(scal_disc.value, scal.value)
        # vector
        vec = pybamm.Vector(np.array([1, 2, 3, 4]))
        vec_disc = disc.process_symbol(vec)
        self.assertIsInstance(vec_disc, pybamm.Vector)
        np.testing.assert_array_equal(vec_disc.entries, vec.entries)
        # matrix
        mat = pybamm.Matrix(np.array([[1, 2, 3, 4], [5, 6, 7, 8]]))
        mat_disc = disc.process_symbol(mat)
        self.assertIsInstance(mat_disc, pybamm.Matrix)
        np.testing.assert_array_equal(mat_disc.entries, mat.entries)

        # binary operator
        bin = var + scal
        bin_disc = disc.process_symbol(bin)
        self.assertIsInstance(bin_disc, pybamm.Addition)
        self.assertIsInstance(bin_disc.children[0], pybamm.StateVector)
        self.assertIsInstance(bin_disc.children[1], pybamm.Scalar)

        bin2 = scal + var
        bin2_disc = disc.process_symbol(bin2)
        self.assertIsInstance(bin2_disc, pybamm.Addition)
        self.assertIsInstance(bin2_disc.children[0], pybamm.Scalar)
        self.assertIsInstance(bin2_disc.children[1], pybamm.StateVector)

        # non-spatial unary operator
        un1 = -var
        un1_disc = disc.process_symbol(un1)
        self.assertIsInstance(un1_disc, pybamm.Negate)
        self.assertIsInstance(un1_disc.children[0], pybamm.StateVector)

        un2 = abs(var)
        un2_disc = disc.process_symbol(un2)
        self.assertIsInstance(un2_disc, pybamm.AbsoluteValue)
        self.assertIsInstance(un2_disc.children[0], pybamm.StateVector)

        # function of one variable
        def myfun(x):
            return np.exp(x)

        func = pybamm.Function(myfun, var)
        func_disc = disc.process_symbol(func)
        self.assertIsInstance(func_disc, pybamm.Function)
        self.assertIsInstance(func_disc.children[0], pybamm.StateVector)

        func = pybamm.Function(myfun, scal)
        func_disc = disc.process_symbol(func)
        self.assertIsInstance(func_disc, pybamm.Function)
        self.assertIsInstance(func_disc.children[0], pybamm.Scalar)

        # function of multiple variables
        def myfun(x, y):
            return np.exp(x) * y

        func = pybamm.Function(myfun, var, scal)
        func_disc = disc.process_symbol(func)
        self.assertIsInstance(func_disc, pybamm.Function)
        self.assertIsInstance(func_disc.children[0], pybamm.StateVector)
        self.assertIsInstance(func_disc.children[1], pybamm.Scalar)

        # boundary value
        bv_left = pybamm.BoundaryValue(var_vec, "left")
        bv_left_disc = disc.process_symbol(bv_left)
        self.assertIsInstance(bv_left_disc, pybamm.MatrixMultiplication)
        self.assertIsInstance(bv_left_disc.left, pybamm.Matrix)
        self.assertIsInstance(bv_left_disc.right, pybamm.StateVector)
        bv_right = pybamm.BoundaryValue(var_vec, "left")
        bv_right_disc = disc.process_symbol(bv_right)
        self.assertIsInstance(bv_right_disc, pybamm.MatrixMultiplication)
        self.assertIsInstance(bv_right_disc.left, pybamm.Matrix)
        self.assertIsInstance(bv_right_disc.right, pybamm.StateVector)

        # not implemented
        sym = pybamm.Symbol("sym")
        with self.assertRaises(NotImplementedError):
            disc.process_symbol(sym)
Пример #12
0
    def test_extrapolate_2d_models(self):
        # create discretisation
        mesh = get_p2d_mesh_for_testing()
        method_options = {
            "extrapolation": {
                "order": "linear",
                "use bcs": False
            }
        }
        spatial_methods = {
            "macroscale": pybamm.FiniteVolume(method_options),
            "negative particle": pybamm.FiniteVolume(method_options),
            "positive particle": pybamm.FiniteVolume(method_options),
            "current collector": pybamm.FiniteVolume(method_options),
        }
        disc = pybamm.Discretisation(mesh, spatial_methods)

        # Microscale
        var = pybamm.Variable("var", domain="negative particle")
        extrap_right = pybamm.BoundaryValue(var, "right")
        disc.set_variable_slices([var])
        extrap_right_disc = disc.process_symbol(extrap_right)
        self.assertEqual(extrap_right_disc.domain, [])
        # domain for boundary values must now be explicitly set
        extrap_right.domain = ["negative electrode"]
        disc.set_variable_slices([var])
        extrap_right_disc = disc.process_symbol(extrap_right)
        self.assertEqual(extrap_right_disc.domain, ["negative electrode"])
        # evaluate
        y_macro = mesh["negative electrode"][0].nodes
        y_micro = mesh["negative particle"][0].nodes
        y = np.outer(y_macro, y_micro).reshape(-1, 1)
        # extrapolate to r=1 --> should evaluate to y_macro
        np.testing.assert_array_almost_equal(
            extrap_right_disc.evaluate(y=y)[:, 0], y_macro)

        var = pybamm.Variable("var", domain="positive particle")
        extrap_right = pybamm.BoundaryValue(var, "right")
        disc.set_variable_slices([var])
        extrap_right_disc = disc.process_symbol(extrap_right)
        self.assertEqual(extrap_right_disc.domain, [])
        # domain for boundary values must now be explicitly set
        extrap_right.domain = ["positive electrode"]
        disc.set_variable_slices([var])
        extrap_right_disc = disc.process_symbol(extrap_right)
        self.assertEqual(extrap_right_disc.domain, ["positive electrode"])

        # 2d macroscale
        mesh = get_1p1d_mesh_for_testing()
        disc = pybamm.Discretisation(mesh, spatial_methods)
        var = pybamm.Variable("var", domain="negative electrode")
        extrap_right = pybamm.BoundaryValue(var, "right")
        disc.set_variable_slices([var])
        extrap_right_disc = disc.process_symbol(extrap_right)
        self.assertEqual(extrap_right_disc.domain, [])

        # test extrapolate to "negative tab" gives same as "left" and
        # "positive tab" gives same "right" (see get_mesh_for_testing)
        var = pybamm.Variable("var", domain="current collector")
        disc.set_variable_slices([var])
        submesh = mesh["current collector"]
        constant_y = np.ones_like(submesh[0].nodes[:, np.newaxis])

        extrap_neg = pybamm.BoundaryValue(var, "negative tab")
        extrap_neg_disc = disc.process_symbol(extrap_neg)
        extrap_left = pybamm.BoundaryValue(var, "left")
        extrap_left_disc = disc.process_symbol(extrap_left)
        np.testing.assert_array_equal(
            extrap_neg_disc.evaluate(None, constant_y),
            extrap_left_disc.evaluate(None, constant_y),
        )

        extrap_pos = pybamm.BoundaryValue(var, "positive tab")
        extrap_pos_disc = disc.process_symbol(extrap_pos)
        extrap_right = pybamm.BoundaryValue(var, "right")
        extrap_right_disc = disc.process_symbol(extrap_right)
        np.testing.assert_array_equal(
            extrap_pos_disc.evaluate(None, constant_y),
            extrap_right_disc.evaluate(None, constant_y),
        )
Пример #13
0
    def test_quadratic_extrapolate_left_right(self):
        # create discretisation
        mesh = get_mesh_for_testing()
        method_options = {
            "extrapolation": {
                "order": "quadratic",
                "use bcs": False
            }
        }
        spatial_methods = {
            "macroscale": pybamm.FiniteVolume(method_options),
            "negative particle": pybamm.FiniteVolume(method_options),
            "current collector": pybamm.ZeroDimensionalMethod(method_options),
        }
        disc = pybamm.Discretisation(mesh, spatial_methods)

        whole_cell = ["negative electrode", "separator", "positive electrode"]
        macro_submesh = mesh.combine_submeshes(*whole_cell)
        micro_submesh = mesh["negative particle"]

        # Macroscale
        # create variable
        var = pybamm.Variable("var", domain=whole_cell)
        # boundary value should work with something more complicated than a variable
        extrap_left = pybamm.BoundaryValue(2 * var, "left")
        extrap_right = pybamm.BoundaryValue(4 - var, "right")
        disc.set_variable_slices([var])
        extrap_left_disc = disc.process_symbol(extrap_left)
        extrap_right_disc = disc.process_symbol(extrap_right)

        # check constant extrapolates to constant
        constant_y = np.ones_like(macro_submesh[0].nodes[:, np.newaxis])
        np.testing.assert_array_almost_equal(
            extrap_left_disc.evaluate(None, constant_y), 2.0)
        np.testing.assert_array_almost_equal(
            extrap_right_disc.evaluate(None, constant_y), 3.0)

        # check linear variable extrapolates correctly
        linear_y = macro_submesh[0].nodes
        np.testing.assert_array_almost_equal(
            extrap_left_disc.evaluate(None, linear_y), 0)
        np.testing.assert_array_almost_equal(
            extrap_right_disc.evaluate(None, linear_y), 3)

        # Fluxes
        extrap_flux_left = pybamm.BoundaryGradient(2 * var, "left")
        extrap_flux_right = pybamm.BoundaryGradient(1 - var, "right")
        extrap_flux_left_disc = disc.process_symbol(extrap_flux_left)
        extrap_flux_right_disc = disc.process_symbol(extrap_flux_right)

        # check constant extrapolates to constant
        np.testing.assert_array_almost_equal(
            extrap_flux_left_disc.evaluate(None, constant_y), 0)
        self.assertEqual(extrap_flux_right_disc.evaluate(None, constant_y), 0)

        # check linear variable extrapolates correctly
        np.testing.assert_array_almost_equal(
            extrap_flux_left_disc.evaluate(None, linear_y), 2)
        np.testing.assert_array_almost_equal(
            extrap_flux_right_disc.evaluate(None, linear_y), -1)

        # Microscale
        # create variable
        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)

        # 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)
Пример #14
0
k = k_dim * L_0_dim / D_dim(c_inf_dim)
V_hat = V_hat_dim * c_inf_dim


def D(cc):
    c_dim = c_inf_dim * cc
    return D_dim(c_dim) / D_dim(c_inf_dim)


# variables
x = pybamm.SpatialVariable("x", domain="SEI layer", coord_sys="cartesian")
c = pybamm.Variable("Solvent concentration", domain="SEI layer")
L = pybamm.Variable("SEI thickness")

# 3. State governing equations ---------------------------------------------------------
R = k * pybamm.BoundaryValue(c, "left")  # SEI reaction flux
N = -(1 / L) * D(c) * pybamm.grad(c)  # solvent flux
dcdt = (V_hat * R) * pybamm.inner(x / L, pybamm.grad(c)) - (
    1 / L) * pybamm.div(N)  # solvent concentration governing equation
dLdt = V_hat * R  # SEI thickness governing equation

model.rhs = {c: dcdt, L: dLdt}  # add to model

# 4. State boundary conditions ---------------------------------------------------------
D_left = pybamm.BoundaryValue(
    D(c),
    "left")  # pybamm requires BoundaryValue(D(c)) and not D(BoundaryValue(c))
grad_c_left = L * R / D_left  # left bc
c_right = pybamm.Scalar(1)  # right bc

# add to model
Пример #15
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()
    def __init__(self):
        super().__init__()
        self.name = "Effective resistance in current collector model"
        self.param = pybamm.standard_parameters_lithium_ion

        # Get useful parameters
        param = self.param
        l_cn = param.l_cn
        l_cp = param.l_cp
        l_y = param.l_y
        sigma_cn_dbl_prime = param.sigma_cn_dbl_prime
        sigma_cp_dbl_prime = param.sigma_cp_dbl_prime
        alpha_prime = param.alpha_prime

        # Set model variables
        var = pybamm.standard_spatial_vars

        psi = pybamm.Variable("Current collector potential weighted sum",
                              ["current collector"])
        W = pybamm.Variable(
            "Perturbation to current collector potential difference",
            ["current collector"],
        )
        c_psi = pybamm.Variable("Lagrange multiplier for variable `psi`")
        c_W = pybamm.Variable("Lagrange multiplier for variable `W`")

        self.variables = {
            "Current collector potential weighted sum": psi,
            "Perturbation to current collector potential difference": W,
            "Lagrange multiplier for variable `psi`": c_psi,
            "Lagrange multiplier for variable `W`": c_W,
        }

        # Algebraic equations (enforce zero mean constraint through Lagrange multiplier)
        # 0*LagrangeMultiplier hack otherwise gives KeyError
        self.algebraic = {
            psi:
            pybamm.laplacian(psi) +
            c_psi * pybamm.DefiniteIntegralVector(psi, vector_type="column"),
            W:
            pybamm.laplacian(W) - pybamm.source(1, W) +
            c_W * pybamm.DefiniteIntegralVector(W, vector_type="column"),
            c_psi:
            pybamm.Integral(psi, [var.y, var.z]) + 0 * c_psi,
            c_W:
            pybamm.Integral(W, [var.y, var.z]) + 0 * c_W,
        }

        # Boundary conditons
        psi_neg_tab_bc = l_cn
        psi_pos_tab_bc = -l_cp
        W_neg_tab_bc = l_y / (alpha_prime * sigma_cn_dbl_prime)
        W_pos_tab_bc = l_y / (alpha_prime * sigma_cp_dbl_prime)

        self.boundary_conditions = {
            psi: {
                "negative tab": (psi_neg_tab_bc, "Neumann"),
                "positive tab": (psi_pos_tab_bc, "Neumann"),
            },
            W: {
                "negative tab": (W_neg_tab_bc, "Neumann"),
                "positive tab": (W_pos_tab_bc, "Neumann"),
            },
        }

        # "Initial conditions" provides initial guess for solver
        # TODO: better guess than zero?
        self.initial_conditions = {
            psi: pybamm.Scalar(0),
            W: pybamm.Scalar(0),
            c_psi: pybamm.Scalar(0),
            c_W: pybamm.Scalar(0),
        }

        # Define effective current collector resistance
        psi_neg_tab = pybamm.BoundaryValue(psi, "negative tab")
        psi_pos_tab = pybamm.BoundaryValue(psi, "positive tab")
        W_neg_tab = pybamm.BoundaryValue(W, "negative tab")
        W_pos_tab = pybamm.BoundaryValue(W, "positive tab")

        R_cc = ((alpha_prime / l_y) * (sigma_cn_dbl_prime * l_cn * W_pos_tab +
                                       sigma_cp_dbl_prime * l_cp * W_neg_tab) -
                (psi_pos_tab - psi_neg_tab)) / (sigma_cn_dbl_prime * l_cn +
                                                sigma_cp_dbl_prime * l_cp)

        R_cc_dim = R_cc * param.potential_scale / param.I_typ

        self.variables.update({
            "Current collector potential weighted sum (negative tab)":
            psi_neg_tab,
            "Current collector potential weighted sum (positive tab)":
            psi_pos_tab,
            "Perturbation to c.c. potential difference (negative tab)":
            W_neg_tab,
            "Perturbation to c.c. potential difference (positive tab)":
            W_pos_tab,
            "Effective current collector resistance":
            R_cc,
            "Effective current collector resistance [Ohm]":
            R_cc_dim,
        })
Пример #17
0
    def set_voltage_variables(self):

        ocp_n = self.variables["Negative electrode open circuit potential"]
        ocp_p = self.variables["Positive electrode open circuit potential"]
        ocp_n_av = self.variables[
            "X-averaged negative electrode open circuit potential"]
        ocp_p_av = self.variables[
            "X-averaged positive electrode open circuit potential"]

        ocp_n_dim = self.variables[
            "Negative electrode open circuit potential [V]"]
        ocp_p_dim = self.variables[
            "Positive electrode open circuit potential [V]"]
        ocp_n_av_dim = self.variables[
            "X-averaged negative electrode open circuit potential [V]"]
        ocp_p_av_dim = self.variables[
            "X-averaged positive electrode open circuit potential [V]"]

        ocp_n_left = pybamm.boundary_value(ocp_n, "left")
        ocp_n_left_dim = pybamm.boundary_value(ocp_n_dim, "left")
        ocp_p_right = pybamm.boundary_value(ocp_p, "right")
        ocp_p_right_dim = pybamm.boundary_value(ocp_p_dim, "right")

        ocv_av = ocp_p_av - ocp_n_av
        ocv_av_dim = ocp_p_av_dim - ocp_n_av_dim
        ocv = ocp_p_right - ocp_n_left
        ocv_dim = ocp_p_right_dim - ocp_n_left_dim

        # overpotentials
        eta_r_n_av = self.variables[
            "X-averaged negative electrode reaction overpotential"]
        eta_r_n_av_dim = self.variables[
            "X-averaged negative electrode reaction overpotential [V]"]
        eta_r_p_av = self.variables[
            "X-averaged positive electrode reaction overpotential"]
        eta_r_p_av_dim = self.variables[
            "X-averaged positive electrode reaction overpotential [V]"]

        delta_phi_s_n_av = self.variables[
            "X-averaged negative electrode ohmic losses"]
        delta_phi_s_n_av_dim = self.variables[
            "X-averaged negative electrode ohmic losses [V]"]
        delta_phi_s_p_av = self.variables[
            "X-averaged positive electrode ohmic losses"]
        delta_phi_s_p_av_dim = self.variables[
            "X-averaged positive electrode ohmic losses [V]"]

        delta_phi_s_av = delta_phi_s_p_av - delta_phi_s_n_av
        delta_phi_s_av_dim = delta_phi_s_p_av_dim - delta_phi_s_n_av_dim

        eta_r_av = eta_r_p_av - eta_r_n_av
        eta_r_av_dim = eta_r_p_av_dim - eta_r_n_av_dim

        # terminal voltage (Note: phi_s_cn is zero at the negative tab)
        phi_s_cp = self.variables["Positive current collector potential"]
        phi_s_cp_dim = self.variables[
            "Positive current collector potential [V]"]
        if self.options["dimensionality"] == 0:
            V = phi_s_cp
            V_dim = phi_s_cp_dim
        elif self.options["dimensionality"] in [1, 2]:
            V = pybamm.BoundaryValue(phi_s_cp, "positive tab")
            V_dim = pybamm.BoundaryValue(phi_s_cp_dim, "positive tab")

        # TODO: add current collector losses to the voltage in 3D

        self.variables.update({
            "X-averaged open circuit voltage": ocv_av,
            "Measured open circuit voltage": ocv,
            "X-averaged open circuit voltage [V]": ocv_av_dim,
            "Measured open circuit voltage [V]": ocv_dim,
            "X-averaged reaction overpotential": eta_r_av,
            "X-averaged reaction overpotential [V]": eta_r_av_dim,
            "X-averaged solid phase ohmic losses": delta_phi_s_av,
            "X-averaged solid phase ohmic losses [V]": delta_phi_s_av_dim,
            "Terminal voltage": V,
            "Terminal voltage [V]": V_dim,
        })

        # Battery-wide variables
        eta_e_av_dim = self.variables.get(
            "X-averaged electrolyte ohmic losses [V]", 0)
        eta_c_av_dim = self.variables.get(
            "X-averaged concentration overpotential [V]", 0)
        num_cells = pybamm.Parameter(
            "Number of cells connected in series to make a battery")

        self.variables.update({
            "X-averaged battery open circuit voltage [V]":
            ocv_av_dim * num_cells,
            "Measured battery open circuit voltage [V]":
            ocv_dim * num_cells,
            "X-averaged battery reaction overpotential [V]":
            eta_r_av_dim * num_cells,
            "X-averaged battery solid phase ohmic losses [V]":
            delta_phi_s_av_dim * num_cells,
            "X-averaged battery electrolyte ohmic losses [V]":
            eta_e_av_dim * num_cells,
            "X-averaged battery concentration overpotential [V]":
            eta_c_av_dim * num_cells,
            "Battery voltage [V]":
            V_dim * num_cells,
        })

        # Cut-off voltage
        voltage = self.variables["Terminal voltage"]
        self.events["Minimum voltage"] = voltage - self.param.voltage_low_cut
        self.events["Maximum voltage"] = voltage - self.param.voltage_high_cut
Пример #18
0
    def test_process_symbol(self):
        parameter_values = pybamm.ParameterValues({"a": 4, "b": 2, "c": 3})
        # process parameter
        a = pybamm.Parameter("a")
        processed_a = parameter_values.process_symbol(a)
        self.assertIsInstance(processed_a, pybamm.Scalar)
        self.assertEqual(processed_a.value, 4)

        # process binary operation
        var = pybamm.Variable("var")
        add = a + var
        processed_add = parameter_values.process_symbol(add)
        self.assertIsInstance(processed_add, pybamm.Addition)
        self.assertIsInstance(processed_add.children[0], pybamm.Scalar)
        self.assertIsInstance(processed_add.children[1], pybamm.Variable)
        self.assertEqual(processed_add.children[0].value, 4)

        b = pybamm.Parameter("b")
        add = a + b
        processed_add = parameter_values.process_symbol(add)
        self.assertIsInstance(processed_add, pybamm.Scalar)
        self.assertEqual(processed_add.value, 6)

        scal = pybamm.Scalar(34)
        mul = a * scal
        processed_mul = parameter_values.process_symbol(mul)
        self.assertIsInstance(processed_mul, pybamm.Scalar)
        self.assertEqual(processed_mul.value, 136)

        # process integral
        aa = pybamm.Parameter("a", domain=["negative electrode"])
        x = pybamm.SpatialVariable("x", domain=["negative electrode"])
        integ = pybamm.Integral(aa, x)
        processed_integ = parameter_values.process_symbol(integ)
        self.assertIsInstance(processed_integ, pybamm.Integral)
        self.assertIsInstance(processed_integ.children[0], pybamm.Scalar)
        self.assertEqual(processed_integ.children[0].value, 4)
        self.assertEqual(processed_integ.integration_variable[0].id, x.id)

        # process unary operation
        v = pybamm.Variable("v", domain="test")
        grad = pybamm.Gradient(v)
        processed_grad = parameter_values.process_symbol(grad)
        self.assertIsInstance(processed_grad, pybamm.Gradient)
        self.assertIsInstance(processed_grad.children[0], pybamm.Variable)

        # process delta function
        aa = pybamm.Parameter("a")
        delta_aa = pybamm.DeltaFunction(aa, "left", "some domain")
        processed_delta_aa = parameter_values.process_symbol(delta_aa)
        self.assertIsInstance(processed_delta_aa, pybamm.DeltaFunction)
        self.assertEqual(processed_delta_aa.side, "left")
        processed_a = processed_delta_aa.children[0]
        self.assertIsInstance(processed_a, pybamm.Scalar)
        self.assertEqual(processed_a.value, 4)

        # process boundary operator (test for BoundaryValue)
        aa = pybamm.Parameter("a", domain=["negative electrode"])
        x = pybamm.SpatialVariable("x", domain=["negative electrode"])
        boundary_op = pybamm.BoundaryValue(aa * x, "left")
        processed_boundary_op = parameter_values.process_symbol(boundary_op)
        self.assertIsInstance(processed_boundary_op, pybamm.BoundaryOperator)
        processed_a = processed_boundary_op.children[0].children[0]
        processed_x = processed_boundary_op.children[0].children[1]
        self.assertIsInstance(processed_a, pybamm.Scalar)
        self.assertEqual(processed_a.value, 4)
        self.assertEqual(processed_x.id, x.id)

        # process broadcast
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        broad = pybamm.PrimaryBroadcast(a, whole_cell)
        processed_broad = parameter_values.process_symbol(broad)
        self.assertIsInstance(processed_broad, pybamm.Broadcast)
        self.assertEqual(processed_broad.domain, whole_cell)
        self.assertIsInstance(processed_broad.children[0], pybamm.Scalar)
        self.assertEqual(processed_broad.children[0].evaluate(), 4)

        # process concatenation
        conc = pybamm.concatenation(
            pybamm.Vector(np.ones(10), domain="test"),
            pybamm.Vector(2 * np.ones(15), domain="test 2"),
        )
        processed_conc = parameter_values.process_symbol(conc)
        self.assertIsInstance(processed_conc.children[0], pybamm.Vector)
        self.assertIsInstance(processed_conc.children[1], pybamm.Vector)
        np.testing.assert_array_equal(processed_conc.children[0].entries, 1)
        np.testing.assert_array_equal(processed_conc.children[1].entries, 2)

        # process domain concatenation
        c_e_n = pybamm.Variable("c_e_n", ["negative electrode"])
        c_e_s = pybamm.Variable("c_e_p", ["separator"])
        test_mesh = shared.get_mesh_for_testing()
        dom_con = pybamm.DomainConcatenation([a * c_e_n, b * c_e_s], test_mesh)
        processed_dom_con = parameter_values.process_symbol(dom_con)
        a_proc = processed_dom_con.children[0].children[0]
        b_proc = processed_dom_con.children[1].children[0]
        self.assertIsInstance(a_proc, pybamm.Scalar)
        self.assertIsInstance(b_proc, pybamm.Scalar)
        self.assertEqual(a_proc.value, 4)
        self.assertEqual(b_proc.value, 2)

        # process variable
        c = pybamm.Variable("c")
        processed_c = parameter_values.process_symbol(c)
        self.assertIsInstance(processed_c, pybamm.Variable)
        self.assertEqual(processed_c.name, "c")

        # process scalar
        d = pybamm.Scalar(14)
        processed_d = parameter_values.process_symbol(d)
        self.assertIsInstance(processed_d, pybamm.Scalar)
        self.assertEqual(processed_d.value, 14)

        # process array types
        e = pybamm.Vector(np.ones(4))
        processed_e = parameter_values.process_symbol(e)
        self.assertIsInstance(processed_e, pybamm.Vector)
        np.testing.assert_array_equal(processed_e.evaluate(), np.ones((4, 1)))

        f = pybamm.Matrix(np.ones((5, 6)))
        processed_f = parameter_values.process_symbol(f)
        self.assertIsInstance(processed_f, pybamm.Matrix)
        np.testing.assert_array_equal(processed_f.evaluate(), np.ones((5, 6)))

        # process statevector
        g = pybamm.StateVector(slice(0, 10))
        processed_g = parameter_values.process_symbol(g)
        self.assertIsInstance(processed_g, pybamm.StateVector)
        np.testing.assert_array_equal(
            processed_g.evaluate(y=np.ones(10)), np.ones((10, 1))
        )

        # not implemented
        sym = pybamm.Symbol("sym")
        with self.assertRaises(NotImplementedError):
            parameter_values.process_symbol(sym)

        # not found
        with self.assertRaises(KeyError):
            x = pybamm.Parameter("x")
            parameter_values.process_symbol(x)
Пример #19
0
    def test_indefinite_integral(self):

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

        # input a phi, take grad, then integrate to recover phi approximation
        # (need to test this way as check evaluated on edges using if has grad
        # and no div)
        phi = pybamm.Variable("phi",
                              domain=["negative electrode", "separator"])
        i = pybamm.grad(phi)  # create test current (variable on edges)

        x = pybamm.SpatialVariable("x", ["negative electrode", "separator"])
        int_grad_phi = pybamm.IndefiniteIntegral(i, x)
        disc.set_variable_slices([phi])  # i is not a fundamental variable
        # Set boundary conditions (required for shape but don't matter)
        disc._bcs = {
            phi.id: {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (pybamm.Scalar(0), "Neumann"),
            }
        }
        int_grad_phi_disc = disc.process_symbol(int_grad_phi)
        left_boundary_value = pybamm.BoundaryValue(int_grad_phi, "left")
        left_boundary_value_disc = disc.process_symbol(left_boundary_value)

        combined_submesh = mesh.combine_submeshes("negative electrode",
                                                  "separator")

        # constant case
        phi_exact = np.ones((combined_submesh[0].npts, 1))
        phi_approx = int_grad_phi_disc.evaluate(None, phi_exact)
        phi_approx += 1  # add constant of integration
        np.testing.assert_array_equal(phi_exact, phi_approx)
        self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0)
        # linear case
        phi_exact = combined_submesh[0].nodes[:, np.newaxis]
        phi_approx = int_grad_phi_disc.evaluate(None, phi_exact)
        np.testing.assert_array_almost_equal(phi_exact, phi_approx)
        self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0)

        # sine case
        phi_exact = np.sin(combined_submesh[0].nodes[:, np.newaxis])
        phi_approx = int_grad_phi_disc.evaluate(None, phi_exact)
        np.testing.assert_array_almost_equal(phi_exact, phi_approx)
        self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0)

        # --------------------------------------------------------------------
        # region which doesn't start at zero
        phi = pybamm.Variable("phi",
                              domain=["separator", "positive electrode"])
        i = pybamm.grad(phi)  # create test current (variable on edges)
        x = pybamm.SpatialVariable("x", ["separator", "positive electrode"])
        int_grad_phi = pybamm.IndefiniteIntegral(i, x)
        disc.set_variable_slices([phi])  # i is not a fundamental variable
        disc._bcs = {
            phi.id: {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (pybamm.Scalar(0), "Neumann"),
            }
        }
        int_grad_phi_disc = disc.process_symbol(int_grad_phi)
        left_boundary_value = pybamm.BoundaryValue(int_grad_phi, "left")
        left_boundary_value_disc = disc.process_symbol(left_boundary_value)
        combined_submesh = mesh.combine_submeshes("separator",
                                                  "positive electrode")

        # constant case
        phi_exact = np.ones((combined_submesh[0].npts, 1))
        phi_approx = int_grad_phi_disc.evaluate(None, phi_exact)
        phi_approx += 1  # add constant of integration
        np.testing.assert_array_equal(phi_exact, phi_approx)
        self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0)

        # linear case
        phi_exact = (combined_submesh[0].nodes[:, np.newaxis] -
                     combined_submesh[0].edges[0])
        phi_approx = int_grad_phi_disc.evaluate(None, phi_exact)
        np.testing.assert_array_almost_equal(phi_exact, phi_approx)
        np.testing.assert_array_almost_equal(
            left_boundary_value_disc.evaluate(y=phi_exact), 0)

        # sine case
        phi_exact = np.sin(combined_submesh[0].nodes[:, np.newaxis] -
                           combined_submesh[0].edges[0])
        phi_approx = int_grad_phi_disc.evaluate(None, phi_exact)
        np.testing.assert_array_almost_equal(phi_exact, phi_approx)
        np.testing.assert_array_almost_equal(
            left_boundary_value_disc.evaluate(y=phi_exact), 0)

        # --------------------------------------------------------------------
        # micrsoscale case
        c = pybamm.Variable("c", domain=["negative particle"])
        N = pybamm.grad(c)  # create test current (variable on edges)
        r_n = pybamm.SpatialVariable("r_n", ["negative particle"])
        c_integral = pybamm.IndefiniteIntegral(N, r_n)
        disc.set_variable_slices([c])  # N is not a fundamental variable
        disc._bcs = {
            c.id: {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (pybamm.Scalar(0), "Neumann"),
            }
        }

        c_integral_disc = disc.process_symbol(c_integral)
        left_boundary_value = pybamm.BoundaryValue(c_integral, "left")
        left_boundary_value_disc = disc.process_symbol(left_boundary_value)
        combined_submesh = mesh["negative particle"]

        # constant case
        c_exact = np.ones((combined_submesh[0].npts, 1))
        c_approx = c_integral_disc.evaluate(None, c_exact)
        c_approx += 1  # add constant of integration
        np.testing.assert_array_equal(c_exact, c_approx)
        self.assertEqual(left_boundary_value_disc.evaluate(y=c_exact), 0)

        # linear case
        c_exact = combined_submesh[0].nodes[:, np.newaxis]
        c_approx = c_integral_disc.evaluate(None, c_exact)
        np.testing.assert_array_almost_equal(c_exact, c_approx)
        np.testing.assert_array_almost_equal(
            left_boundary_value_disc.evaluate(y=c_exact), 0)

        # sine case
        c_exact = np.sin(combined_submesh[0].nodes[:, np.newaxis])
        c_approx = c_integral_disc.evaluate(None, c_exact)
        np.testing.assert_array_almost_equal(c_exact, c_approx, decimal=3)
        np.testing.assert_array_almost_equal(
            left_boundary_value_disc.evaluate(y=c_exact), 0)