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)
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)
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