Exemplo n.º 1
0
    def test_convert_array_symbols(self):
        # Arrays
        a = np.array([1, 2, 3, 4, 5])
        pybamm_a = pybamm.Array(a)
        self.assert_casadi_equal(pybamm_a.to_casadi(), casadi.MX(a))

        casadi_t = casadi.MX.sym("t")
        casadi_y = casadi.MX.sym("y", 10)
        casadi_y_dot = casadi.MX.sym("y_dot", 10)

        pybamm_t = pybamm.Time()
        pybamm_y = pybamm.StateVector(slice(0, 10))
        pybamm_y_dot = pybamm.StateVectorDot(slice(0, 10))

        # Time
        self.assertEqual(pybamm_t.to_casadi(casadi_t, casadi_y), casadi_t)

        # State Vector
        self.assert_casadi_equal(pybamm_y.to_casadi(casadi_t, casadi_y),
                                 casadi_y)

        # State Vector Dot
        self.assert_casadi_equal(
            pybamm_y_dot.to_casadi(casadi_t, casadi_y, casadi_y_dot),
            casadi_y_dot)
Exemplo n.º 2
0
 def test_multislice_raises(self):
     y1 = pybamm.StateVector(slice(0, 4), slice(7, 8))
     y_dot1 = pybamm.StateVectorDot(slice(0, 4), slice(7, 8))
     y2 = pybamm.StateVector(slice(4, 7))
     with self.assertRaises(NotImplementedError):
         y1.jac(y1)
     with self.assertRaises(NotImplementedError):
         y2.jac(y1)
     with self.assertRaises(NotImplementedError):
         y_dot1.jac(y1)
Exemplo n.º 3
0
    def test_linear_ydot(self):
        y = pybamm.StateVector(slice(0, 4))
        y_dot = pybamm.StateVectorDot(slice(0, 4))
        u = pybamm.StateVector(slice(0, 2))
        v = pybamm.StateVector(slice(2, 4))
        u_dot = pybamm.StateVectorDot(slice(0, 2))
        v_dot = pybamm.StateVectorDot(slice(2, 4))

        y0 = np.ones(4)
        y_dot0 = np.ones(4)

        func = u_dot
        jacobian = np.array([[1, 0, 0, 0], [0, 1, 0, 0]])
        dfunc_dy = func.jac(y_dot).evaluate(y=y0, y_dot=y_dot0)
        np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())

        func = -v_dot
        jacobian = np.array([[0, 0, -1, 0], [0, 0, 0, -1]])
        dfunc_dy = func.jac(y_dot).evaluate(y=y0, y_dot=y_dot0)
        np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())

        func = u_dot
        jacobian = np.array([[0, 0, 0, 0], [0, 0, 0, 0]])
        dfunc_dy = func.jac(y).evaluate(y=y0, y_dot=y_dot0)
        np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())

        func = -v_dot
        jacobian = np.array([[0, 0, 0, 0], [0, 0, 0, 0]])
        dfunc_dy = func.jac(y).evaluate(y=y0, y_dot=y_dot0)
        np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())

        func = u
        jacobian = np.array([[0, 0, 0, 0], [0, 0, 0, 0]])
        dfunc_dy = func.jac(y_dot).evaluate(y=y0, y_dot=y_dot0)
        np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())

        func = -v
        jacobian = np.array([[0, 0, 0, 0], [0, 0, 0, 0]])
        dfunc_dy = func.jac(y_dot).evaluate(y=y0, y_dot=y_dot0)
        np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())
Exemplo n.º 4
0
    def test_evaluate(self):
        sv = pybamm.StateVectorDot(slice(0, 10))
        y_dot = np.linspace(0, 2, 19)
        np.testing.assert_array_equal(sv.evaluate(y_dot=y_dot),
                                      np.linspace(0, 1, 10)[:, np.newaxis])

        # Try evaluating with a y that is too short
        y_dot2 = np.ones(5)
        with self.assertRaisesRegex(
                ValueError,
                "y_dot is too short, so value with slice is smaller than expected",
        ):
            sv.evaluate(y_dot=y_dot2)
Exemplo n.º 5
0
 def test_errors(self):
     y = pybamm.StateVector(slice(0, 10))
     with self.assertRaisesRegex(
             ValueError, "Must provide a 'y' for converting state vectors"):
         y.to_casadi()
     y_dot = pybamm.StateVectorDot(slice(0, 10))
     with self.assertRaisesRegex(
             ValueError,
             "Must provide a 'y_dot' for converting state vectors"):
         y_dot.to_casadi()
     var = pybamm.Variable("var")
     with self.assertRaisesRegex(TypeError,
                                 "Cannot convert symbol of type"):
         var.to_casadi()
Exemplo n.º 6
0
 def test_name(self):
     sv = pybamm.StateVectorDot(slice(0, 10))
     self.assertEqual(sv.name, "y_dot[0:10]")
Exemplo n.º 7
0
 def test_diff_state_vector_dot(self):
     a = pybamm.StateVectorDot(slice(0, 1))
     b = pybamm.StateVector(slice(1, 2))
     self.assertEqual(a.diff(a).id, pybamm.Scalar(1).id)
     self.assertEqual(a.diff(b).id, pybamm.Scalar(0).id)
Exemplo n.º 8
0
    def test_check_well_posedness_variables(self):
        # Well-posed ODE model
        model = pybamm.BaseModel()
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        c = pybamm.Variable("c", domain=whole_cell)
        d = pybamm.Variable("d", domain=whole_cell)
        model.rhs = {c: 5 * pybamm.div(pybamm.grad(d)) - 1, d: -c}
        model.initial_conditions = {c: 1, d: 2}
        model.boundary_conditions = {
            c: {
                "left": (0, "Dirichlet"),
                "right": (0, "Dirichlet")
            },
            d: {
                "left": (0, "Dirichlet"),
                "right": (0, "Dirichlet")
            },
        }
        model.check_well_posedness()

        # Well-posed DAE model
        e = pybamm.Variable("e", domain=whole_cell)
        model.algebraic = {e: e - c - d}
        model.check_well_posedness()

        # Underdetermined model - not enough differential equations
        model.rhs = {c: 5 * pybamm.div(pybamm.grad(d)) - 1}
        model.algebraic = {e: e - c - d}
        with self.assertRaisesRegex(pybamm.ModelError, "underdetermined"):
            model.check_well_posedness()

        # Underdetermined model - not enough algebraic equations
        model.algebraic = {}
        with self.assertRaisesRegex(pybamm.ModelError, "underdetermined"):
            model.check_well_posedness()

        # Overdetermined model - repeated keys
        model.algebraic = {c: c - d, d: e + d}
        with self.assertRaisesRegex(pybamm.ModelError, "overdetermined"):
            model.check_well_posedness()
        # Overdetermined model - extra keys in algebraic
        model.rhs = {c: 5 * pybamm.div(pybamm.grad(d)) - 1, d: -d}
        model.algebraic = {e: c - d}
        with self.assertRaisesRegex(pybamm.ModelError, "overdetermined"):
            model.check_well_posedness()
        model.rhs = {c: 1, d: -1}
        model.algebraic = {e: c - d}
        with self.assertRaisesRegex(pybamm.ModelError, "overdetermined"):
            model.check_well_posedness()

        # After discretisation, don't check for overdetermined from extra algebraic keys
        model = pybamm.BaseModel()
        model.algebraic = {c: 5 * pybamm.StateVector(slice(0, 15)) - 1}
        # passes with post_discretisation=True
        model.check_well_posedness(post_discretisation=True)
        # fails with post_discretisation=False (default)
        with self.assertRaisesRegex(pybamm.ModelError, "extra algebraic keys"):
            model.check_well_posedness()

        # before discretisation, fail if the algebraic eqn keys don't appear in the eqns
        model = pybamm.BaseModel()
        model.algebraic = {c: d - 2, d: d - c}
        with self.assertRaisesRegex(
                pybamm.ModelError,
                "each variable in the algebraic eqn keys must appear in the eqn",
        ):
            model.check_well_posedness()
        # passes when we switch the equations around
        model.algebraic = {c: d - c, d: d - 2}
        model.check_well_posedness()

        # after discretisation, algebraic equation without a StateVector fails
        model = pybamm.BaseModel()
        model.algebraic = {
            c:
            1,
            d:
            pybamm.StateVector(slice(0, 15)) -
            pybamm.StateVector(slice(15, 30)),
        }
        with self.assertRaisesRegex(
                pybamm.ModelError,
                "each algebraic equation must contain at least one StateVector",
        ):
            model.check_well_posedness(post_discretisation=True)

        # model must be in semi-explicit form
        model = pybamm.BaseModel()
        model.rhs = {c: d.diff(pybamm.t), d: -1}
        model.initial_conditions = {c: 1, d: 1}
        with self.assertRaisesRegex(pybamm.ModelError,
                                    "time derivative of variable found"):
            model.check_well_posedness()

        # model must be in semi-explicit form
        model = pybamm.BaseModel()
        model.algebraic = {c: 2 * d - c, d: c * d.diff(pybamm.t) - d}
        model.initial_conditions = {c: 1, d: 1}
        with self.assertRaisesRegex(pybamm.ModelError,
                                    "time derivative of variable found"):
            model.check_well_posedness()

        # model must be in semi-explicit form
        model = pybamm.BaseModel()
        model.rhs = {c: d.diff(pybamm.t), d: -1}
        model.initial_conditions = {c: 1, d: 1}
        with self.assertRaisesRegex(pybamm.ModelError,
                                    "time derivative of variable found"):
            model.check_well_posedness()

        # model must be in semi-explicit form
        model = pybamm.BaseModel()
        model.algebraic = {
            d: 5 * pybamm.StateVector(slice(0, 15)) - 1,
            c: 5 * pybamm.StateVectorDot(slice(0, 15)) - 1,
        }
        with self.assertRaisesRegex(pybamm.ModelError,
                                    "time derivative of state vector found"):
            model.check_well_posedness(post_discretisation=True)

        # model must be in semi-explicit form
        model = pybamm.BaseModel()
        model.rhs = {c: 5 * pybamm.StateVectorDot(slice(0, 15)) - 1}
        model.initial_conditions = {c: 1}
        with self.assertRaisesRegex(pybamm.ModelError,
                                    "time derivative of state vector found"):
            model.check_well_posedness(post_discretisation=True)
Exemplo n.º 9
0
    def _process_symbol(self, symbol):
        """ See :meth:`Discretisation.process_symbol()`. """

        if symbol.domain != []:
            spatial_method = self.spatial_methods[symbol.domain[0]]
            # If boundary conditions are provided, need to check for BCs on tabs
            if self.bcs:
                key_id = list(self.bcs.keys())[0]
                if any("tab" in side
                       for side in list(self.bcs[key_id].keys())):
                    self.bcs[key_id] = self.check_tab_conditions(
                        symbol, self.bcs[key_id])

        if isinstance(symbol, pybamm.BinaryOperator):
            # Pre-process children
            left, right = symbol.children
            disc_left = self.process_symbol(left)
            disc_right = self.process_symbol(right)
            if symbol.domain == []:
                return symbol._binary_new_copy(disc_left, disc_right)
            else:
                return spatial_method.process_binary_operators(
                    symbol, left, right, disc_left, disc_right)
        elif isinstance(symbol, pybamm.UnaryOperator):
            child = symbol.child
            disc_child = self.process_symbol(child)
            if child.domain != []:
                child_spatial_method = self.spatial_methods[child.domain[0]]

            if isinstance(symbol, pybamm.Gradient):
                return child_spatial_method.gradient(child, disc_child,
                                                     self.bcs)

            elif isinstance(symbol, pybamm.Divergence):
                return child_spatial_method.divergence(child, disc_child,
                                                       self.bcs)

            elif isinstance(symbol, pybamm.Laplacian):
                return child_spatial_method.laplacian(child, disc_child,
                                                      self.bcs)

            elif isinstance(symbol, pybamm.Gradient_Squared):
                return child_spatial_method.gradient_squared(
                    child, disc_child, self.bcs)

            elif isinstance(symbol, pybamm.Mass):
                return child_spatial_method.mass_matrix(child, self.bcs)

            elif isinstance(symbol, pybamm.BoundaryMass):
                return child_spatial_method.boundary_mass_matrix(
                    child, self.bcs)

            elif isinstance(symbol, pybamm.IndefiniteIntegral):
                return child_spatial_method.indefinite_integral(
                    child, disc_child, "forward")
            elif isinstance(symbol, pybamm.BackwardIndefiniteIntegral):
                return child_spatial_method.indefinite_integral(
                    child, disc_child, "backward")

            elif isinstance(symbol, pybamm.Integral):
                out = child_spatial_method.integral(child, disc_child)
                out.copy_domains(symbol)
                return out

            elif isinstance(symbol, pybamm.DefiniteIntegralVector):
                return child_spatial_method.definite_integral_matrix(
                    child.domains, vector_type=symbol.vector_type)

            elif isinstance(symbol, pybamm.BoundaryIntegral):
                return child_spatial_method.boundary_integral(
                    child, disc_child, symbol.region)

            elif isinstance(symbol, pybamm.Broadcast):
                # Broadcast new_child to the domain specified by symbol.domain
                # Different discretisations may broadcast differently
                if symbol.domain == []:
                    symbol = disc_child * pybamm.Vector(np.array([1]))
                else:
                    symbol = spatial_method.broadcast(
                        disc_child,
                        symbol.domain,
                        symbol.auxiliary_domains,
                        symbol.broadcast_type,
                    )
                return symbol

            elif isinstance(symbol, pybamm.DeltaFunction):
                return spatial_method.delta_function(symbol, disc_child)

            elif isinstance(symbol, pybamm.BoundaryOperator):
                # if boundary operator applied on "negative tab" or
                # "positive tab" *and* the mesh is 1D then change side to
                # "left" or "right" as appropriate
                if symbol.side in ["negative tab", "positive tab"]:
                    mesh = self.mesh[symbol.children[0].domain[0]]
                    if isinstance(mesh, pybamm.SubMesh1D):
                        symbol.side = mesh.tabs[symbol.side]
                return child_spatial_method.boundary_value_or_flux(
                    symbol, disc_child, self.bcs)

            else:
                return symbol._unary_new_copy(disc_child)

        elif isinstance(symbol, pybamm.Function):
            disc_children = [
                self.process_symbol(child) for child in symbol.children
            ]
            return symbol._function_new_copy(disc_children)

        elif isinstance(symbol, pybamm.VariableDot):
            return pybamm.StateVectorDot(
                *self.y_slices[symbol.get_variable().id],
                domain=symbol.domain,
                auxiliary_domains=symbol.auxiliary_domains)

        elif isinstance(symbol, pybamm.Variable):
            # Check if variable is a standard variable or an external variable
            if any(symbol.id == var.id
                   for var in self.external_variables.values()):
                # Look up dictionary key based on value
                idx = [x.id for x in self.external_variables.values()
                       ].index(symbol.id)
                name, parent_and_slice = list(
                    self.external_variables.keys())[idx]
                if parent_and_slice is None:
                    # Variable didn't come from a concatenation so we can just create a
                    # normal external variable using the symbol's name
                    return pybamm.ExternalVariable(
                        symbol.name,
                        size=self._get_variable_size(symbol),
                        domain=symbol.domain,
                        auxiliary_domains=symbol.auxiliary_domains,
                    )
                else:
                    # We have to use a special name since the concatenation doesn't have
                    # a very informative name. Needs improving
                    parent, start, end = parent_and_slice
                    ext = pybamm.ExternalVariable(
                        name,
                        size=self._get_variable_size(parent),
                        domain=parent.domain,
                        auxiliary_domains=parent.auxiliary_domains,
                    )
                    out = ext[slice(start, end)]
                    out.domain = symbol.domain
                    return out

            else:
                # add a try except block for a more informative error if a variable
                # can't be found. This should usually be caught earlier by
                # model.check_well_posedness, but won't be if debug_mode is False
                try:
                    y_slices = self.y_slices[symbol.id]
                except KeyError:
                    raise pybamm.ModelError("""
                        No key set for variable '{}'. Make sure it is included in either
                        model.rhs, model.algebraic, or model.external_variables in an
                        unmodified form (e.g. not Broadcasted)
                        """.format(symbol.name))
                return pybamm.StateVector(
                    *y_slices,
                    domain=symbol.domain,
                    auxiliary_domains=symbol.auxiliary_domains)

        elif isinstance(symbol, pybamm.SpatialVariable):
            return spatial_method.spatial_variable(symbol)

        elif isinstance(symbol, pybamm.Concatenation):
            new_children = [
                self.process_symbol(child) for child in symbol.children
            ]
            new_symbol = spatial_method.concatenation(new_children)

            return new_symbol

        elif isinstance(symbol, pybamm.InputParameter):
            # Return a new copy of the input parameter, but set the expected size
            # according to the domain of the input parameter
            expected_size = self._get_variable_size(symbol)
            new_input_parameter = symbol.new_copy()
            new_input_parameter.set_expected_size(expected_size)
            return new_input_parameter

        else:
            # Backup option: return new copy of the object
            try:
                return symbol.new_copy()
            except NotImplementedError:
                raise NotImplementedError(
                    "Cannot discretise symbol of type '{}'".format(
                        type(symbol)))