Example #1
0
    def check_for_time_derivatives(self):
        # Check that no variable time derivatives exist in the rhs equations
        for key, eq in self.rhs.items():
            for node in eq.pre_order():
                if isinstance(node, pybamm.VariableDot):
                    raise pybamm.ModelError(
                        "time derivative of variable found "
                        "({}) in rhs equation {}".format(node, key)
                    )
                if isinstance(node, pybamm.StateVectorDot):
                    raise pybamm.ModelError(
                        "time derivative of state vector found "
                        "({}) in rhs equation {}".format(node, key)
                    )

        # Check that no variable time derivatives exist in the algebraic equations
        for key, eq in self.algebraic.items():
            for node in eq.pre_order():
                if isinstance(node, pybamm.VariableDot):
                    raise pybamm.ModelError(
                        "time derivative of variable found ({}) in algebraic"
                        "equation {}".format(node, key)
                    )
                if isinstance(node, pybamm.StateVectorDot):
                    raise pybamm.ModelError(
                        "time derivative of state vector found ({}) in algebraic"
                        "equation {}".format(node, key)
                    )
Example #2
0
 def check_algebraic_equations(self, post_discretisation):
     """
     Check that the algebraic equations are well-posed.
     Before discretisation, each algebraic equation key must appear in the equation
     After discretisation, there must be at least one StateVector in each algebraic
     equation
     """
     if not post_discretisation:
         # After the model has been defined, each algebraic equation key should
         # appear in that algebraic equation
         # this has been relaxed for concatenations for now
         for var, eqn in self.algebraic.items():
             if not any(x.id == var.id
                        for x in eqn.pre_order()) and not isinstance(
                            var, pybamm.Concatenation):
                 raise pybamm.ModelError(
                     "each variable in the algebraic eqn keys must appear in the eqn"
                 )
     else:
         # variables in keys don't get discretised so they will no longer match
         # with the state vectors in the algebraic equations. Instead, we check
         # that each algebraic equation contains some StateVector
         for eqn in self.algebraic.values():
             if not any(
                     isinstance(x, pybamm.StateVector)
                     for x in eqn.pre_order()):
                 raise pybamm.ModelError(
                     "each algebraic equation must contain at least one StateVector"
                 )
Example #3
0
 def check_initial_conditions(self, model):
     """Check initial conditions are a numpy array"""
     # Individual
     for var, eqn in model.initial_conditions.items():
         assert isinstance(
             eqn.evaluate(t=0, inputs="shape test"), np.ndarray
         ), pybamm.ModelError(
             """
             initial_conditions must be numpy array after discretisation but they are
             {} for variable '{}'.
             """.format(
                 type(eqn.evaluate(t=0, inputs="shape test")), var
             )
         )
     # Concatenated
     assert (
         type(
             model.concatenated_initial_conditions.evaluate(t=0, inputs="shape test")
         )
         is np.ndarray
     ), pybamm.ModelError(
         """
         Concatenated initial_conditions must be numpy array after discretisation but
         they are {}.
         """.format(
             type(model.concatenated_initial_conditions)
         )
     )
Example #4
0
    def check_ics_bcs(self):
        """ Check that the initial and boundary conditions are well-posed. """
        # Initial conditions
        for var in self.rhs.keys():
            if var not in self.initial_conditions.keys():
                raise pybamm.ModelError(
                    """no initial condition given for variable '{}'""".format(
                        var))

        # Boundary conditions
        for var, eqn in {**self.rhs, **self.algebraic}.items():
            if eqn.has_symbol_of_classes(
                (pybamm.Gradient, pybamm.Divergence
                 )) and not eqn.has_symbol_of_classes(pybamm.Integral):
                # I have relaxed this check for now so that the lumped temperature
                # equation doesn't raise errors (this has and average in it)

                # Variable must be in the boundary conditions
                if not any(var.id == x.id
                           for symbol in self.boundary_conditions.keys()
                           for x in symbol.pre_order()):
                    raise pybamm.ModelError("""
                        no boundary condition given for
                        variable '{}' with equation '{}'.
                        """.format(var, eqn))
Example #5
0
 def check_algebraic_equations(self, post_discretisation):
     """
     Check that the algebraic equations are well-posed.
     Before discretisation, each algebraic equation key must appear in the equation
     After discretisation, there must be at least one StateVector in each algebraic
     equation
     """
     vars_in_bcs = set()
     unpacker = pybamm.SymbolUnpacker(pybamm.Variable)
     for side_eqn in self.boundary_conditions.values():
         all_vars = unpacker.unpack_list_of_symbols(
             [eqn for eqn, _ in side_eqn.values()])
         vars_in_bcs.update(all_vars.keys())
     if not post_discretisation:
         # After the model has been defined, each algebraic equation key should
         # appear in that algebraic equation, or in the boundary conditions
         # this has been relaxed for concatenations for now
         for var, eqn in self.algebraic.items():
             if not (any(x.id == var.id
                         for x in eqn.pre_order()) or var.id in vars_in_bcs
                     or isinstance(var, pybamm.Concatenation)):
                 raise pybamm.ModelError(
                     "each variable in the algebraic eqn keys must appear in the eqn"
                 )
     else:
         # variables in keys don't get discretised so they will no longer match
         # with the state vectors in the algebraic equations. Instead, we check
         # that each algebraic equation contains some StateVector
         for eqn in self.algebraic.values():
             if not eqn.has_symbol_of_classes(pybamm.StateVector):
                 raise pybamm.ModelError(
                     "each algebraic equation must contain at least one StateVector"
                 )
Example #6
0
 def check_initial_conditions_rhs(self, model):
     """Check initial conditions and rhs have the same shape"""
     y0 = model.concatenated_initial_conditions
     # Individual
     for var in model.rhs.keys():
         assert (
             model.rhs[var].shape == model.initial_conditions[var].shape
         ), pybamm.ModelError(
             "rhs and initial_conditions must have the same shape after "
             "discretisation but rhs.shape = "
             "{} and initial_conditions.shape = {} for variable '{}'.".
             format(model.rhs[var].shape,
                    model.initial_conditions[var].shape, var))
     # Concatenated
     assert (model.concatenated_rhs.shape[0] +
             model.concatenated_algebraic.shape[0] == y0.shape[0]
             ), pybamm.ModelError("""
         Concatenation of (rhs, algebraic) and initial_conditions must have the
         same shape after discretisation but rhs.shape = {}, algebraic.shape = {},
         and initial_conditions.shape = {}.
         """.format(
                 model.concatenated_rhs.shape,
                 model.concatenated_algebraic.shape,
                 y0.shape,
             ))
Example #7
0
 def check_well_determined(self, post_discretisation):
     """ Check that the model is not under- or over-determined. """
     # Equations (differential and algebraic)
     # Get all the variables from differential and algebraic equations
     vars_in_rhs_keys = set()
     vars_in_algebraic_keys = set()
     vars_in_eqns = set()
     # Get all variables ids from rhs and algebraic keys and equations
     # For equations we look through the whole expression tree.
     # "Variables" can be Concatenations so we also have to look in the whole
     # expression tree
     for var, eqn in self.rhs.items():
         vars_in_rhs_keys.update([
             x.id for x in var.pre_order()
             if isinstance(x, pybamm.Variable)
         ])
         vars_in_eqns.update([
             x.id for x in eqn.pre_order()
             if isinstance(x, pybamm.Variable)
         ])
     for var, eqn in self.algebraic.items():
         vars_in_algebraic_keys.update([
             x.id for x in var.pre_order()
             if isinstance(x, pybamm.Variable)
         ])
         vars_in_eqns.update([
             x.id for x in eqn.pre_order()
             if isinstance(x, pybamm.Variable)
         ])
     # If any keys are repeated between rhs and algebraic then the model is
     # overdetermined
     if not set(vars_in_rhs_keys).isdisjoint(vars_in_algebraic_keys):
         raise pybamm.ModelError("model is overdetermined (repeated keys)")
     # If any algebraic keys don't appear in the eqns then the model is
     # overdetermined (but rhs keys can be absent from the eqns, e.g. dcdt = -1 is
     # fine)
     # Skip this step after discretisation, as any variables in the equations will
     # have been discretised to slices but keys will still be variables
     extra_algebraic_keys = vars_in_algebraic_keys.difference(vars_in_eqns)
     if extra_algebraic_keys and not post_discretisation:
         raise pybamm.ModelError(
             "model is overdetermined (extra algebraic keys)")
     # If any variables in the equations don't appear in the keys then the model is
     # underdetermined
     vars_in_keys = vars_in_rhs_keys.union(vars_in_algebraic_keys)
     extra_variables = vars_in_eqns.difference(vars_in_keys)
     if extra_variables:
         raise pybamm.ModelError(
             "model is underdetermined (too many variables)")
Example #8
0
 def build_coupled_variables(self):
     # Note: pybamm will try to get the coupled variables for the submodels in the
     # order they are set by the user. If this fails for a particular submodel,
     # return to it later and try again. If setting coupled variables fails and
     # there are no more submodels to try, raise an error.
     submodels = list(self.submodels.keys())
     count = 0
     # For this part the FuzzyDict of variables is briefly converted back into a
     # normal dictionary for speed with KeyErrors
     self._variables = dict(self._variables)
     while len(submodels) > 0:
         count += 1
         for submodel_name, submodel in self.submodels.items():
             if submodel_name in submodels:
                 pybamm.logger.debug(
                     "Getting coupled variables for {} submodel ({})".
                     format(submodel_name, self.name))
                 try:
                     self.variables.update(
                         submodel.get_coupled_variables(self.variables))
                     submodels.remove(submodel_name)
                 except KeyError as key:
                     if len(submodels) == 1 or count == 100:
                         # no more submodels to try
                         raise pybamm.ModelError(
                             """Submodel "{}" requires the variable {}, but it cannot be found.
                             Check the selected submodels provide all of the required
                             variables.""".format(submodel_name, key))
                     else:
                         # try setting coupled variables on next loop through
                         pybamm.logger.debug(
                             "Can't find {}, trying other submodels first".
                             format(key))
     # Convert variables back into FuzzyDict
     self._variables = pybamm.FuzzyDict(self._variables)
Example #9
0
 def __init__(self, name, child, side):
     # side can only be "negative tab" or "positive tab" if domain is
     # "current collector"
     if side in ["negative tab", "positive tab"]:
         if child.domain[0] != "current collector":
             raise pybamm.ModelError(
                 """Can only take boundary value on the tabs in the domain
             'current collector', but {} has domain {}""".format(
                     child, child.domain[0]
                 )
             )
     self.side = side
     # boundary value of a child takes the domain from auxiliary domain of the child
     if child.auxiliary_domains != {}:
         domain = child.auxiliary_domains["secondary"]
     # if child has no auxiliary domain, integral removes domain
     else:
         domain = []
     # tertiary auxiliary domain shift down to secondary
     try:
         auxiliary_domains = {"secondary": child.auxiliary_domains["tertiary"]}
     except KeyError:
         auxiliary_domains = {}
     super().__init__(
         name, child, domain=domain, auxiliary_domains=auxiliary_domains
     )
Example #10
0
    def check_variables(self):
        # Create list of all Variable nodes that appear in the model's list of variables
        unpacker = pybamm.SymbolUnpacker(pybamm.Variable)
        all_vars = unpacker.unpack_list_of_symbols(self.variables.values())

        var_ids_in_keys = set()

        model_and_external_variables = (list(self.rhs.keys()) +
                                        list(self.algebraic.keys()) +
                                        self.external_variables)

        for var in model_and_external_variables:
            if isinstance(var, pybamm.Variable):
                var_ids_in_keys.add(var.id)
            # Key can be a concatenation
            elif isinstance(var, pybamm.Concatenation):
                var_ids_in_keys.update([child.id for child in var.children])

        for var_id, var in all_vars.items():
            if var_id not in var_ids_in_keys:
                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(var))
Example #11
0
    def check_variables(self, model):
        """
        Check variables in variable list against rhs
        Be lenient with size check if the variable in model.variables is broadcasted, or
        a concatenation, or an outer product
        (if broadcasted, variable is a multiplication with a vector of ones)
        """
        for rhs_var in model.rhs.keys():
            if rhs_var.name in model.variables.keys():
                var = model.variables[rhs_var.name]

                different_shapes = not np.array_equal(model.rhs[rhs_var].shape,
                                                      var.shape)

                not_concatenation = not isinstance(var, pybamm.Concatenation)
                not_outer = not isinstance(var, pybamm.Outer)

                not_mult_by_one_vec = not (
                    isinstance(var, pybamm.Multiplication)
                    and isinstance(var.right, pybamm.Vector)
                    and np.all(var.right.entries == 1))

                if (different_shapes and not_concatenation and not_outer
                        and not_mult_by_one_vec):
                    raise pybamm.ModelError("""
                    variable and its eqn must have the same shape after discretisation
                    but variable.shape = {} and rhs.shape = {} for variable '{}'.
                    """.format(var.shape, model.rhs[rhs_var].shape, var))
Example #12
0
 def diff(self, variable):
     if variable.id == self.id:
         return pybamm.Scalar(1)
     elif variable.id == pybamm.t.id:
         raise pybamm.ModelError("cannot take second time derivative of a Variable")
     else:
         return pybamm.Scalar(0)
Example #13
0
    def check_variables(self, model):
        """
        Check variables in variable list against rhs
        Be lenient with size check if the variable in model.variables is broadcasted, or
        a concatenation
        (if broadcasted, variable is a multiplication with a vector of ones)
        """
        for rhs_var in model.rhs.keys():
            if rhs_var.name in model.variables.keys():
                var = model.variables[rhs_var.name]

                different_shapes = not np.array_equal(model.rhs[rhs_var].shape,
                                                      var.shape)

                not_concatenation = not isinstance(var, pybamm.Concatenation)

                not_mult_by_one_vec = not (isinstance(
                    var,
                    (pybamm.Multiplication, pybamm.MatrixMultiplication)) and
                                           (pybamm.is_matrix_one(var.left) or
                                            pybamm.is_matrix_one(var.right)))

                if different_shapes and not_concatenation and not_mult_by_one_vec:
                    raise pybamm.ModelError(
                        "variable and its eqn must have the same shape after "
                        "discretisation but variable.shape = "
                        "{} and rhs.shape = {} for variable '{}'. ".format(
                            var.shape, model.rhs[rhs_var].shape, var))
Example #14
0
    def check_variables(self):
        # Create list of all Variable nodes that appear in the model's list of variables
        all_vars = {}
        for eqn in self.variables.values():
            # Add all variables in the equation to the list of variables
            all_vars.update({
                x.id: x
                for x in eqn.pre_order() if isinstance(x, pybamm.Variable)
            })
        var_ids_in_keys = set()

        model_and_external_variables = (list(self.rhs.keys()) +
                                        list(self.algebraic.keys()) +
                                        self.external_variables)

        for var in model_and_external_variables:
            if isinstance(var, pybamm.Variable):
                var_ids_in_keys.add(var.id)
            # Key can be a concatenation
            elif isinstance(var, pybamm.Concatenation):
                var_ids_in_keys.update([child.id for child in var.children])

        for var_id, var in all_vars.items():
            if var_id not in var_ids_in_keys:
                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(var))
Example #15
0
    def __init__(self,
                 filename,
                 units="[]",
                 current_scale=pybamm.electrical_parameters.I_typ):
        self.parameters = {"Current [A]": current_scale}
        self.parameters_eval = {"Current [A]": current_scale}

        # Load data from csv
        if filename:
            pybamm_path = pybamm.root_dir()
            data = pd.read_csv(
                os.path.join(pybamm_path, "input", "drive_cycles", filename),
                comment="#",
                skip_blank_lines=True,
            ).to_dict("list")

            self.time = np.array(data["time [s]"])
            self.units = units
            self.current = np.array(data["current " + units])
            # If voltage data is present, load it into the class
            try:
                self.voltage = np.array(data["voltage [V]"])
            except KeyError:
                self.voltage = None
        else:
            raise pybamm.ModelError("No input file provided for current")
Example #16
0
 def check_ics_bcs(self):
     """Check that the initial and boundary conditions are well-posed."""
     # Initial conditions
     for var in self.rhs.keys():
         if var not in self.initial_conditions.keys():
             raise pybamm.ModelError(
                 """no initial condition given for variable '{}'""".format(
                     var))
Example #17
0
 def check_and_combine_dict(self, dict1, dict2):
     # check that the key ids are distinct
     ids1 = set(x.id for x in dict1.keys())
     ids2 = set(x.id for x in dict2.keys())
     if len(ids1.intersection(ids2)) != 0:
         raise pybamm.ModelError(
             "Submodel incompatible: duplicate variables")
     dict1.update(dict2)
Example #18
0
    def check_tab_conditions(self, symbol, bcs):
        """
        Check any boundary conditions applied on "negative tab", "positive tab"
        and "no tab". For 1D current collector meshes, these conditions are
        converted into boundary conditions on "left" (tab at z=0) or "right"
        (tab at z=l_z) depending on the tab location stored in the mesh. For 2D
        current collector meshes, the boundary conditions can be applied on the
        tabs directly.

        Parameters
        ----------
        symbol : :class:`pybamm.expression_tree.symbol.Symbol`
            The symbol on which the boundary conditions are applied.
        bcs : dict
            The dictionary of boundary conditions (a dict of {side: equation}).

        Returns
        -------
        dict
            The dictionary of boundary conditions, with the keys changed to
            "left" and "right" where necessary.

        """
        # Check symbol domain
        domain = symbol.domain[0]
        mesh = self.mesh[domain][0]

        if domain != "current collector":
            raise pybamm.ModelError(
                """Boundary conditions can only be applied on the tabs in the domain
            'current collector', but {} has domain {}""".format(
                    symbol, domain
                )
            )
        # Replace keys with "left" and "right" as appropriate for 1D meshes
        if isinstance(mesh, pybamm.SubMesh1D):
            # send boundary conditions applied on the tabs to "left" or "right"
            # depending on the tab location stored in the mesh
            for tab in ["negative tab", "positive tab"]:
                if any(tab in side for side in list(bcs.keys())):
                    bcs[mesh.tabs[tab]] = bcs.pop(tab)
            # if there was a tab at either end, then the boundary conditions
            # have now been set on "left" and "right" as required by the spatial
            # method, so there is no need to further modify the bcs dict
            if all(side in list(bcs.keys()) for side in ["left", "right"]):
                pass
            # if both tabs are located at z=0 then the "right" boundary condition
            # (at z=1) is the condition for "no tab"
            elif "left" in list(bcs.keys()):
                bcs["right"] = bcs.pop("no tab")
            # else if both tabs are located at z=1, the "left" boundary condition
            # (at z=0) is the condition for "no tab"
            else:
                bcs["left"] = bcs.pop("no tab")

        return bcs
Example #19
0
    def check_initial_conditions(self, model):

        # Check initial conditions are a numpy array
        # Individual
        for var, eqn in model.initial_conditions.items():
            ic_eval = eqn.evaluate(t=0, inputs="shape test")
            if not isinstance(ic_eval, np.ndarray):
                raise pybamm.ModelError(
                    "initial conditions must be numpy array after discretisation but "
                    "they are {} for variable '{}'.".format(
                        type(ic_eval), var))

            # Check that the initial condition is within the bounds
            # Skip this check if there are input parameters in the initial conditions
            bounds = var.bounds
            if not eqn.has_symbol_of_classes(pybamm.InputParameter) and not (
                    all(bounds[0] <= ic_eval) and all(ic_eval <= bounds[1])):
                raise pybamm.ModelError(
                    "initial condition is outside of variable bounds "
                    "{} for variable '{}'.".format(bounds, var))

        # Check initial conditions and model equations have the same shape
        # Individual
        for var in model.rhs.keys():
            if model.rhs[var].shape != model.initial_conditions[var].shape:
                raise pybamm.ModelError(
                    "rhs and initial conditions must have the same shape after "
                    "discretisation but rhs.shape = "
                    "{} and initial_conditions.shape = {} for variable '{}'.".
                    format(model.rhs[var].shape,
                           model.initial_conditions[var].shape, var))
        for var in model.algebraic.keys():
            if model.algebraic[var].shape != model.initial_conditions[
                    var].shape:
                raise pybamm.ModelError(
                    "algebraic and initial conditions must have the same shape after "
                    "discretisation but algebraic.shape = "
                    "{} and initial_conditions.shape = {} for variable '{}'.".
                    format(
                        model.algebraic[var].shape,
                        model.initial_conditions[var].shape,
                        var,
                    ))
Example #20
0
    def _concatenate_in_order(self, var_eqn_dict, check_complete=False):
        """
        Concatenate a dictionary of {variable: equation} using self.y_slices

        The keys/variables in `var_eqn_dict` must be the same as the ids in
        `self.y_slices`.
        The resultant concatenation is ordered according to the ordering of the slice
        values in `self.y_slices`

        Parameters
        ----------
        var_eqn_dict : dict
            Equations ({variable: equation} dict) to dicretise

                Returns
        -------
        var_eqn_dict : dict
            Discretised right-hand side equations

        """
        # Unpack symbols in variables that are concatenations of variables
        unpacked_variables = []
        slices = []
        for symbol in var_eqn_dict.keys():
            if isinstance(symbol, pybamm.Concatenation):
                unpacked_variables.extend([var for var in symbol.children])
                # must append the slice for the whole concatenation, so that equations
                # get sorted correctly
                slices.append(
                    slice(
                        self.y_slices[symbol.children[0].id][0].start,
                        self.y_slices[symbol.children[-1].id][0].stop,
                    ))
            else:
                unpacked_variables.append(symbol)
                slices.append(self.y_slices[symbol.id][0])

        if check_complete:
            # Check keys from the given var_eqn_dict against self.y_slices
            ids = {v.id for v in unpacked_variables}
            if ids != self.y_slices.keys():
                given_variable_names = [v.name for v in var_eqn_dict.keys()]
                raise pybamm.ModelError(
                    "Initial conditions are insufficient. Only "
                    "provided for {} ".format(given_variable_names))

        equations = list(var_eqn_dict.values())

        # sort equations according to slices
        sorted_equations = [eq for _, eq in sorted(zip(slices, equations))]

        return self.concatenate(*sorted_equations)
Example #21
0
    def solve(self, model, t_eval):
        """
        Execute the solver setup and calculate the solution of the model at
        specified times.

        Parameters
        ----------
        model : :class:`pybamm.BaseModel`
            The model whose solution to calculate. Must have attributes rhs and
            initial_conditions
        t_eval : numeric type
            The times at which to compute the solution

        Raises
        ------
        :class:`pybamm.ModelError`
            If an empty model is passed (`model.rhs = {}` and `model.algebraic={}`)

        """
        pybamm.logger.info("Start solving {}".format(model.name))

        # Make sure model isn't empty
        if len(model.rhs) == 0 and len(model.algebraic) == 0:
            raise pybamm.ModelError("Cannot solve empty model")

        # Set up
        timer = pybamm.Timer()
        start_time = timer.time()
        if model.convert_to_format == "casadi" or isinstance(self, pybamm.CasadiSolver):
            self.set_up_casadi(model)
        else:
            self.set_up(model)
        set_up_time = timer.time() - start_time

        # Solve
        solution, solve_time, termination = self.compute_solution(model, t_eval)

        # Assign times
        solution.solve_time = solve_time
        solution.total_time = timer.time() - start_time
        solution.set_up_time = set_up_time

        pybamm.logger.info("Finish solving {} ({})".format(model.name, termination))
        pybamm.logger.info(
            "Set-up time: {}, Solve time: {}, Total time: {}".format(
                timer.format(solution.set_up_time),
                timer.format(solution.solve_time),
                timer.format(solution.total_time),
            )
        )
        return solution
Example #22
0
 def boundary_conditions(self, boundary_conditions):
     # Convert any numbers to a pybamm.Scalar
     for var, bcs in boundary_conditions.items():
         for side, bc in bcs.items():
             if isinstance(bc[0], numbers.Number):
                 # typ is the type of the bc, e.g. "Dirichlet" or "Neumann"
                 eqn, typ = boundary_conditions[var][side]
                 boundary_conditions[var][side] = (pybamm.Scalar(eqn), typ)
             # Check types
             if bc[1] not in ["Dirichlet", "Neumann"]:
                 raise pybamm.ModelError("""
                     boundary condition types must be Dirichlet or Neumann, not '{}'
                     """.format(bc[1]))
     self._boundary_conditions = boundary_conditions
Example #23
0
    def check_no_repeated_keys(self):
        "Check that no equation keys are repeated"
        rhs_alg = {**self.rhs, **self.algebraic}
        rhs_alg_keys = []

        for var in rhs_alg.keys():
            # Check the variable has not already been defined
            if var.id in rhs_alg_keys:
                raise pybamm.ModelError(
                    "Multiple equations specified for variable {!r}".format(
                        var))
            # Update list of variables
            else:
                rhs_alg_keys.append(var.id)
Example #24
0
 def interpolate(self):
     " Creates the interpolant from the loaded data "
     # If data is dimenionless, multiply by a typical current (e.g. data
     # could be C-rate and current_scale the 1C discharge current). Otherwise,
     # just import the current data.
     if self.units == "[]":
         current = self.parameters_eval["Current [A]"] * self.current
     elif self.units == "[A]":
         current = self.current
     else:
         raise pybamm.ModelError(
             "Current data must have units [A] or be dimensionless")
     # Interpolate using Piecewise Cubic Hermite Interpolating Polynomial
     # (does not overshoot non-smooth data)
     self.current_interp = interp.PchipInterpolator(self.time, current)
Example #25
0
 def check_algebraic_equations(self, post_discretisation):
     """
     Check that the algebraic equations are well-posed. After discretisation,
     there must be at least one StateVector in each algebraic equation.
     """
     if post_discretisation:
         # Check that each algebraic equation contains some StateVector
         for eqn in self.algebraic.values():
             if not eqn.has_symbol_of_classes(pybamm.StateVector):
                 raise pybamm.ModelError(
                     "each algebraic equation must contain at least one StateVector"
                 )
     else:
         # We do not perfom any checks before discretisation (most problematic
         # cases should be caught by `check_well_determined`)
         pass
Example #26
0
    def process_boundary_conditions(self, model):
        """Discretise model boundary_conditions, also converting keys to ids

        Parameters
        ----------
        model : :class:`pybamm.BaseModel`
            Model to dicretise. Must have attributes rhs, initial_conditions and
            boundary_conditions (all dicts of {variable: equation})

        Returns
        -------
        dict
            Dictionary of processed boundary conditions

        """

        processed_bcs = {}

        # process and set pybamm.variables first incase required
        # in discrisation of other boundary conditions
        for key, bcs in model.boundary_conditions.items():
            processed_bcs[key.id] = {}

            # check if the boundary condition at the origin for sphere domains is other
            # than no flux
            if key not in model.external_variables:
                for subdomain in key.domain:
                    if self.mesh[subdomain].coord_sys == "spherical polar":
                        if bcs["left"][0].value != 0 or bcs["left"][
                                1] != "Neumann":
                            raise pybamm.ModelError(
                                "Boundary condition at r = 0 must be a homogeneous "
                                "Neumann condition for {} coordinates".format(
                                    self.mesh[subdomain].coord_sys))

            # Handle any boundary conditions applied on the tabs
            if any("tab" in side for side in list(bcs.keys())):
                bcs = self.check_tab_conditions(key, bcs)

            # Process boundary conditions
            for side, bc in bcs.items():
                eqn, typ = bc
                pybamm.logger.debug("Discretise {} ({} bc)".format(key, side))
                processed_eqn = self.process_symbol(eqn)
                processed_bcs[key.id][side] = (processed_eqn, typ)

        return processed_bcs
Example #27
0
    def check_tab_conditions(self, symbol, bcs):
        """
        Check any boundary conditions applied on "negative tab", "positive tab"
        and "no tab". For 1D current collector meshes, these conditions are
        converted into boundary conditions on "left" (tab at z=0) or "right"
        (tab at z=l_z) depending on the tab location stored in the mesh. For 2D
        current collector meshes, the boundary conditions can be applied on the
        tabs directly.

        Parameters
        ----------
        symbol : :class:`pybamm.expression_tree.symbol.Symbol`
            The symbol on which the boundary conditions are applied.
        bcs : dict
            The dictionary of boundary conditions (a dict of {side: equation}).

        Returns
        -------
        dict
            The dictionary of boundary conditions, with the keys changed to
            "left" and "right" where necessary.

        """
        # Check symbol domain
        domain = symbol.domain[0]
        mesh = self.mesh[domain][0]

        if domain != "current collector":
            raise pybamm.ModelError(
                """Boundary conditions can only be applied on the tabs in the domain
            'current collector', but {} has domain {}""".format(
                    symbol, domain))

        # Replace keys with "left" and "right" as appropriate for 1D meshes
        if isinstance(mesh, pybamm.SubMesh1D):
            # replace negative and/or positive tab
            for tab in ["negative tab", "positive tab"]:
                if any(tab in side for side in list(bcs.keys())):
                    bcs[mesh.tabs[tab]] = bcs.pop(tab)
            # replace no tab
            if any("no tab" in side for side in list(bcs.keys())):
                if "left" in list(bcs.keys()):
                    bcs["right"] = bcs.pop("no tab")  # tab at bottom
                else:
                    bcs["left"] = bcs.pop("no tab")  # tab at top

        return bcs
Example #28
0
    def stiffness_matrix(self, symbol, boundary_conditions):
        """
        Laplacian (stiffness) matrix for finite elements in the appropriate domain.

        Parameters
        ----------
        symbol: :class:`pybamm.Symbol`
            The symbol for which we want to calculate the laplacian matrix
        boundary_conditions : dict
            The boundary conditions of the model
            ({symbol.id: {"negative tab": neg. tab bc, "positive tab": pos. tab bc}})

        Returns
        -------
        :class:`pybamm.Matrix`
            The (sparse) finite element stiffness matrix for the domain
        """
        # get primary domain mesh
        domain = symbol.domain[0]
        mesh = self.mesh[domain][0]

        # make form for the stiffness
        @skfem.bilinear_form
        def stiffness_form(u, du, v, dv, w):
            return sum(du * dv)

        # assemble the stifnness matrix
        stiffness = skfem.asm(stiffness_form, mesh.basis)

        # get boundary conditions and type
        try:
            _, neg_bc_type = boundary_conditions[symbol.id]["negative tab"]
            _, pos_bc_type = boundary_conditions[symbol.id]["positive tab"]
        except KeyError:
            raise pybamm.ModelError(
                "No boundary conditions provided for symbol `{}``".format(
                    symbol))

        # adjust matrix for Dirichlet boundary conditions
        if neg_bc_type == "Dirichlet":
            self.bc_apply(stiffness, mesh.negative_tab_dofs)
        if pos_bc_type == "Dirichlet":
            self.bc_apply(stiffness, mesh.positive_tab_dofs)

        return pybamm.Matrix(stiffness)
    def build_model(self):

        # Check if already built
        if self._built:
            raise pybamm.ModelError(
                """Model already built. If you are adding a new submodel, try using
                `model.update` instead.""")

        pybamm.logger.info("Start building {}".format(self.name))

        if self._built_fundamental_and_external is False:
            self.build_fundamental_and_external()

        self.build_coupled_variables()

        self.build_model_equations()

        pybamm.logger.debug("Setting voltage variables ({})".format(self.name))
        self.set_voltage_variables()

        pybamm.logger.debug("Setting SoC variables ({})".format(self.name))
        self.set_soc_variables()

        # Massive hack for consistent delta_phi = phi_s - phi_e with SPMe
        # This needs to be corrected
        if isinstance(self, pybamm.lithium_ion.SPMe):
            for domain in ["Negative", "Positive"]:
                phi_s = self.variables[domain + " electrode potential"]
                phi_e = self.variables[domain + " electrolyte potential"]
                delta_phi = phi_s - phi_e
                s = self.submodels[domain.lower() + " interface"]
                var = s._get_standard_surface_potential_difference_variables(
                    delta_phi)
                self.variables.update(var)

        self._built = True
        pybamm.logger.info("Finish building {}".format(self.name))
Example #30
0
    def process_model(self, unprocessed_model, inplace=True):
        """Assign parameter values to a model.
        Currently inplace, could be changed to return a new model.

        Parameters
        ----------
        unprocessed_model : :class:`pybamm.BaseModel`
            Model to assign parameter values for
        inplace: bool, optional
            If True, replace the parameters in the model in place. Otherwise, return a
            new model with parameter values set. Default is True.

        Raises
        ------
        :class:`pybamm.ModelError`
            If an empty model is passed (`model.rhs = {}` and `model.algebraic = {}` and
            `model.variables = {}`)

        """
        pybamm.logger.info("Start setting parameters for {}".format(
            unprocessed_model.name))

        # set up inplace vs not inplace
        if inplace:
            # any changes to unprocessed_model attributes will change model attributes
            # since they point to the same object
            model = unprocessed_model
        else:
            # create a blank model of the same class
            model = unprocessed_model.new_empty_copy()

        if (len(unprocessed_model.rhs) == 0
                and len(unprocessed_model.algebraic) == 0
                and len(unprocessed_model.variables) == 0):
            raise pybamm.ModelError(
                "Cannot process parameters for empty model")

        new_rhs = {}
        for variable, equation in unprocessed_model.rhs.items():
            pybamm.logger.verbose(
                "Processing parameters for {!r} (rhs)".format(variable))
            new_rhs[variable] = self.process_symbol(equation)
        model.rhs = new_rhs

        new_algebraic = {}
        for variable, equation in unprocessed_model.algebraic.items():
            pybamm.logger.verbose(
                "Processing parameters for {!r} (algebraic)".format(variable))
            new_algebraic[variable] = self.process_symbol(equation)
        model.algebraic = new_algebraic

        new_initial_conditions = {}
        for variable, equation in unprocessed_model.initial_conditions.items():
            pybamm.logger.verbose(
                "Processing parameters for {!r} (initial conditions)".format(
                    variable))
            new_initial_conditions[variable] = self.process_symbol(equation)
        model.initial_conditions = new_initial_conditions

        model.boundary_conditions = self.process_boundary_conditions(
            unprocessed_model)

        new_variables = {}
        for variable, equation in unprocessed_model.variables.items():
            pybamm.logger.verbose(
                "Processing parameters for {!r} (variables)".format(variable))
            new_variables[variable] = self.process_symbol(equation)
        model.variables = new_variables

        new_events = []
        for event in unprocessed_model.events:
            pybamm.logger.verbose(
                "Processing parameters for event '{}''".format(event.name))
            new_events.append(
                pybamm.Event(event.name, self.process_symbol(event.expression),
                             event.event_type))

        for event in self.parameter_events:
            pybamm.logger.verbose(
                "Processing parameters for event '{}''".format(event.name))
            new_events.append(
                pybamm.Event(event.name, self.process_symbol(event.expression),
                             event.event_type))

        model.events = new_events

        # Set external variables
        model.external_variables = [
            self.process_symbol(var)
            for var in unprocessed_model.external_variables
        ]

        # Process timescale
        model.timescale = self.process_symbol(unprocessed_model.timescale)

        # Process length scales
        new_length_scales = {}
        for domain, scale in unprocessed_model.length_scales.items():
            new_length_scales[domain] = self.process_symbol(scale)
        model.length_scales = new_length_scales

        pybamm.logger.info("Finish setting parameters for {}".format(
            model.name))

        return model