Example #1
0
    def __init__(self,
                 name,
                 domain=None,
                 auxiliary_domains=None,
                 coord_sys=None):
        self.coord_sys = coord_sys
        super().__init__(name,
                         domain=domain,
                         auxiliary_domains=auxiliary_domains)
        domain = self.domain

        if domain == []:
            raise ValueError("domain must be provided")

        # Check symbol name vs domain name
        if name == "r" and not (len(domain) == 1 and "particle" in domain[0]):
            raise pybamm.DomainError("domain must be particle if name is 'r'")
        elif name == "r_n" and domain != ["negative particle"]:
            raise pybamm.DomainError(
                "domain must be negative particle if name is 'r_n'")
        elif name == "r_p" and domain != ["positive particle"]:
            raise pybamm.DomainError(
                "domain must be positive particle if name is 'r_p'")
        elif name in ["x", "y", "z", "x_n", "x_s", "x_p"] and any(
            ["particle" in dom for dom in domain]):
            raise pybamm.DomainError(
                "domain cannot be particle if name is '{}'".format(name))
Example #2
0
    def __init__(self, lims, npts, tabs):

        # check that two variables have been passed in
        if len(lims) != 2:
            raise pybamm.GeometryError(
                "lims should contain exactly two variables, not {}".format(len(lims))
            )

        # get spatial variables
        spatial_vars = list(lims.keys())

        # check coordinate system agrees
        if spatial_vars[0].coord_sys == spatial_vars[1].coord_sys:
            coord_sys = spatial_vars[0].coord_sys
        else:
            raise pybamm.DomainError(
                """spatial variables should have the same coordinate system,
                but have coordinate systems {} and {}""".format(
                    spatial_vars[0].coord_sys, spatial_vars[1].coord_sys
                )
            )

        # compute edges
        edges = {}
        for var in spatial_vars:
            if var.name not in ["y", "z"]:
                raise pybamm.DomainError(
                    "spatial variable must be y or z not {}".format(var.name)
                )
            else:
                edges[var.name] = np.linspace(
                    lims[var]["min"], lims[var]["max"], npts[var.id]
                )

        super().__init__(edges, coord_sys, tabs)
Example #3
0
    def combine_submeshes(self, *submeshnames):
        """Combine submeshes into a new submesh, using self.submeshclass
        Raises pybamm.DomainError if submeshes to be combined do not match up (edges are
        not aligned).

        Parameters
        ----------
        submeshnames: list of str
            The names of the submeshes to be combined

        Returns
        -------
        submesh: :class:`self.submeshclass`
            A new submesh with the class defined by self.submeshclass
        """
        # Check that the final edge of each submesh is the same as the first edge of the
        # next submesh
        for i in range(len(submeshnames) - 1):
            for j in range(len(self[submeshnames[i]])):
                if (self[submeshnames[i]][j].edges[-1] !=
                        self[submeshnames[i + 1]][j].edges[0]):
                    raise pybamm.DomainError("submesh edges are not aligned")

            coord_sys = self[submeshnames[i]][0].coord_sys
            coord_sys_r = self[submeshnames[i + 1]][0].coord_sys
            if coord_sys != coord_sys_r:
                raise pybamm.DomainError(
                    "trying to combine two meshes in different coordinate systems"
                )
        submeshes = [None] * len(self[submeshnames[0]])
        # Hack for the special case of current collector
        if submeshnames == ("current collector", ) and isinstance(
                self[submeshnames[0]][0].edges, dict):
            return self[submeshnames[0]]
        for i in range(len(self[submeshnames[0]])):
            combined_submesh_edges = np.concatenate(
                [self[submeshnames[0]][i].edges] + [
                    self[submeshname][i].edges[1:]
                    for submeshname in submeshnames[1:]
                ])
            coord_sys = self[submeshnames[0]][i].coord_sys
            submeshes[i] = pybamm.SubMesh1D(combined_submesh_edges, coord_sys)
            # add in internal boundaries
            submeshes[i].internal_boundaries = [
                self[submeshname][i].edges[0]
                for submeshname in submeshnames[1:]
            ]

        return submeshes
Example #4
0
    def __init__(self, lims, npts, tabs, side="top", stretch=2.3):

        # check side is top
        if side != "top":
            raise pybamm.GeometryError(
                "At present, side can only be 'top', but is set to {}".format(side)
            )

        # check that two variables have been passed in
        if len(lims) != 2:
            raise pybamm.GeometryError(
                "lims should contain exactly two variables, not {}".format(len(lims))
            )

        # get spatial variables
        spatial_vars = list(lims.keys())

        # check coordinate system agrees
        if spatial_vars[0].coord_sys == spatial_vars[1].coord_sys:
            coord_sys = spatial_vars[0].coord_sys
        else:
            raise pybamm.DomainError(
                """spatial variables should have the same coordinate system,
                but have coordinate systems {} and {}""".format(
                    spatial_vars[0].coord_sys, spatial_vars[1].coord_sys
                )
            )

        # compute edges
        edges = {}
        for var in spatial_vars:
            if var.name not in ["y", "z"]:
                raise pybamm.DomainError(
                    "spatial variable must be y or z not {}".format(var.name)
                )
            elif var.name == "y":
                edges[var.name] = np.linspace(
                    lims[var]["min"], lims[var]["max"], npts[var.id]
                )
            elif var.name == "z":
                ii = np.array(range(0, npts[var.id]))
                a = lims[var]["min"]
                b = lims[var]["max"]
                edges[var.name] = (b - a) * (
                    np.exp(-stretch * ii / (npts[var.id] - 1)) - 1
                ) / (np.exp(-stretch) - 1) + a

        super().__init__(edges, coord_sys, tabs)
Example #5
0
    def check_and_convert_equations(self, equations):
        """
        Convert any scalar equations in dict to 'pybamm.Scalar'
        and check that domains are consistent
        """
        # Convert any numbers to a pybamm.Scalar
        for var, eqn in equations.items():
            if isinstance(eqn, numbers.Number):
                equations[var] = pybamm.Scalar(eqn)

        if not all([
                variable.domain == equation.domain or variable.domain == []
                or equation.domain == []
                for variable, equation in equations.items()
        ]):
            raise pybamm.DomainError(
                "variable and equation in '{}' must have the same domain".
                format(self.name))

        # For initial conditions, check that the equation doesn't contain any
        # Variable objects
        # skip this if the dictionary has no "name" attribute (which will be the case
        # after pickling)
        if hasattr(self, "name") and self.name == "initial_conditions":
            for var, eqn in equations.items():
                if eqn.has_symbol_of_classes(pybamm.Variable):
                    unpacker = pybamm.SymbolUnpacker(pybamm.Variable)
                    variable_in_equation = list(
                        unpacker.unpack_symbol(eqn).values())[0]
                    raise TypeError(
                        "Initial conditions cannot contain 'Variable' objects, "
                        "but '{!r}' found in initial conditions for '{}'".
                        format(variable_in_equation, var))

        return equations
Example #6
0
 def get_children_domains(self, children):
     # combine domains from children
     domain = []
     for child in children:
         if not isinstance(child, pybamm.Symbol):
             raise TypeError("{} is not a pybamm symbol".format(child))
         child_domain = child.domain
         if child_domain == []:
             raise pybamm.DomainError(
                 "Cannot concatenate child '{}' with empty domain".format(child)
             )
         if set(domain).isdisjoint(child_domain):
             domain += child_domain
         else:
             raise pybamm.DomainError("domain of children must be disjoint")
     return domain
Example #7
0
def z_average(symbol):
    """convenience function for creating an average in the z-direction

    Parameters
    ----------
    symbol : :class:`pybamm.Symbol`
        The function to be averaged

    Returns
    -------
    :class:`Symbol`
        the new averaged symbol
    """
    # Symbol must have domain [] or ["current collector"]
    if symbol.domain not in [[], ["current collector"]]:
        raise pybamm.DomainError(
            """z-average only implemented in the 'current collector' domain,
            but symbol has domains {}""".format(
                symbol.domain
            )
        )
    # If symbol doesn't have a domain, its average value is itself
    if symbol.domain == []:
        new_symbol = symbol.new_copy()
        new_symbol.parent = None
        return new_symbol
    # If symbol is a Broadcast, its average value is its child
    elif isinstance(symbol, pybamm.Broadcast):
        return symbol.orphans[0]
    # Otherwise, use Integral to calculate average value
    else:
        z = pybamm.standard_spatial_vars.z
        l_z = pybamm.geometric_parameters.l_z
        return Integral(symbol, z) / l_z
Example #8
0
    def __init__(self, lims, npts, side="top", stretch=2.3):

        # check side is top
        if side != "top":
            raise pybamm.GeometryError(
                "At present, side can only be 'top', but is set to {}".format(
                    side))

        spatial_vars, tabs = self.read_lims(lims)
        coord_sys = spatial_vars[0].coord_sys

        # compute edges
        edges = {}
        for var in spatial_vars:
            if var.name not in ["y", "z"]:
                raise pybamm.DomainError(
                    "spatial variable must be y or z not {}".format(var.name))
            elif var.name == "y":
                edges[var.name] = np.linspace(lims[var]["min"],
                                              lims[var]["max"], npts[var.id])
            elif var.name == "z":
                ii = np.array(range(0, npts[var.id]))
                a = lims[var]["min"]
                b = lims[var]["max"]
                edges[var.name] = (b - a) * (np.exp(-stretch * ii /
                                                    (npts[var.id] - 1)) -
                                             1) / (np.exp(-stretch) - 1) + a

        super().__init__(edges, coord_sys, tabs)
Example #9
0
    def auxiliary_domains(self, auxiliary_domains):
        # Turn dictionary into appropriate form
        if auxiliary_domains is None:
            auxiliary_domains = {}
        for level, dom in auxiliary_domains.items():
            if isinstance(dom, str):
                auxiliary_domains[level] = [dom]

        # Check domains don't clash
        if self.domain in auxiliary_domains.values():
            raise pybamm.DomainError("Domain cannot be the same as an auxiliary domain")
        values = [tuple(val) for val in auxiliary_domains.values()]
        if len(set(values)) != len(values):
            raise pybamm.DomainError("All auxiliary domains must be different")

        self._auxiliary_domains = auxiliary_domains
Example #10
0
    def __init__(self, lims, npts, tabs, y_edges=None, z_edges=None):

        # raise error if no edges passed
        if y_edges is None:
            raise pybamm.GeometryError("User mesh requires parameter 'y_edges'")
        if z_edges is None:
            raise pybamm.GeometryError("User mesh requires parameter 'z_edges'")

        # check that two variables have been passed in
        if len(lims) != 2:
            raise pybamm.GeometryError(
                "lims should contain exactly two variables, not {}".format(len(lims))
            )

        # get spatial variables
        spatial_vars = list(lims.keys())

        # check coordinate system agrees
        if spatial_vars[0].coord_sys == spatial_vars[1].coord_sys:
            coord_sys = spatial_vars[0].coord_sys
        else:
            raise pybamm.DomainError(
                """spatial variables should have the same coordinate system,
                but have coordinate systems {} and {}""".format(
                    spatial_vars[0].coord_sys, spatial_vars[1].coord_sys
                )
            )

        # check and store edges
        edges = {"y": y_edges, "z": z_edges}
        for var in spatial_vars:

            # check that npts equals number of user-supplied edges
            if npts[var.id] != len(edges[var.name]):
                raise pybamm.GeometryError(
                    """User-suppled edges has should have length npts but has length {}.
                     Number of points (npts) for variable {} in
                     domain {} is {}.""".format(
                        len(edges[var.name]), var.name, var.domain, npts[var.id]
                    )
                )

            # check end points of edges agree with spatial_lims
            if edges[var.name][0] != lims[var]["min"]:
                raise pybamm.GeometryError(
                    """First entry of edges is {}, but should be equal to {}
                     for variable {} in domain {}.""".format(
                        edges[var.name][0], lims[var]["min"], var.name, var.domain
                    )
                )
            if edges[var.name][-1] != lims[var]["max"]:
                raise pybamm.GeometryError(
                    """Last entry of edges is {}, but should be equal to {}
                    for variable {} in domain {}.""".format(
                        edges[var.name][-1], lims[var]["max"], var.name, var.domain
                    )
                )

        super().__init__(edges, coord_sys=coord_sys, tabs=tabs)
Example #11
0
def x_average(symbol):
    """convenience function for creating an average in the x-direction

    Parameters
    ----------
    symbol : :class:`pybamm.Symbol`
        The function to be averaged

    Returns
    -------
    :class:`Symbol`
        the new averaged symbol
    """
    # If symbol doesn't have a domain, its average value is itself
    if symbol.domain in [[], ["current collector"]]:
        new_symbol = symbol.new_copy()
        new_symbol.parent = None
        return new_symbol
    # If symbol is a Broadcast, its average value is its child
    elif isinstance(symbol, pybamm.Broadcast):
        return symbol.orphans[0]
    # If symbol is a concatenation of Broadcasts, its average value is its child
    elif (
        isinstance(symbol, pybamm.Concatenation)
        and all(isinstance(child, pybamm.Broadcast) for child in symbol.children)
        and symbol.domain == ["negative electrode", "separator", "positive electrode"]
    ):
        a, b, c = [orp.orphans[0] for orp in symbol.orphans]
        if a.id == b.id == c.id:
            return a
        else:
            l_n = pybamm.geometric_parameters.l_n
            l_s = pybamm.geometric_parameters.l_s
            l_p = pybamm.geometric_parameters.l_p
            return (l_n * a + l_s * b + l_p * c) / (l_n + l_s + l_p)
    # Otherwise, use Integral to calculate average value
    else:
        if symbol.domain == ["negative electrode"]:
            x = pybamm.standard_spatial_vars.x_n
            l = pybamm.geometric_parameters.l_n
        elif symbol.domain == ["separator"]:
            x = pybamm.standard_spatial_vars.x_s
            l = pybamm.geometric_parameters.l_s
        elif symbol.domain == ["positive electrode"]:
            x = pybamm.standard_spatial_vars.x_p
            l = pybamm.geometric_parameters.l_p
        elif symbol.domain == ["negative electrode", "separator", "positive electrode"]:
            x = pybamm.standard_spatial_vars.x
            l = pybamm.Scalar(1)
        elif symbol.domain == ["negative particle"]:
            x = pybamm.standard_spatial_vars.x_n
            l = pybamm.geometric_parameters.l_n
        elif symbol.domain == ["positive particle"]:
            x = pybamm.standard_spatial_vars.x_p
            l = pybamm.geometric_parameters.l_p
        else:
            raise pybamm.DomainError("domain '{}' not recognised".format(symbol.domain))

        return Integral(symbol, x) / l
Example #12
0
 def domain(self, domain):
     if domain in ["Negative", "Separator", "Positive"]:
         self._domain = domain
     elif domain is None:
         pass
     else:
         raise pybamm.DomainError(
             "Domain must be either 'Negative' or 'Positive' not {}".format(
                 domain))
Example #13
0
 def __init__(self, child, side, domain):
     self.side = side
     if domain is None:
         raise pybamm.DomainError("Delta function domain cannot be None")
     if child.domain != []:
         auxiliary_domains = {"secondary": child.domain}
     else:
         auxiliary_domains = {}
     super().__init__("delta_function", child, domain, auxiliary_domains)
Example #14
0
    def __init__(self, lims, npts, tabs):

        # check that two variables have been passed in
        if len(lims) != 2:
            raise pybamm.GeometryError(
                "lims should contain exactly two variables, not {}".format(len(lims))
            )

        # get spatial variables
        spatial_vars = list(lims.keys())

        # check coordinate system agrees
        if spatial_vars[0].coord_sys == spatial_vars[1].coord_sys:
            coord_sys = spatial_vars[0].coord_sys
        else:
            raise pybamm.DomainError(
                """spatial variables should have the same coordinate system,
                but have coordinate systems {} and {}""".format(
                    spatial_vars[0].coord_sys, spatial_vars[1].coord_sys
                )
            )

        # compute edges
        edges = {}
        for var in spatial_vars:
            if var.name not in ["y", "z"]:
                raise pybamm.DomainError(
                    "spatial variable must be y or z not {}".format(var.name)
                )
            else:
                # Create N Chebyshev nodes in the interval (a,b)
                N = npts[var.id] - 2
                ii = np.array(range(1, N + 1))
                a = lims[var]["min"]
                b = lims[var]["max"]
                x_cheb = (a + b) / 2 + (b - a) / 2 * np.cos(
                    (2 * ii - 1) * np.pi / 2 / N
                )

                # Append the boundary nodes. Note: we need to flip the order the
                # Chebyshev nodes as they are created in descending order.
                edges[var.name] = np.concatenate(([a], np.flip(x_cheb), [b]))

        super().__init__(edges, coord_sys, tabs)
Example #15
0
    def combine_submeshes(self, *submeshnames):
        """Combine submeshes into a new submesh, using self.submeshclass
        Raises pybamm.DomainError if submeshes to be combined do not match up (edges are
        not aligned).

        Parameters
        ----------
        submeshnames: list of str
            The names of the submeshes to be combined

        Returns
        -------
        submesh: :class:`self.submeshclass`
            A new submesh with the class defined by self.submeshclass
        """
        if submeshnames == ():
            raise ValueError("Submesh domains being combined cannot be empty")
        # If there is just a single submesh, we can return it directly
        if len(submeshnames) == 1:
            return self[submeshnames[0]]
        # Check that the final edge of each submesh is the same as the first edge of the
        # next submesh
        for i in range(len(submeshnames) - 1):
            if self[submeshnames[i]].edges[-1] != self[submeshnames[i + 1]].edges[0]:
                raise pybamm.DomainError("submesh edges are not aligned")

            coord_sys = self[submeshnames[i]].coord_sys
            coord_sys_r = self[submeshnames[i + 1]].coord_sys
            if coord_sys != coord_sys_r:
                raise pybamm.DomainError(
                    "trying to combine two meshes in different coordinate systems"
                )
        combined_submesh_edges = np.concatenate(
            [self[submeshnames[0]].edges]
            + [self[submeshname].edges[1:] for submeshname in submeshnames[1:]]
        )
        coord_sys = self[submeshnames[0]].coord_sys
        submesh = pybamm.SubMesh1D(combined_submesh_edges, coord_sys)
        # add in internal boundaries
        submesh.internal_boundaries = [
            self[submeshname].edges[0] for submeshname in submeshnames[1:]
        ]

        return submesh
Example #16
0
 def domain(self, domain):
     if domain is None:
         domain = []
     elif isinstance(domain, str):
         domain = [domain]
     if domain == [] and self.auxiliary_domains != {}:
         raise pybamm.DomainError(
             "Domain cannot be empty if auxiliary domains are not empty")
     if domain in self.auxiliary_domains.values():
         raise pybamm.DomainError(
             "Domain cannot be the same as an auxiliary domain")
     try:
         iter(domain)
     except TypeError:
         raise TypeError("Domain: argument domain is not iterable")
     else:
         self._domain = domain
         # Update id since domain has changed
         self.set_id()
Example #17
0
 def __init__(self, name, child):
     if child.domain == []:
         raise pybamm.DomainError(
             "Cannot upwind '{}' since its domain is empty. ".format(
                 child) + "Try broadcasting the object first, e.g.\n\n"
             "\tpybamm.div(pybamm.PrimaryBroadcast(symbol, 'domain'))")
     if child.evaluates_on_edges("primary") is True:
         raise TypeError(
             "Cannot upwind '{}' since it does not ".format(child) +
             "evaluate on nodes.")
     super().__init__(name, child)
Example #18
0
 def __init__(self, child):
     if child.domain == []:
         raise pybamm.DomainError(
             "Cannot take gradient of '{}' since its domain is empty. ".
             format(child) + "Try broadcasting the object first, e.g.\n\n"
             "\tpybamm.grad(pybamm.PrimaryBroadcast(symbol, 'domain'))")
     if child.evaluates_on_edges("primary") is True:
         raise TypeError(
             "Cannot take gradient of '{}' since it evaluates on edges".
             format(child))
     super().__init__("grad", child)
Example #19
0
 def get_children_domains(self, children):
     # combine domains from children
     domain = []
     for child in children:
         child_domain = child.domain
         if set(domain).isdisjoint(child_domain):
             domain += child_domain
         else:
             raise pybamm.DomainError(
                 """domain of children must be disjoint""")
     return domain
Example #20
0
    def check_and_set_domains(self, child, broadcast_type, broadcast_domain,
                              broadcast_auxiliary_domains):
        "See :meth:`Broadcast.check_and_set_domains`"
        if child.domain == []:
            raise TypeError(
                "Cannot take SecondaryBroadcast of an object with empty domain. "
                "Use PrimaryBroadcast instead.")
        # Can only do secondary broadcast from particle to electrode or from
        # electrode to current collector
        if child.domain[0] in [
                "negative particle",
                "positive particle",
        ] and broadcast_domain[0] not in [
                "negative electrode",
                "separator",
                "positive electrode",
        ]:
            raise pybamm.DomainError(
                """Secondary broadcast from particle domain must be to electrode or
                separator domains""")
        elif child.domain[0] in [
                "negative electrode",
                "separator",
                "positive electrode",
        ] and broadcast_domain != ["current collector"]:
            raise pybamm.DomainError(
                """Secondary broadcast from electrode or separator must be to
                current collector domains""")
        elif child.domain == ["current collector"]:
            raise pybamm.DomainError(
                "Cannot do secondary broadcast from current collector domain")
        # Domain stays the same as child domain and broadcast domain is secondary
        # domain
        domain = child.domain
        auxiliary_domains = {"secondary": broadcast_domain}
        # Child's secondary domain becomes tertiary domain
        if "secondary" in child.auxiliary_domains:
            auxiliary_domains["tertiary"] = child.auxiliary_domains[
                "secondary"]

        return domain, auxiliary_domains
Example #21
0
    def check_and_set_domains(self, child, broadcast_type, broadcast_domain,
                              broadcast_auxiliary_domains):
        "See :meth:`Broadcast.check_and_set_domains`"
        # Can only do primary broadcast from current collector to electrode or particle
        # or from electrode to particle. Note current collector to particle *is* allowed
        if child.domain == []:
            pass
        elif child.domain == ["current collector"
                              ] and broadcast_domain[0] not in [
                                  "negative electrode",
                                  "separator",
                                  "positive electrode",
                                  "negative particle",
                                  "positive particle",
                              ]:
            raise pybamm.DomainError(
                """Primary broadcast from current collector domain must be to electrode
                or separator or particle domains""")
        elif child.domain[0] in [
                "negative electrode",
                "separator",
                "positive electrode",
        ] and broadcast_domain[0] not in [
                "negative particle", "positive particle"
        ]:
            raise pybamm.DomainError(
                """Primary broadcast from electrode or separator must be to particle
                domains""")
        elif child.domain[0] in ["negative particle", "positive particle"]:
            raise pybamm.DomainError(
                "Cannot do primary broadcast from particle domain")

        domain = broadcast_domain
        auxiliary_domains = {}
        if child.domain != []:
            auxiliary_domains["secondary"] = child.domain
        if "secondary" in child.auxiliary_domains:
            auxiliary_domains["tertiary"] = child.auxiliary_domains[
                "secondary"]

        return domain, auxiliary_domains
Example #22
0
    def check_and_set_domains(self, child, broadcast_type, broadcast_domain,
                              broadcast_auxiliary_domains):
        "See :meth:`Broadcast.check_and_set_domains`"

        # Variables on the current collector can only be broadcast to 'primary'
        if child.domain == ["current collector"]:
            raise pybamm.DomainError(
                "Cannot do full broadcast from current collector domain")
        domain = broadcast_domain
        auxiliary_domains = broadcast_auxiliary_domains or {}

        return domain, auxiliary_domains
Example #23
0
    def internal_neumann_condition(
        self, left_symbol_disc, right_symbol_disc, left_mesh, right_mesh
    ):
        """
        A method to find the internal neumann conditions between two symbols
        on adjacent subdomains.

        Parameters
        ----------
        left_symbol_disc : :class:`pybamm.Symbol`
            The discretised symbol on the left subdomain
        right_symbol_disc : :class:`pybamm.Symbol`
            The discretised symbol on the right subdomain
        left_mesh : list
            The mesh on the left subdomain
        right_mesh : list
            The mesh on the right subdomain
        """

        left_npts = left_mesh[0].npts
        right_npts = right_mesh[0].npts

        sec_pts = len(left_mesh)

        if sec_pts != len(right_mesh):
            raise pybamm.DomainError(
                """Number of secondary points in subdomains do not match"""
            )

        left_sub_matrix = np.zeros((1, left_npts))
        left_sub_matrix[0][left_npts - 1] = 1
        left_matrix = pybamm.Matrix(csr_matrix(kron(eye(sec_pts), left_sub_matrix)))

        right_sub_matrix = np.zeros((1, right_npts))
        right_sub_matrix[0][0] = 1
        right_matrix = pybamm.Matrix(csr_matrix(kron(eye(sec_pts), right_sub_matrix)))

        # Remove domains to avoid clash
        left_domain = left_symbol_disc.domain
        right_domain = right_symbol_disc.domain
        left_symbol_disc.domain = []
        right_symbol_disc.domain = []

        # Finite volume derivative
        dy = right_matrix @ right_symbol_disc - left_matrix @ left_symbol_disc
        dx = right_mesh[0].nodes[0] - left_mesh[0].nodes[-1]

        # Change domains back
        left_symbol_disc.domain = left_domain
        right_symbol_disc.domain = right_domain

        return dy / dx
Example #24
0
 def get_children_domains(self, ldomain, rdomain):
     "Combine domains from children in appropriate way"
     if ldomain == rdomain:
         return ldomain
     elif ldomain == []:
         return rdomain
     elif rdomain == []:
         return ldomain
     else:
         raise pybamm.DomainError("""
             children must have same (or empty) domains, but left.domain is '{}'
             and right.domain is '{}'
             """.format(ldomain, rdomain))
Example #25
0
 def __init__(self, child):
     if child.domain == []:
         raise pybamm.DomainError(
             "Cannot take divergence of '{}' since its domain is empty. ".
             format(child) + "Try broadcasting the object first, e.g.\n\n"
             "\tpybamm.div(pybamm.PrimaryBroadcast(symbol, 'domain'))")
     if child.evaluates_on_edges("primary") is False:
         raise TypeError(
             "Cannot take divergence of '{}' since it does not ".format(
                 child) +
             "evaluate on edges. Usually, a gradient should be taken before the "
             "divergence.")
     super().__init__("div", child)
Example #26
0
    def __init__(self, lims, npts):
        spatial_vars, tabs = self.read_lims(lims)
        coord_sys = spatial_vars[0].coord_sys

        # compute edges
        edges = {}
        for var in spatial_vars:
            if var.name not in ["y", "z"]:
                raise pybamm.DomainError(
                    "spatial variable must be y or z not {}".format(var.name))
            else:
                edges[var.name] = np.linspace(lims[var]["min"],
                                              lims[var]["max"], npts[var.id])

        super().__init__(edges, coord_sys, tabs)
Example #27
0
    def get_children_auxiliary_domains(self, children):
        "Combine auxiliary domains from children, at all levels"
        aux_domains = {}
        for child in children:
            for level in child.auxiliary_domains.keys():
                if (level not in aux_domains or aux_domains[level] == [] or
                        child.auxiliary_domains[level] == aux_domains[level]):
                    aux_domains[level] = child.auxiliary_domains[level]
                else:
                    raise pybamm.DomainError(
                        """children must have same or empty auxiliary domains,
                        not {!s} and {!s}""".format(
                            aux_domains[level],
                            child.auxiliary_domains[level]))

        return aux_domains
Example #28
0
    def get_children_domains(self, children_list):
        """Obtains the unique domain of the children. If the
        children have different domains then raise an error"""
        domains = [child.domain for child in children_list if child.domain != []]

        # check that there is one common domain amongst children
        distinct_domains = set(tuple(dom) for dom in domains)

        if len(distinct_domains) > 1:
            raise pybamm.DomainError(
                "Functions can only be applied to variables on the same domain"
            )
        elif len(distinct_domains) == 0:
            domain = []
        else:
            domain = domains[0]

        return domain
Example #29
0
    def __init__(self, children, full_mesh, copy_this=None):
        # Convert any constant symbols in children to a Vector of the right size for
        # concatenation
        children = list(children)

        # Allow the base class to sort the domains into the correct order
        super().__init__(*children, name="domain concatenation")

        # ensure domain is sorted according to mesh keys
        domain_dict = {d: full_mesh.domain_order.index(d) for d in self.domain}
        self.domain = sorted(domain_dict, key=domain_dict.__getitem__)

        if copy_this is None:
            # store mesh
            self._full_mesh = full_mesh

            # Check that there is a domain, otherwise the functionality won't work
            # and we should raise a DomainError
            if self.domain == []:
                raise pybamm.DomainError(
                    """
                    domain cannot be empty for a DomainConcatenation.
                    Perhaps the children should have been Broadcasted first?
                    """
                )

            # create dict of domain => slice of final vector
            self.secondary_dimensions_npts = len(self.full_mesh[self.domain[0]])
            self._slices = self.create_slices(self)

            # store size of final vector
            self._size = self._slices[self.domain[-1]][-1].stop

            # create disc of domain => slice for each child
            self._children_slices = [
                self.create_slices(child) for child in self.cached_children
            ]
        else:
            self._full_mesh = copy.copy(copy_this._full_mesh)
            self._slices = copy.copy(copy_this._slices)
            self._size = copy.copy(copy_this._size)
            self._children_slices = copy.copy(copy_this._children_slices)
            self.secondary_dimensions_npts = copy_this.secondary_dimensions_npts
Example #30
0
 def domain(self, domain):
     ok_domain_list = [
         "Negative",
         "Separator",
         "Positive",
         "Negative electrode",
         "Negative electrolyte",
         "Separator electrolyte",
         "Positive electrode",
         "Positive electrolyte",
     ]
     if domain in ok_domain_list:
         self._domain = domain
     elif domain is None:
         pass
     else:
         raise pybamm.DomainError(
             "Domain '{}' not recognised (must be one of {})".format(
                 domain, ok_domain_list))