예제 #1
0
    def __init__(self, mesh, elem, mapping=None):

        self.mapping = mesh.mapping() if mapping is None else mapping

        self.dofs = Dofs(mesh, elem)

        if mesh.refdom != elem.refdom:
            raise ValueError("Incompatible Mesh and Element.")

        # global degree-of-freedom location
        # disabled for MappingMortar by checking mapping.maps
        if hasattr(elem, 'doflocs') and not hasattr(mapping, 'maps'):
            doflocs = self.mapping.F(elem.doflocs.T)
            self.doflocs = np.zeros((doflocs.shape[0], self.N))

            # match mapped dofs and global dof numbering
            for itr in range(doflocs.shape[0]):
                for jtr in range(self.element_dofs.shape[0]):
                    self.doflocs[itr, self.element_dofs[jtr]] =\
                        doflocs[itr, :, jtr]

        self.mesh = mesh
        self.elem = elem

        self.Nbfun = self.element_dofs.shape[0]

        self.nelems = None  # subclasses should overwrite

        self.refdom = mesh.refdom
        self.brefdom = mesh.brefdom
예제 #2
0
    def _get_dofs(self, facets):
        """Return global DOF numbers corresponding to a set of facets."""
        ix = self._expand_facets(facets)
        nodal_dofs = {}
        facet_dofs = {}
        edge_dofs = {}
        interior_dofs = {}
        offset = 0

        if ix.p is not None:
            for i in range(self.nodal_dofs.shape[0]):
                nodal_dofs[self.dofnames[i]] = self.nodal_dofs[i, ix.p]
            offset += self.nodal_dofs.shape[0]
        if ix.facets is not None:
            for i in range(self.facet_dofs.shape[0]):
                facet_dofs[self.dofnames[i +
                                         offset]] = self.facet_dofs[i,
                                                                    ix.facets]
            offset += self.facet_dofs.shape[0]
        if ix.edges is not None:
            for i in range(self.edge_dofs.shape[0]):
                edge_dofs[self.dofnames[i + offset]] = self.edge_dofs[i,
                                                                      ix.edges]
            offset += self.edge_dofs.shape[0]
        if ix.t is not None:
            for i in range(self.interior_dofs.shape[0]):
                interior_dofs[self.dofnames[i +
                                            offset]] = self.interior_dofs[i,
                                                                          ix.t]

        return Dofs(nodal_dofs, facet_dofs, edge_dofs, interior_dofs)
예제 #3
0
    def __init__(self,
                 mesh: Mesh,
                 elem: Element,
                 mapping: Optional[Mapping] = None,
                 intorder: Optional[int] = None,
                 quadrature: Optional[Tuple[ndarray, ndarray]] = None,
                 refdom: Type[Refdom] = Refdom,
                 dofs: Optional[Dofs] = None):

        if mesh.refdom != elem.refdom:
            raise ValueError("Incompatible Mesh and Element.")

        self.mapping = mesh._mapping() if mapping is None else mapping
        self.dofs = Dofs(mesh, elem) if dofs is None else dofs

        # global degree-of-freedom location
        try:
            doflocs = self.mapping.F(elem.doflocs.T)
            self.doflocs = np.zeros((doflocs.shape[0], self.N))

            # match mapped dofs and global dof numbering
            for itr in range(doflocs.shape[0]):
                for jtr in range(self.dofs.element_dofs.shape[0]):
                    self.doflocs[itr, self.dofs.element_dofs[jtr]] =\
                        doflocs[itr, :, jtr]
        except Exception:
            logger.warning("Unable to calculate global DOF locations.")

        self.mesh = mesh
        self.elem = elem

        self.Nbfun = self.dofs.element_dofs.shape[0]

        self.nelems = 0  # subclasses should overwrite

        if quadrature is not None:
            self.X, self.W = quadrature
        else:
            self.X, self.W = get_quadrature(
                refdom,
                intorder if intorder is not None else 2 * self.elem.maxdeg
            )
예제 #4
0
class AbstractBasis:
    """Finite element basis at global quadrature points.

    Please see the following implementations:

    - :class:`~skfem.assembly.CellBasis`, basis functions inside elements
    - :class:`~skfem.assembly.FacetBasis`, basis functions on boundary
    - :class:`~skfem.assembly.InteriorFacetBasis`, basis functions on facets
      inside the domain

    """

    mesh: Mesh
    elem: Element
    tind: Optional[ndarray] = None
    tind_normals: Optional[ndarray] = None
    dx: ndarray
    basis: List[Tuple[DiscreteField, ...]] = []
    X: ndarray
    W: ndarray
    dofs: Dofs

    def __init__(self,
                 mesh: Mesh,
                 elem: Element,
                 mapping: Optional[Mapping] = None,
                 intorder: Optional[int] = None,
                 quadrature: Optional[Tuple[ndarray, ndarray]] = None,
                 refdom: Type[Refdom] = Refdom,
                 dofs: Optional[Dofs] = None):

        if mesh.refdom != elem.refdom:
            raise ValueError("Incompatible Mesh and Element.")

        self.mapping = mesh._mapping() if mapping is None else mapping
        self.dofs = Dofs(mesh, elem) if dofs is None else dofs

        # global degree-of-freedom location
        try:
            doflocs = self.mapping.F(elem.doflocs.T)
            self.doflocs = np.zeros((doflocs.shape[0], self.N))

            # match mapped dofs and global dof numbering
            for itr in range(doflocs.shape[0]):
                for jtr in range(self.dofs.element_dofs.shape[0]):
                    self.doflocs[itr, self.dofs.element_dofs[jtr]] =\
                        doflocs[itr, :, jtr]
        except Exception:
            logger.warning("Unable to calculate global DOF locations.")

        self.mesh = mesh
        self.elem = elem

        self.Nbfun = self.dofs.element_dofs.shape[0]

        self.nelems = 0  # subclasses should overwrite

        if quadrature is not None:
            self.X, self.W = quadrature
        else:
            self.X, self.W = get_quadrature(
                refdom,
                intorder if intorder is not None else 2 * self.elem.maxdeg
            )

    @property
    def nodal_dofs(self):
        return self.dofs.nodal_dofs

    @property
    def facet_dofs(self):
        return self.dofs.facet_dofs

    @property
    def edge_dofs(self):
        return self.dofs.edge_dofs

    @property
    def interior_dofs(self):
        return self.dofs.interior_dofs

    @property
    def N(self):
        return self.dofs.N

    @property
    def element_dofs(self):
        if not hasattr(self, '_element_dofs'):
            if self.tind is None:
                self._element_dofs = self.dofs.element_dofs
            else:
                self._element_dofs = self.dofs.element_dofs[:, self.tind]
        return self._element_dofs

    def complement_dofs(self, *D):
        if type(D[0]) is dict:
            # if a dict of Dofs objects are given, flatten all
            D = tuple(D[0][key].all() for key in D[0])
        return np.setdiff1d(np.arange(self.N), np.concatenate(D))

    def find_dofs(self,
                  facets: Dict[str, ndarray] = None,
                  skip: List[str] = None) -> Dict[str, DofsView]:
        warn("find_dofs deprecated in favor of get_dofs.", DeprecationWarning)
        if facets is None:
            if self.mesh.boundaries is None:
                facets = {'all': self.mesh.boundary_facets()}
            else:
                facets = self.mesh.boundaries

        return {k: self.dofs.get_facet_dofs(facets[k], skip_dofnames=skip)
                for k in facets}

    def get_dofs(self,
                 facets: Optional[Any] = None,
                 elements: Optional[Any] = None,
                 skip: List[str] = None) -> Any:
        """Find global DOF numbers.

        Accepts an array of facet/element indices.  However, various argument
        types can be turned into an array of facet/element indices.

        Get all boundary DOFs:

        >>> import numpy as np
        >>> from skfem import MeshTri, Basis, ElementTriP1
        >>> m = MeshTri().refined()
        >>> basis = Basis(m, ElementTriP1())
        >>> basis.get_dofs().flatten()
        array([0, 1, 2, 3, 4, 5, 7, 8])

        Get DOFs via a function query:

        >>> import numpy as np
        >>> from skfem import MeshTri, Basis, ElementTriP1
        >>> m = MeshTri().refined()
        >>> basis = Basis(m, ElementTriP1())
        >>> basis.get_dofs(lambda x: np.isclose(x[0], 0)).flatten()
        array([0, 2, 5])

        Get DOFs via named boundaries:

        >>> import numpy as np
        >>> from skfem import MeshTri, Basis, ElementTriP1
        >>> m = (MeshTri()
        ...      .refined()
        ...      .with_boundaries({'left': lambda x: np.isclose(x[0], 0)}))
        >>> basis = Basis(m, ElementTriP1())
        >>> basis.get_dofs('left').flatten()
        array([0, 2, 5])

        Get DOFs via named subdomains:

        >>> from skfem import MeshTri, Basis, ElementTriP1
        >>> m = (MeshTri()
        ...      .refined()
        ...      .with_subdomains({'left': lambda x: x[0] < .5}))
        >>> basis = Basis(m, ElementTriP1())
        >>> basis.get_dofs(elements='left').flatten()
        array([0, 2, 4, 5, 6, 8])

        Further reduce the set of DOFs:

        >>> import numpy as np
        >>> from skfem import MeshTri, Basis, ElementTriArgyris
        >>> m = MeshTri().refined()
        >>> basis = Basis(m, ElementTriArgyris())
        >>> basis.get_dofs(lambda x: np.isclose(x[0], 0)).nodal.keys()
        dict_keys(['u', 'u_x', 'u_y', 'u_xx', 'u_xy', 'u_yy'])
        >>> basis.get_dofs(lambda x: np.isclose(x[0], 0)).all(['u', 'u_x'])
        array([ 0,  1, 12, 13, 30, 31])

        Skip some DOF names altogether:

        >>> import numpy as np
        >>> from skfem import MeshTri, Basis, ElementTriArgyris
        >>> m = MeshTri().refined()
        >>> basis = Basis(m, ElementTriArgyris())
        >>> basis.get_dofs(lambda x: np.isclose(x[0], 0),
        ...                skip=['u_x', 'u_y']).nodal.keys()
        dict_keys(['u', 'u_xx', 'u_xy', 'u_yy'])

        Combine several boundaries into one call:

        >>> import numpy as np
        >>> from skfem import MeshTri, Basis, ElementTriP1
        >>> m = (MeshTri()
        ...      .with_boundaries({'left': lambda x: np.isclose(x[0], 0),
        ...                        'right': lambda x: np.isclose(x[0], 1)}))
        >>> basis = Basis(m, ElementTriP1())
        >>> basis.get_dofs({'left', 'right'}).flatten()
        array([0, 1, 2, 3])

        Parameters
        ----------
        facets
            An array of facet indices.  If ``None``, find facets by
            ``self.mesh.boundary_facets()``.  If callable, call
            ``self.mesh.facets_satisfying(facets)`` to get the facets.  If
            string, use ``self.mesh.boundaries[facets]`` to get the facets.
            If list, tuple or set, use the combined facet indices.
        elements
            An array of element indices.  See above.
        skip
            List of dofnames to skip.

        """
        if isinstance(facets, dict):
            warn("Passing dict to get_dofs is deprecated.", DeprecationWarning)

            def to_indices(f):
                if callable(f):
                    return self.mesh.facets_satisfying(f)
                return f

            return {k: self.dofs.get_facet_dofs(to_indices(facets[k]),
                                                skip_dofnames=skip)
                    for k in facets}

        if elements is not None and facets is not None:
            raise ValueError

        if elements is not None:
            elements = self.mesh.normalize_elements(elements)
            return self.dofs.get_element_dofs(elements, skip_dofnames=skip)

        facets = self.mesh.normalize_facets(facets)
        return self.dofs.get_facet_dofs(facets, skip_dofnames=skip)

    def __repr__(self):
        size = sum([sum([y.size if hasattr(y, 'size') else 0
                         for y in x])
                    for x in self.basis[0]]) * 8 * len(self.basis)
        rep = ""
        rep += "<skfem {}({}, {}) object>\n".format(type(self).__name__,
                                                    type(self.mesh).__name__,
                                                    type(self.elem).__name__)
        rep += "  Number of elements: {}\n".format(self.nelems)
        rep += "  Number of DOFs: {}\n".format(self.N)
        rep += "  Size: {} B".format(size)
        return rep

    def __str__(self):
        return self.__repr__()

    def default_parameters(self):
        """This is used by :func:`skfem.assembly.asm` to get the default
        parameters for 'w'."""
        raise NotImplementedError("Default parameters not implemented.")

    def interpolate(self, w: ndarray) -> Union[DiscreteField,
                                               Tuple[DiscreteField, ...]]:
        """Interpolate a solution vector to quadrature points.

        Useful when a solution vector is needed in the forms, e.g., when
        evaluating functionals or when solving nonlinear problems.

        Parameters
        ----------
        w
            A solution vector.

        """
        if w.shape[0] != self.N:
            raise ValueError("Input array has wrong size.")

        if isinstance(self.elem, ElementVector):
            # ElementVector shouldn't get split here: workaround
            refs: List[Tuple[ndarray, 'AbstractBasis']] = [(np.array([]),
                                                            self)]
        else:
            refs = self.split(w)
        dfs: List[DiscreteField] = []

        # loop over solution components
        for c in range(len(refs)):
            ref = refs[c][1].basis[0][0]
            if ref.is_zero():
                dfs.append(ref)
                continue
            ref = ref.astuple
            fs = []

            def linear_combination(n, refn):
                """Global discrete function at quadrature points."""
                out = 0. * refn.copy()
                for i in range(self.Nbfun):
                    values = w[self.element_dofs[i]]
                    if self.basis[i][c].is_zero():
                        continue
                    out += np.einsum('...,...j->...j', values,
                                     self.basis[i][c].get(n))
                return out

            # interpolate DiscreteField
            for n in range(len(ref)):
                if ref[n] is not None:
                    fs.append(linear_combination(n, ref[n]))
                else:
                    fs.append(None)

            dfs.append(DiscreteField(*fs))

        if len(dfs) > 1:
            return tuple(dfs)
        return dfs[0]

    def split_indices(self) -> List[ndarray]:
        """Return indices for the solution components."""
        if ((isinstance(self.elem, ElementComposite)
             or isinstance(self.elem, ElementVector))):
            nelems = (len(self.elem.elems)
                      if isinstance(self.elem, ElementComposite)
                      else self.mesh.dim())
            o = np.zeros(4, dtype=np.int64)
            output: List[ndarray] = []
            for k in range(nelems):
                e = (self.elem.elems[k]
                     if isinstance(self.elem, ElementComposite)
                     else self.elem.elem)
                output.append(np.concatenate((
                    self.nodal_dofs[o[0]:(o[0] + e.nodal_dofs)].flatten('F'),
                    self.edge_dofs[o[1]:(o[1] + e.edge_dofs)].flatten('F'),
                    self.facet_dofs[o[2]:(o[2] + e.facet_dofs)].flatten('F'),
                    (self.interior_dofs[o[3]:(o[3] + e.interior_dofs)]
                     .flatten('F'))
                )).astype(np.int64))
                o += np.array([e.nodal_dofs,
                               e.edge_dofs,
                               e.facet_dofs,
                               e.interior_dofs])
            return output
        return [np.unique(self.dofs.element_dofs)]

    def split_bases(self) -> List['AbstractBasis']:
        """Return Basis objects for the solution components."""
        if isinstance(self.elem, ElementComposite):
            return [type(self)(self.mesh, e, self.mapping,
                               quadrature=self.quadrature)
                    for e in self.elem.elems]
        elif isinstance(self.elem, ElementVector):
            return [type(self)(self.mesh, self.elem.elem, self.mapping,
                               quadrature=self.quadrature)
                    for _ in range(self.mesh.dim())]
        return [self]

    @property
    def quadrature(self):
        return self.X, self.W

    def split(self, x: ndarray) -> List[Tuple[ndarray, 'AbstractBasis']]:
        """Split a solution vector into components."""
        xs = [x[ix] for ix in self.split_indices()]
        return list(zip(xs, self.split_bases()))

    def zero_w(self) -> ndarray:
        """Return a zero array with correct dimensions for
        :func:`~skfem.assembly.asm`."""
        return np.zeros((self.nelems, 0 if self.W is None else len(self.W)))

    def zeros(self) -> ndarray:
        """Return a zero array with same dimensions as the solution."""
        return np.zeros(self.N)

    def with_element(self, elem: Element) -> 'AbstractBasis':
        """Create a copy of ``self`` that uses different element."""
        raise NotImplementedError

    def _projection(self, interp):

        from skfem.assembly import BilinearForm, LinearForm
        from skfem.helpers import inner

        if isinstance(interp, float):
            interp = interp + self.global_coordinates()[0] * 0.

        if callable(interp):
            interp = interp(self.global_coordinates())

        return (
            BilinearForm(lambda u, v, _: inner(u, v)).assemble(self),
            LinearForm(lambda v, w: inner(interp, v)).assemble(self),
        )

    def project(self, interp, **kwargs):
        raise NotImplementedError

    def plot(self, x, visuals='matplotlib', **kwargs):
        """Convenience wrapper for skfem.visuals."""
        mod = importlib.import_module('skfem.visuals.{}'.format(visuals))
        return mod.plot(self, x, **kwargs)

    def draw(self, visuals='matplotlib', **kwargs):
        """Convenience wrapper for skfem.visuals."""
        mod = importlib.import_module('skfem.visuals.{}'.format(visuals))
        return mod.draw(self, **kwargs)
예제 #5
0
class Basis:
    """Finite element basis at global quadrature points.

    Please see the following implementations:

    - :class:`~skfem.assembly.InteriorBasis`, basis functions inside elements
    - :class:`~skfem.assembly.FacetBasis`, basis functions on boundaries

    """

    tind: ndarray = None

    def __init__(self, mesh, elem, mapping=None):

        self.mapping = mesh.mapping() if mapping is None else mapping

        self.dofs = Dofs(mesh, elem)

        if mesh.refdom != elem.refdom:
            raise ValueError("Incompatible Mesh and Element.")

        # global degree-of-freedom location
        # disabled for MappingMortar by checking mapping.maps
        if hasattr(elem, 'doflocs') and not hasattr(mapping, 'maps'):
            doflocs = self.mapping.F(elem.doflocs.T)
            self.doflocs = np.zeros((doflocs.shape[0], self.N))

            # match mapped dofs and global dof numbering
            for itr in range(doflocs.shape[0]):
                for jtr in range(self.element_dofs.shape[0]):
                    self.doflocs[itr, self.element_dofs[jtr]] =\
                        doflocs[itr, :, jtr]

        self.mesh = mesh
        self.elem = elem

        self.Nbfun = self.element_dofs.shape[0]

        self.nelems = None  # subclasses should overwrite

        self.refdom = mesh.refdom
        self.brefdom = mesh.brefdom

    @property
    def nodal_dofs(self):
        return self.dofs.nodal_dofs

    @property
    def facet_dofs(self):
        return self.dofs.facet_dofs

    @property
    def edge_dofs(self):
        return self.dofs.edge_dofs

    @property
    def interior_dofs(self):
        return self.dofs.interior_dofs

    @property
    def N(self):
        return self.dofs.N

    @property
    def element_dofs(self):
        if self.tind is None:
            return self.dofs.element_dofs
        return self.dofs.element_dofs[:, self.tind]

    def complement_dofs(self, *D):
        if type(D[0]) is dict:
            # if a dict of Dofs objects are given, flatten all
            D = tuple(D[0][key].all() for key in D[0])
        return np.setdiff1d(np.arange(self.N), np.concatenate(D))

    def find_dofs(self,
                  facets: Dict[str, ndarray] = None,
                  skip: List[str] = None) -> Dict[str, Dofs]:
        """Return global DOF numbers corresponding to a dictionary of facets.

        Facets can be queried from :class:`~skfem.mesh.Mesh` objects:

        >>> m = MeshTri()
        >>> m.refine()
        >>> m.facets_satisfying(lambda x: x[0] == 0)
        array([1, 5])

        This corresponds to a list of facet indices that can be passed over:

        >>> basis = InteriorBasis(m, ElementTriP1())
        >>> basis.find_dofs({'left': np.array([1, 5])})['left']
        Dofs(nodal={'u': array([0, 2, 5])}, facet={}, edge={}, interior={})

        Parameters
        ----------
        facets
            A dictionary of facets. If `None`, use `self.mesh.boundaries`
            if set or otherwise use `{'all': self.mesh.boundary_facets()}`.
        skip
            List of dofnames to skip.

        """
        if facets is None:
            if self.mesh.boundaries is None:
                facets = {'all': self.mesh.boundary_facets()}
            else:
                facets = self.mesh.boundaries

        return {
            k: self.dofs.get_facet_dofs(facets[k], skip_dofnames=skip)
            for k in facets
        }

    def get_dofs(self, facets: Optional[Any] = None) -> Any:
        """Find global DOF numbers.

        Accepts a richer set of types than
        :meth:`skfem.assembly.basis.Basis.find_dofs`.

        Parameters
        ----------
        facets
            A list of facet indices. If `None`, find facets by
            Mesh.boundary_facets.  If callable, call Mesh.facets_satisfying
            to get facets. If array, find the corresponding DOFs. If dict of
            arrays, find DOFs for each entry. If dict of callables, call
            Mesh.facets_satisfying for each entry to get facets and then find
            DOFs for those.

        Returns
        -------
        Dofs or Dict[str, Dofs]
            A subset of DOFs as :class:`skfem.assembly.dofs.Dofs`.

        """
        if facets is None:
            facets = self.mesh.boundary_facets()
        elif callable(facets):
            facets = self.mesh.facets_satisfying(facets)
        if isinstance(facets, dict):

            def to_indices(f):
                if callable(f):
                    return self.mesh.facets_satisfying(f)
                return f

            return {
                k: self.dofs.get_facet_dofs(to_indices(facets[k]))
                for k in facets
            }
        return self.dofs.get_facet_dofs(facets)

    def default_parameters(self):
        """This is used by :func:`skfem.assembly.asm` to get the default
        parameters for 'w'."""
        raise NotImplementedError("Default parameters not implemented.")

    def interpolate(
            self,
            w: ndarray) -> Union[DiscreteField, Tuple[DiscreteField, ...]]:
        """Interpolate a solution vector to quadrature points.

        Useful when a solution vector is needed in the forms, e.g., when
        evaluating functionals or when solving nonlinear problems.

        Parameters
        ----------
        w
            A solution vector.

        """
        if w.shape[0] != self.N:
            raise ValueError("Input array has wrong size.")

        refs = self.basis[0]
        dfs: List[DiscreteField] = []

        # loop over solution components
        for c in range(len(refs)):
            ref = refs[c]
            fs = []

            def linear_combination(n, refn):
                """Global discrete function at quadrature points."""
                out = 0. * refn.copy()
                for i in range(self.Nbfun):
                    values = w[self.element_dofs[i]][:, None]
                    if len(refn.shape) == 2:  # values
                        out += values * self.basis[i][c][n]
                    elif len(refn.shape) == 3:  # derivatives
                        for j in range(out.shape[0]):
                            out[j, :, :] += values * self.basis[i][c][n][j]
                    elif len(refn.shape) == 4:  # second derivatives
                        for j in range(out.shape[0]):
                            for k in range(out.shape[1]):
                                out[j, k, :, :] += \
                                    values * self.basis[i][c][n][j, k]
                    elif len(refn.shape) == 5:  # third derivatives
                        for j in range(out.shape[0]):
                            for k in range(out.shape[1]):
                                for l in range(out.shape[2]):
                                    out[j, k, l, :, :] += \
                                        values * \
                                        self.basis[i][c][-1][n][j, k, l]
                    elif len(refn.shape) == 6:  # fourth derivatives
                        for j in range(out.shape[0]):
                            for k in range(out.shape[1]):
                                for l in range(out.shape[2]):
                                    for m in range(out.shape[3]):
                                        out[j, k, l, m, :, :] += \
                                            values *\
                                            self.basis[i][c][-1][n][j, k, l, m]
                    else:
                        raise ValueError("The requested order of "
                                         "derivatives not supported.")
                return out

            # interpolate first and second derivatives
            for n in range(len(ref) - 1):
                if ref[n] is not None:
                    fs.append(linear_combination(n, ref[n]))
                else:
                    fs.append(None)

            # interpolate high-order derivatives
            fs.append([])

            if ref[-1] is not None:
                for n in range(len(ref[-1])):
                    fs[-1].append(linear_combination(n, ref[-1][n]))

            dfs.append(DiscreteField(*fs))

        if len(dfs) > 1:
            return tuple(dfs)
        return dfs[0]

    def split_indices(self) -> List[ndarray]:
        """Return indices for the solution components."""
        if isinstance(self.elem, ElementComposite):
            o = np.zeros(4, dtype=np.int)
            output = [None] * len(self.elem.elems)
            for k in range(len(self.elem.elems)):
                e = self.elem.elems[k]
                output[k] = np.concatenate(
                    (self.nodal_dofs[o[0]:(o[0] + e.nodal_dofs)].flatten(),
                     self.edge_dofs[o[1]:(o[1] + e.edge_dofs)].flatten(),
                     self.facet_dofs[o[2]:(o[2] + e.facet_dofs)].flatten(),
                     self.interior_dofs[o[3]:(
                         o[3] + e.interior_dofs)].flatten())).astype(np.int)
                o += np.array(
                    [e.nodal_dofs, e.edge_dofs, e.facet_dofs, e.interior_dofs])
            return output
        raise ValueError("Basis.elem has only a single component!")

    def split_bases(self) -> List[BasisType]:
        """Return Basis objects for the solution components."""
        if isinstance(self.elem, ElementComposite):
            return [
                type(self)(self.mesh,
                           e,
                           self.mapping,
                           quadrature=self.quadrature) for e in self.elem.elems
            ]
        raise ValueError("Basis.elem has only a single component!")

    @property
    def quadrature(self):
        return self.X, self.W

    def split(self, x: ndarray) -> List[Tuple[ndarray, BasisType]]:
        """Split a solution vector into components."""
        xs = [x[ix] for ix in self.split_indices()]
        return list(zip(xs, self.split_bases()))

    def zero_w(self) -> ndarray:
        """Return a zero array with correct dimensions for
        :func:`~skfem.assembly.asm`."""
        return np.zeros((self.nelems, len(self.W)))

    def zeros(self) -> ndarray:
        """Return a zero array with same dimensions as the solution."""
        return np.zeros(self.N)
예제 #6
0
class Basis:
    """Finite element basis at global quadrature points.

    Please see the following implementations:

    - :class:`~skfem.assembly.InteriorBasis`, basis functions inside elements
    - :class:`~skfem.assembly.ExteriorFacetBasis`, basis functions on boundary
    - :class:`~skfem.assembly.InteriorFacetBasis`, basis functions on facets
      inside the domain

    """

    mesh: Mesh
    elem: Element
    tind: Optional[ndarray] = None
    dx: ndarray
    basis: List[Tuple[DiscreteField, ...]] = []
    X: ndarray
    W: ndarray

    def __init__(self,
                 mesh: Mesh,
                 elem: Element,
                 mapping: Optional[Mapping] = None,
                 intorder: Optional[int] = None,
                 quadrature: Optional[Tuple[ndarray, ndarray]] = None,
                 refdom: str = "none"):

        if mesh.refdom != elem.refdom:
            raise ValueError("Incompatible Mesh and Element.")

        self.mapping = mesh._mapping() if mapping is None else mapping

        self.dofs = Dofs(mesh, elem)

        # global degree-of-freedom location
        try:
            doflocs = self.mapping.F(elem.doflocs.T)
            self.doflocs = np.zeros((doflocs.shape[0], self.N))

            # match mapped dofs and global dof numbering
            for itr in range(doflocs.shape[0]):
                for jtr in range(self.element_dofs.shape[0]):
                    self.doflocs[itr, self.element_dofs[jtr]] =\
                        doflocs[itr, :, jtr]
        except Exception:
            warnings.warn("Unable to calculate DOF locations.")

        self.mesh = mesh
        self.elem = elem

        self.Nbfun = self.element_dofs.shape[0]

        self.nelems = 0  # subclasses should overwrite

        if quadrature is not None:
            self.X, self.W = quadrature
        else:
            self.X, self.W = get_quadrature(
                refdom,
                intorder if intorder is not None else 2 * self.elem.maxdeg)

    @property
    def nodal_dofs(self):
        return self.dofs.nodal_dofs

    @property
    def facet_dofs(self):
        return self.dofs.facet_dofs

    @property
    def edge_dofs(self):
        return self.dofs.edge_dofs

    @property
    def interior_dofs(self):
        return self.dofs.interior_dofs

    @property
    def N(self):
        return self.dofs.N

    @property
    def element_dofs(self):
        if self.tind is None:
            return self.dofs.element_dofs
        return self.dofs.element_dofs[:, self.tind]

    def complement_dofs(self, *D):
        if type(D[0]) is dict:
            # if a dict of Dofs objects are given, flatten all
            D = tuple(D[0][key].all() for key in D[0])
        return np.setdiff1d(np.arange(self.N), np.concatenate(D))

    def find_dofs(self,
                  facets: Dict[str, ndarray] = None,
                  skip: List[str] = None) -> Dict[str, DofsView]:
        """Return global DOF numbers corresponding to a dictionary of facets.

        Facets can be queried from :class:`~skfem.mesh.Mesh` objects:

        >>> from skfem import MeshTri
        >>> m = MeshTri().refined()
        >>> m.facets_satisfying(lambda x: x[0] == 0)
        array([1, 5])

        This corresponds to a list of facet indices that can be passed over:

        >>> import numpy as np
        >>> from skfem import InteriorBasis, ElementTriP1
        >>> basis = InteriorBasis(m, ElementTriP1())
        >>> basis.find_dofs({'left': np.array([1, 5])})['left'].all()
        array([0, 2, 5])

        Parameters
        ----------
        facets
            A dictionary of facets. If ``None``, use ``self.mesh.boundaries``
            if set or otherwise use ``{'all': self.mesh.boundary_facets()}``.
        skip
            List of dofnames to skip.

        """
        if facets is None:
            if self.mesh.boundaries is None:
                facets = {'all': self.mesh.boundary_facets()}
            else:
                facets = self.mesh.boundaries

        return {
            k: self.dofs.get_facet_dofs(facets[k], skip_dofnames=skip)
            for k in facets
        }

    def get_dofs(self, facets: Optional[Any] = None) -> Any:
        """Find global DOF numbers.

        Accepts a richer set of types than
        :meth:`skfem.assembly.Basis.find_dofs`.

        Parameters
        ----------
        facets
            A list of facet indices. If ``None``, find facets by
            ``self.mesh.boundary_facets()``.  If callable, call
            ``self.mesh.facets_satisfying(facets)`` to get the facets.
            If array, simply find the corresponding DOF's. If a dictionary
            of arrays, find DOF's for each entry. If a dictionary of
            callables, call ``self.mesh.facets_satisfying`` for each entry to
            get facets and then find DOF's for those.

        """
        if facets is None:
            facets = self.mesh.boundary_facets()
        elif callable(facets):
            facets = self.mesh.facets_satisfying(facets)
        if isinstance(facets, dict):

            def to_indices(f):
                if callable(f):
                    return self.mesh.facets_satisfying(f)
                return f

            return {
                k: self.dofs.get_facet_dofs(to_indices(facets[k]))
                for k in facets
            }
        return self.dofs.get_facet_dofs(facets)

    def default_parameters(self):
        """This is used by :func:`skfem.assembly.asm` to get the default
        parameters for 'w'."""
        raise NotImplementedError("Default parameters not implemented.")

    def interpolate(
            self,
            w: ndarray) -> Union[DiscreteField, Tuple[DiscreteField, ...]]:
        """Interpolate a solution vector to quadrature points.

        Useful when a solution vector is needed in the forms, e.g., when
        evaluating functionals or when solving nonlinear problems.

        Parameters
        ----------
        w
            A solution vector.

        """
        if w.shape[0] != self.N:
            raise ValueError("Input array has wrong size.")

        refs = self.basis[0]
        dfs: List[DiscreteField] = []

        # loop over solution components
        for c in range(len(refs)):
            ref = refs[c]
            fs = []

            def linear_combination(n, refn):
                """Global discrete function at quadrature points."""
                out = 0. * refn.copy()
                for i in range(self.Nbfun):
                    values = w[self.element_dofs[i]]
                    out += np.einsum('...,...j->...j', values,
                                     self.basis[i][c][n])
                return out

            # interpolate DiscreteField
            for n in range(len(ref)):
                if ref[n] is not None:
                    fs.append(linear_combination(n, ref[n]))
                else:
                    fs.append(None)

            dfs.append(DiscreteField(*fs))

        if len(dfs) > 1:
            return tuple(dfs)
        return dfs[0]

    def split_indices(self) -> List[ndarray]:
        """Return indices for the solution components."""
        if isinstance(self.elem, ElementComposite):
            o = np.zeros(4, dtype=np.int_)
            output: List[ndarray] = []
            for k in range(len(self.elem.elems)):
                e = self.elem.elems[k]
                output.append(
                    np.concatenate(
                        (self.nodal_dofs[o[0]:(o[0] + e.nodal_dofs)].flatten(),
                         self.edge_dofs[o[1]:(o[1] + e.edge_dofs)].flatten(),
                         self.facet_dofs[o[2]:(o[2] + e.facet_dofs)].flatten(),
                         self.interior_dofs[o[3]:(
                             o[3] + e.interior_dofs)].flatten())).astype(
                                 np.int_))
                o += np.array(
                    [e.nodal_dofs, e.edge_dofs, e.facet_dofs, e.interior_dofs])
            return output
        raise ValueError("Basis.elem has only a single component!")

    def split_bases(self) -> List['Basis']:
        """Return Basis objects for the solution components."""
        if isinstance(self.elem, ElementComposite):
            return [
                type(self)(self.mesh,
                           e,
                           self.mapping,
                           quadrature=self.quadrature) for e in self.elem.elems
            ]
        raise ValueError("Basis.elem has only a single component!")

    @property
    def quadrature(self):
        return self.X, self.W

    def split(self, x: ndarray) -> List[Tuple[ndarray, 'Basis']]:
        """Split a solution vector into components."""
        xs = [x[ix] for ix in self.split_indices()]
        return list(zip(xs, self.split_bases()))

    def zero_w(self) -> ndarray:
        """Return a zero array with correct dimensions for
        :func:`~skfem.assembly.asm`."""
        return np.zeros((self.nelems, 0 if self.W is None else len(self.W)))

    def zeros(self) -> ndarray:
        """Return a zero array with same dimensions as the solution."""
        return np.zeros(self.N)

    def with_element(self, elem: Element) -> 'Basis':
        """Create a copy of ``self`` that uses different element."""
        raise NotImplementedError
예제 #7
0
    def _get_dofs(self, facets: ndarray, skip: List[str] = []):
        """Return :class:`skfem.assembly.Dofs` corresponding to facets."""
        m = self.mesh
        nodal_ix = np.unique(m.facets[:, facets].flatten())
        facet_ix = facets

        if m.dim() == 3:
            edge_candidates = m.t2e[:, m.f2t[0, facets]].flatten()
            # subset of edges that share all points with the given facets
            subset_ix = np.nonzero(
                np.prod(np.isin(m.edges[:, edge_candidates],
                                m.facets[:, facets].flatten()),
                        axis=0))[0]
            edge_ix = np.intersect1d(m.boundary_edges(),
                                     edge_candidates[subset_ix])
        else:
            edge_ix = []

        n_nodal = self.nodal_dofs.shape[0]
        n_facet = self.facet_dofs.shape[0]
        n_edge = self.edge_dofs.shape[0]

        # group dofs based on 'dofnames' on different topological entities
        nodals = {
            self.dofnames[i]: np.zeros((0, len(nodal_ix)), dtype=np.int64)
            for i in range(n_nodal) if self.dofnames[i] not in skip
        }
        for i in range(n_nodal):
            if self.dofnames[i] not in skip:
                nodals[self.dofnames[i]] =\
                    np.vstack((nodals[self.dofnames[i]],
                               self.nodal_dofs[i, nodal_ix]))
        off = n_nodal

        facets = {
            self.dofnames[i + off]: np.zeros((0, len(facet_ix)),
                                             dtype=np.int64)
            for i in range(n_facet) if self.dofnames[i + off] not in skip
        }
        for i in range(n_facet):
            if self.dofnames[i + off] not in skip:
                facets[self.dofnames[i + off]] =\
                    np.vstack((facets[self.dofnames[i + off]],
                               self.facet_dofs[i, facet_ix]))
        off += n_facet

        edges = {
            self.dofnames[i + off]: np.zeros((0, len(edge_ix)), dtype=np.int64)
            for i in range(n_edge) if self.dofnames[i + off] not in skip
        }
        for i in range(n_edge):
            if self.dofnames[i + off] not in skip:
                edges[self.dofnames[i + off]] =\
                    np.vstack((edges[self.dofnames[i + off]],
                               self.edge_dofs[i, edge_ix]))

        return Dofs(nodal={k: nodals[k].flatten()
                           for k in nodals},
                    facet={k: facets[k].flatten()
                           for k in facets},
                    edge={k: edges[k].flatten()
                          for k in edges})