Source code for FIAT.nodal_enriched

# Copyright (C) 2013 Andrew T. T. McRae, 2015-2016 Jan Blechta
#
# This file is part of FIAT (https://www.fenicsproject.org)
#
# SPDX-License-Identifier:    LGPL-3.0-or-later

import numpy as np
import math

from FIAT.expansions import polynomial_entity_ids
from FIAT.polynomial_set import PolynomialSet
from FIAT.dual_set import DualSet
from FIAT.finite_element import CiarletElement
from FIAT.barycentric_interpolation import LagrangeLineExpansionSet

__all__ = ['NodalEnrichedElement']


[docs] class NodalEnrichedElement(CiarletElement): """NodalEnriched element is a direct sum of a sequence of finite elements. Primal basis is reorthogonalized to the dual basis for nodality. The following is equivalent: * the constructor is well-defined, * the resulting element is unisolvent and its basis is nodal, * the supplied elements are unisolvent with nodal basis and their primal bases are mutually linearly independent, * the supplied elements are unisolvent with nodal basis and their dual bases are mutually linearly independent. """ def __init__(self, *elements): # Test elements are nodal if not all(e.is_nodal() for e in elements): raise ValueError("Not all elements given for construction " "of NodalEnrichedElement are nodal") # Extract common data embedded_degrees = [e.get_nodal_basis().get_embedded_degree() for e in elements] embedded_degree = max(embedded_degrees) degree = max(e.degree() for e in elements) order = max(e.get_order() for e in elements) formdegree = None if any(e.get_formdegree() is None for e in elements) \ else max(e.get_formdegree() for e in elements) # LagrangeExpansionSet has fixed degree, ensure we grab the highest one elem = elements[embedded_degrees.index(embedded_degree)] ref_el = elem.get_reference_complex() expansion_set = elem.get_nodal_basis().get_expansion_set() mapping = elem.mapping()[0] value_shape = elem.value_shape() # Sanity check assert all(e.get_reference_complex() == ref_el for e in elements) assert all(set(e.mapping()) == {mapping, } for e in elements) assert all(e.value_shape() == value_shape for e in elements) # Merge polynomial sets if isinstance(expansion_set, LagrangeLineExpansionSet): # Obtain coefficients via interpolation points = expansion_set.get_points() coeffs = np.vstack([e.tabulate(0, points)[(0,)] for e in elements]) else: assert all(e.get_nodal_basis().get_expansion_set() == expansion_set for e in elements) coeffs = [e.get_coeffs() for e in elements] coeffs = _merge_coeffs(coeffs, ref_el, embedded_degrees, expansion_set.continuity) poly_set = PolynomialSet(ref_el, degree, embedded_degree, expansion_set, coeffs) # Renumber dof numbers offsets = np.cumsum([0] + [e.space_dimension() for e in elements[:-1]]) entity_ids = _merge_entity_ids((e.entity_dofs() for e in elements), offsets) # Merge dual bases nodes = [node for e in elements for node in e.dual_basis()] ref_el = ref_el.get_parent() or ref_el dual_set = DualSet(nodes, ref_el, entity_ids) # CiarletElement constructor adjusts poly_set coefficients s.t. # dual_set is really dual to poly_set super().__init__(poly_set, dual_set, order, formdegree=formdegree, mapping=mapping)
def _merge_coeffs(coeffss, ref_el, degrees, continuity): # Indices of the hierachical expansion set on each facet entity_ids = polynomial_entity_ids(ref_el, max(degrees), continuity) # Number of bases members total_dim = sum(c.shape[0] for c in coeffss) # Value shape value_shape = coeffss[0].shape[1:-1] assert all(c.shape[1:-1] == value_shape for c in coeffss) # Number of expansion polynomials max_expansion_dim = max(c.shape[-1] for c in coeffss) # Compose new coeffs shape = (total_dim, *value_shape, max_expansion_dim) new_coeffs = np.zeros(shape, dtype=coeffss[0].dtype) counter = 0 for c, degree in zip(coeffss, degrees): ids = [] if continuity == "C0": dims = sorted(entity_ids) else: dims = (ref_el.get_spatial_dimension(),) for dim in dims: if continuity == "C0": dimPk = math.comb(degree - 1, dim) else: dimPk = math.comb(degree + dim, dim) for entity in sorted(entity_ids[dim]): ids.extend(entity_ids[dim][entity][:dimPk]) num_members = c.shape[0] new_coeffs[counter:counter+num_members, ..., ids] = c counter += num_members assert counter == total_dim return new_coeffs def _merge_entity_ids(entity_ids, offsets): ret = {} for i, ids in enumerate(entity_ids): for dim in ids: if dim not in ret: ret[dim] = {} for entity in ids[dim]: if entity not in ret[dim]: ret[dim][entity] = [] ret[dim][entity].extend(np.array(ids[dim][entity]) + offsets[i]) return ret