def __init__(self, poly_set, dual, order, formdegree=None, mapping="affine", ref_el=None): ref_el = ref_el or poly_set.get_reference_element() super().__init__(ref_el, dual, order, formdegree, mapping) # build generalized Vandermonde matrix old_coeffs = poly_set.get_coeffs() dualmat = dual.to_riesz(poly_set) shp = dualmat.shape if len(shp) > 2: num_cols = np.prod(shp[1:]) A = np.reshape(dualmat, (dualmat.shape[0], num_cols)) B = np.reshape(old_coeffs, (old_coeffs.shape[0], num_cols)) else: A = dualmat B = old_coeffs V = np.dot(A, np.transpose(B)) self.V = V Vinv = np.linalg.inv(V) new_coeffs_flat = np.dot(np.transpose(Vinv), B) new_shp = tuple([new_coeffs_flat.shape[0]] + list(shp[1:])) new_coeffs = np.reshape(new_coeffs_flat, new_shp) self.poly_set = PolynomialSet(ref_el, poly_set.get_degree(), poly_set.get_embedded_degree(), poly_set.get_expansion_set(), new_coeffs, poly_set.get_dmats())
def NedelecSpace2D(ref_el, k): """Constructs a basis for the 2d H(curl) space of the first kind which is (P_k)^2 + P_k rot( x )""" sd = ref_el.get_spatial_dimension() if sd != 2: raise Exception("NedelecSpace2D requires 2d reference element") vec_Pkp1 = ONPolynomialSet(ref_el, k + 1, (sd, )) dimPkp1 = expansions.polynomial_dimension(ref_el, k + 1) dimPk = expansions.polynomial_dimension(ref_el, k) dimPkm1 = expansions.polynomial_dimension(ref_el, k - 1) vec_Pk_indices = list( chain(*(range(i * dimPkp1, i * dimPkp1 + dimPk) for i in range(sd)))) vec_Pk_from_Pkp1 = vec_Pkp1.take(vec_Pk_indices) Pkp1 = ONPolynomialSet(ref_el, k + 1) PkH = Pkp1.take(list(range(dimPkm1, dimPk))) Q = quadrature.make_quadrature(ref_el, 2 * k + 2) Qpts = np.array(Q.get_points()) Qwts = np.array(Q.get_weights()) zero_index = tuple([0 for i in range(sd)]) PkH_at_Qpts = PkH.tabulate(Qpts)[zero_index] Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[zero_index] PkH_crossx_coeffs = np.zeros( (PkH.get_num_members(), sd, Pkp1.get_num_members()), "d") def rot_x_foo(a): if a == 0: return 1, 1.0 elif a == 1: return 0, -1.0 for i in range(PkH.get_num_members()): for j in range(sd): (ind, sign) = rot_x_foo(j) for k in range(Pkp1.get_num_members()): PkH_crossx_coeffs[i, j, k] = sign * sum( Qwts * PkH_at_Qpts[i, :] * Qpts[:, ind] * Pkp1_at_Qpts[k, :]) # for l in range( len( Qpts ) ): # PkH_crossx_coeffs[i,j,k] += Qwts[ l ] \ # * PkH_at_Qpts[i,l] \ # * Qpts[l][ind] \ # * Pkp1_at_Qpts[k,l] \ # * sign PkHcrossx = PolynomialSet(ref_el, k + 1, k + 1, vec_Pkp1.get_expansion_set(), PkH_crossx_coeffs, vec_Pkp1.get_dmats()) return polynomial_set_union_normalized(vec_Pk_from_Pkp1, PkHcrossx)
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 ref_el = elements[0].get_reference_element() expansion_set = elements[0].get_nodal_basis().get_expansion_set() degree = min(e.get_nodal_basis().get_degree() for e in elements) embedded_degree = max(e.get_nodal_basis().get_embedded_degree() for e in elements) order = max(e.get_order() for e in elements) mapping = elements[0].mapping()[0] formdegree = None if any(e.get_formdegree() is None for e in elements) \ else max(e.get_formdegree() for e in elements) value_shape = elements[0].value_shape() # Sanity check assert all(e.get_nodal_basis().get_reference_element() == ref_el for e in elements) assert all(type(e.get_nodal_basis().get_expansion_set()) == type(expansion_set) for e in elements) assert all(e_mapping == mapping for e in elements for e_mapping in e.mapping()) assert all(e.value_shape() == value_shape for e in elements) # Merge polynomial sets coeffs = _merge_coeffs([e.get_coeffs() for e in elements]) dmats = _merge_dmats([e.dmats() for e in elements]) poly_set = PolynomialSet(ref_el, degree, embedded_degree, expansion_set, coeffs, dmats) # 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()] 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 RTSpace(ref_el, deg): """Constructs a basis for the the Raviart-Thomas space (P_k)^d + P_k x""" sd = ref_el.get_spatial_dimension() vec_Pkp1 = ONPolynomialSet(ref_el, deg + 1, (sd, )) dimPkp1 = expansions.polynomial_dimension(ref_el, deg + 1) dimPk = expansions.polynomial_dimension(ref_el, deg) dimPkm1 = expansions.polynomial_dimension(ref_el, deg - 1) vec_Pk_indices = list( chain(*(range(i * dimPkp1, i * dimPkp1 + dimPk) for i in range(sd)))) vec_Pk_from_Pkp1 = vec_Pkp1.take(vec_Pk_indices) Pkp1 = ONPolynomialSet(ref_el, deg + 1) PkH = Pkp1.take(list(range(dimPkm1, dimPk))) Q = quadrature.make_quadrature(ref_el, 2 * deg + 2) # have to work on this through "tabulate" interface # first, tabulate PkH at quadrature points Qpts = np.array(Q.get_points()) Qwts = np.array(Q.get_weights()) zero_index = tuple([0 for i in range(sd)]) PkH_at_Qpts = PkH.tabulate(Qpts)[zero_index] Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[zero_index] PkHx_coeffs = np.zeros((PkH.get_num_members(), sd, Pkp1.get_num_members()), "d") for i in range(PkH.get_num_members()): for j in range(sd): fooij = PkH_at_Qpts[i, :] * Qpts[:, j] * Qwts PkHx_coeffs[i, j, :] = np.dot(Pkp1_at_Qpts, fooij) PkHx = PolynomialSet(ref_el, deg, deg + 1, vec_Pkp1.get_expansion_set(), PkHx_coeffs, vec_Pkp1.get_dmats()) return polynomial_set_union_normalized(vec_Pk_from_Pkp1, PkHx)
def BDFMSpace(ref_el, order): sd = ref_el.get_spatial_dimension() if sd != 2: raise Exception("BDFM_k elements only valid for dim 2") # Note that order will be 2. # Linear vector valued space. Since the embedding degree of this element # is 2, this is implemented by taking the quadratic space and selecting # the linear polynomials. vec_poly_set = ONPolynomialSet(ref_el, order, (sd, )) # Linears are the first three polynomials in each dimension. vec_poly_set = vec_poly_set.take([0, 1, 2, 6, 7, 8]) # Scalar quadratic Lagrange element. lagrange_ele = lagrange.Lagrange(ref_el, order) # Select the dofs associated with the edges. edge_dofs_dict = lagrange_ele.dual.get_entity_ids()[sd - 1] edge_dofs = np.array([(edge, dof) for edge, dofs in list(edge_dofs_dict.items()) for dof in dofs]) tangent_polys = lagrange_ele.poly_set.take(edge_dofs[:, 1]) new_coeffs = np.zeros( (tangent_polys.get_num_members(), sd, tangent_polys.coeffs.shape[-1])) # Outer product of the tangent vectors with the quadratic edge polynomials. for i, (edge, dof) in enumerate(edge_dofs): tangent = ref_el.compute_edge_tangent(edge) new_coeffs[i, :, :] = np.outer(tangent, tangent_polys.coeffs[i, :]) bubble_set = PolynomialSet(ref_el, order, order, vec_poly_set.get_expansion_set(), new_coeffs, vec_poly_set.get_dmats()) element_set = polynomial_set_union_normalized(bubble_set, vec_poly_set) return element_set
class CiarletElement(FiniteElement): """Class implementing Ciarlet's abstraction of a finite element being a domain, function space, and set of nodes. Elements derived from this class are nodal finite elements, with a nodal basis generated from polynomials encoded in a `PolynomialSet`. """ def __init__(self, poly_set, dual, order, formdegree=None, mapping="affine", ref_el=None): ref_el = ref_el or poly_set.get_reference_element() super().__init__(ref_el, dual, order, formdegree, mapping) # build generalized Vandermonde matrix old_coeffs = poly_set.get_coeffs() dualmat = dual.to_riesz(poly_set) shp = dualmat.shape if len(shp) > 2: num_cols = np.prod(shp[1:]) A = np.reshape(dualmat, (dualmat.shape[0], num_cols)) B = np.reshape(old_coeffs, (old_coeffs.shape[0], num_cols)) else: A = dualmat B = old_coeffs V = np.dot(A, np.transpose(B)) self.V = V Vinv = np.linalg.inv(V) new_coeffs_flat = np.dot(np.transpose(Vinv), B) new_shp = tuple([new_coeffs_flat.shape[0]] + list(shp[1:])) new_coeffs = np.reshape(new_coeffs_flat, new_shp) self.poly_set = PolynomialSet(ref_el, poly_set.get_degree(), poly_set.get_embedded_degree(), poly_set.get_expansion_set(), new_coeffs, poly_set.get_dmats()) def degree(self): "Return the degree of the (embedding) polynomial space." return self.poly_set.get_embedded_degree() def get_nodal_basis(self): """Return the nodal basis, encoded as a PolynomialSet object, for the finite element.""" return self.poly_set def get_coeffs(self): """Return the expansion coefficients for the basis of the finite element.""" return self.poly_set.get_coeffs() def tabulate(self, order, points, entity=None): """Return tabulated values of derivatives up to given order of basis functions at given points. :arg order: The maximum order of derivative. :arg points: An iterable of points. :arg entity: Optional (dimension, entity number) pair indicating which topological entity of the reference element to tabulate on. If ``None``, default cell-wise tabulation is performed. """ if entity is None: entity = (self.ref_el.get_spatial_dimension(), 0) entity_dim, entity_id = entity transform = self.ref_el.get_entity_transform(entity_dim, entity_id) return self.poly_set.tabulate(list(map(transform, points)), order) def value_shape(self): "Return the value shape of the finite element functions." return self.poly_set.get_shape() def dmats(self): """Return dmats: expansion coefficients for basis function derivatives.""" return self.get_nodal_basis().get_dmats() def get_num_members(self, arg): "Return number of members of the expansion set." return self.get_nodal_basis().get_expansion_set().get_num_members(arg) @staticmethod def is_nodal(): """True if primal and dual bases are orthogonal. If false, dual basis is not implemented or is undefined. All implementations/subclasses are nodal including this one. """ return True
def NedelecSpace3D(ref_el, k): """Constructs a nodal basis for the 3d first-kind Nedelec space""" sd = ref_el.get_spatial_dimension() if sd != 3: raise Exception("NedelecSpace3D requires 3d reference element") vec_Pkp1 = ONPolynomialSet(ref_el, k + 1, (sd, )) dimPkp1 = expansions.polynomial_dimension(ref_el, k + 1) dimPk = expansions.polynomial_dimension(ref_el, k) if k > 0: dimPkm1 = expansions.polynomial_dimension(ref_el, k - 1) else: dimPkm1 = 0 vec_Pk_indices = list( chain(*(range(i * dimPkp1, i * dimPkp1 + dimPk) for i in range(sd)))) vec_Pk = vec_Pkp1.take(vec_Pk_indices) vec_Pke_indices = list( chain(*(range(i * dimPkp1 + dimPkm1, i * dimPkp1 + dimPk) for i in range(sd)))) vec_Pke = vec_Pkp1.take(vec_Pke_indices) Pkp1 = ONPolynomialSet(ref_el, k + 1) Q = quadrature.make_quadrature(ref_el, 2 * (k + 1)) Qpts = np.array(Q.get_points()) Qwts = np.array(Q.get_weights()) zero_index = tuple([0 for i in range(sd)]) PkCrossXcoeffs = np.zeros( (vec_Pke.get_num_members(), sd, Pkp1.get_num_members()), "d") Pke_qpts = vec_Pke.tabulate(Qpts)[zero_index] Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[zero_index] for i in range(vec_Pke.get_num_members()): for j in range(sd): # vector components qwts_cur_bf_val = ( Qpts[:, (j + 2) % 3] * Pke_qpts[i, (j + 1) % 3, :] - Qpts[:, (j + 1) % 3] * Pke_qpts[i, (j + 2) % 3, :]) * Qwts PkCrossXcoeffs[i, j, :] = np.dot(Pkp1_at_Qpts, qwts_cur_bf_val) # for k in range( Pkp1.get_num_members() ): # PkCrossXcoeffs[i,j,k] = sum( Qwts * cur_bf_val * Pkp1_at_Qpts[k,:] ) # for l in range( len( Qpts ) ): # cur_bf_val = Qpts[l][(j+2)%3] \ # * Pke_qpts[i,(j+1)%3,l] \ # - Qpts[l][(j+1)%3] \ # * Pke_qpts[i,(j+2)%3,l] # PkCrossXcoeffs[i,j,k] += Qwts[l] \ # * cur_bf_val \ # * Pkp1_at_Qpts[k,l] PkCrossX = PolynomialSet(ref_el, k + 1, k + 1, vec_Pkp1.get_expansion_set(), PkCrossXcoeffs, vec_Pkp1.get_dmats()) return polynomial_set_union_normalized(vec_Pk, PkCrossX)