def __init__(self, ref_el, degree): # Initialise data structures topology = ref_el.get_topology() entity_ids = { dim: {entity_i: [] for entity_i in entities} for dim, entities in topology.items() } # Calculate inverse topology inverse_topology = { vertices: (dim, entity_i) for dim, entities in topology.items() for entity_i, vertices in entities.items() } # Generate triangular barycentric indices dim = ref_el.get_spatial_dimension() kss = mis(dim + 1, degree) # Fill data structures nodes = [] for i, ks in enumerate(kss): vertices, = numpy.nonzero(ks) entity_dim, entity_i = inverse_topology[tuple(vertices)] entity_ids[entity_dim][entity_i].append(i) # Leave nodes unimplemented for now nodes.append(None) super(BernsteinDualSet, self).__init__(nodes, ref_el, entity_ids)
def __init__(self, ref_el, degree): nodes = [] dim = ref_el.get_spatial_dimension() Q = quadrature.make_quadrature(ref_el, 2 * (degree + 1)) f_at_qpts = numpy.ones(len(Q.wts)) nodes.append(functional.IntegralMoment(ref_el, Q, f_at_qpts)) vertices = ref_el.get_vertices() midpoint = tuple(sum(numpy.array(vertices)) / len(vertices)) for k in range(1, degree + 1): # Loop over all multi-indices of degree k. for alpha in mis(dim, k): nodes.append( functional.PointDerivative(ref_el, midpoint, alpha)) entity_ids = { d: {e: [] for e in ref_el.sub_entities[d]} for d in range(dim + 1) } entity_ids[dim][0] = list(range(len(nodes))) super(DiscontinuousTaylorDualSet, self).__init__(nodes, ref_el, entity_ids)
def basis_evaluation(self, order, ps, entity=None, coordinate_mapping=None): """Return code for evaluating the element at known points on the reference element. :param order: return derivatives up to this order. :param ps: the point set object. :param entity: the cell entity on which to tabulate. """ # Spatial dimension dimension = self.cell.get_spatial_dimension() # Shape of the tabulation matrix shape = tuple(index.extent for index in ps.indices) + self.index_shape + self.value_shape result = {} for derivative in range(order + 1): for alpha in mis(dimension, derivative): name = str.format("rt_{}_{}_{}_{}_{}_{}", self.variant, self.degree, ''.join(map(str, alpha)), self.shift_axes, 'c' if self.continuous else 'd', {None: "", '+': "p", '-': "m"}[self.restriction]) result[alpha] = gem.partial_indexed(gem.Variable(name, shape), ps.indices) return result
def tabulate(self, order, points, entity=None): if entity is None: entity = (self.ref_el.get_dimension(), 0) entity_dim, entity_id = entity transform = self.ref_el.get_entity_transform(entity_dim, entity_id) points = list(map(transform, points)) phivals = {} for o in range(order + 1): alphas = mis(self.fdim, o) for alpha in alphas: try: polynomials = self.basis[alpha] except KeyError: zr = tuple([0] * self.fdim) polynomials = diff(self.basis[zr], *zip(variables, alpha)) self.basis[alpha] = polynomials T = np.zeros((len(polynomials[:, 0]), self.fdim, len(points))) for i in range(len(points)): subs = { v: points[i][k] for k, v in enumerate(variables[:self.fdim]) } for ell in range(self.fdim): for j, f in enumerate(polynomials[:, ell]): T[j, ell, i] = f.evalf(subs=subs) phivals[alpha] = T return phivals
def __init__(self, ref_el, degree): # Initialise data structures topology = ref_el.get_topology() entity_ids = {dim: {entity_i: [] for entity_i in entities} for dim, entities in topology.items()} # Calculate inverse topology inverse_topology = {vertices: (dim, entity_i) for dim, entities in topology.items() for entity_i, vertices in entities.items()} # Generate triangular barycentric indices dim = ref_el.get_spatial_dimension() kss = mis(dim + 1, degree) # Fill data structures nodes = [] for i, ks in enumerate(kss): vertices, = numpy.nonzero(ks) entity_dim, entity_i = inverse_topology[tuple(vertices)] entity_ids[entity_dim][entity_i].append(i) # Leave nodes unimplemented for now nodes.append(None) super(BernsteinDualSet, self).__init__(nodes, ref_el, entity_ids)
def basis_evaluation(self, order, ps, entity=None): """Return code for evaluating the element at known points on the reference element. :param order: return derivatives up to this order. :param ps: the point set object. :param entity: the cell entity on which to tabulate. """ # Spatial dimension dimension = self.cell.get_spatial_dimension() # Shape of the tabulation matrix shape = tuple( index.extent for index in ps.indices) + self.index_shape + self.value_shape result = {} for derivative in range(order + 1): for alpha in mis(dimension, derivative): name = str.format("rt_{}_{}_{}_{}_{}_{}", self.variant, self.degree, ''.join(map(str, alpha)), self.shift_axes, 'c' if self.continuous else 'd', { None: "", '+': "p", '-': "m" }[self.restriction]) result[alpha] = gem.partial_indexed(gem.Variable(name, shape), ps.indices) return result
def tabulate(self, order, points, entity=None): 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) points = list(map(transform, points)) phivals = {} dim = self.flat_el.get_spatial_dimension() if dim <= 1: raise NotImplementedError('no tabulate method for serendipity elements of dimension 1 or less.') if dim >= 4: raise NotImplementedError('tabulate does not support higher dimensions than 3.') for o in range(order + 1): alphas = mis(dim, o) for alpha in alphas: try: polynomials = self.basis[alpha] except KeyError: polynomials = diff(self.basis[(0,)*dim], *zip(variables, alpha)) self.basis[alpha] = polynomials T = np.zeros((len(polynomials), len(points))) for i in range(len(points)): subs = {v: points[i][k] for k, v in enumerate(variables[:dim])} for j, f in enumerate(polynomials): T[j, i] = f.evalf(subs=subs) phivals[alpha] = T return phivals
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. """ # Transform points to reference cell coordinates ref_el = self.get_reference_element() if entity is None: entity = (ref_el.get_spatial_dimension(), 0) entity_dim, entity_id = entity entity_transform = ref_el.get_entity_transform(entity_dim, entity_id) cell_points = list(map(entity_transform, points)) # Construct Cartesian to Barycentric coordinate mapping vs = numpy.asarray(ref_el.get_vertices()) B2R = numpy.vstack([vs.T, numpy.ones(len(vs))]) R2B = numpy.linalg.inv(B2R) B = numpy.hstack([cell_points, numpy.ones((len(cell_points), 1))]).dot(R2B.T) # Evaluate everything deg = self.degree() dim = ref_el.get_spatial_dimension() raw_result = {(alpha, i): vec for i, ks in enumerate(mis(dim + 1, deg)) for o in range(order + 1) for alpha, vec in bernstein_Dx(B, ks, o, R2B).items()} # Rearrange result space_dim = self.space_dimension() dtype = numpy.array(list(raw_result.values())).dtype result = { alpha: numpy.zeros((space_dim, len(cell_points)), dtype=dtype) for o in range(order + 1) for alpha in mis(dim, o) } for (alpha, i), vec in raw_result.items(): result[alpha][i, :] = vec return result
def point_evaluation_ciarlet(fiat_element, order, refcoords, entity): # Coordinates on the reference entity (SymPy) esd, = refcoords.shape Xi = sp.symbols('X Y Z')[:esd] # Coordinates on the reference cell cell = fiat_element.get_reference_element() X = cell.get_entity_transform(*entity)(Xi) # Evaluate expansion set at SymPy point poly_set = fiat_element.get_nodal_basis() degree = poly_set.get_embedded_degree() base_values = poly_set.get_expansion_set().tabulate(degree, [X]) m = len(base_values) assert base_values.shape == (m, 1) base_values_sympy = np.array(list(base_values.flat)) # Find constant polynomials def is_const(expr): try: float(expr) return True except TypeError: return False const_mask = np.array(list(map(is_const, base_values_sympy))) # Convert SymPy expression to GEM mapper = gem.node.Memoizer(sympy2gem) mapper.bindings = { s: gem.Indexed(refcoords, (i, )) for i, s in enumerate(Xi) } base_values = gem.ListTensor(list(map(mapper, base_values.flat))) # Populate result dict, creating precomputed coefficient # matrices for each derivative tuple. result = {} for i in range(order + 1): for alpha in mis(cell.get_spatial_dimension(), i): D = form_matrix_product(poly_set.get_dmats(), alpha) table = np.dot(poly_set.get_coeffs(), np.transpose(D)) assert table.shape[-1] == m zerocols = np.isclose( abs(table).max(axis=tuple(range(table.ndim - 1))), 0.0) if all(np.logical_or(const_mask, zerocols)): # Casting is safe by assertion of is_const vals = base_values_sympy[const_mask].astype(np.float64) result[alpha] = gem.Literal(table[..., const_mask].dot(vals)) else: beta = tuple(gem.Index() for s in table.shape[:-1]) k = gem.Index() result[alpha] = gem.ComponentTensor( gem.IndexSum( gem.Product( gem.Indexed(gem.Literal(table), beta + (k, )), gem.Indexed(base_values, (k, ))), (k, )), beta) return result
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. """ # Transform points to reference cell coordinates ref_el = self.get_reference_element() if entity is None: entity = (ref_el.get_spatial_dimension(), 0) entity_dim, entity_id = entity entity_transform = ref_el.get_entity_transform(entity_dim, entity_id) cell_points = list(map(entity_transform, points)) # Construct Cartesian to Barycentric coordinate mapping vs = numpy.asarray(ref_el.get_vertices()) B2R = numpy.vstack([vs.T, numpy.ones(len(vs))]) R2B = numpy.linalg.inv(B2R) B = numpy.hstack([cell_points, numpy.ones((len(cell_points), 1))]).dot(R2B.T) # Evaluate everything deg = self.degree() dim = ref_el.get_spatial_dimension() raw_result = {(alpha, i): vec for i, ks in enumerate(mis(dim + 1, deg)) for o in range(order + 1) for alpha, vec in bernstein_Dx(B, ks, o, R2B).items()} # Rearrange result space_dim = self.space_dimension() dtype = numpy.array(list(raw_result.values())).dtype result = {alpha: numpy.zeros((space_dim, len(cell_points)), dtype=dtype) for o in range(order + 1) for alpha in mis(dim, o)} for (alpha, i), vec in raw_result.items(): result[alpha][i, :] = vec return result
def bernstein_Dx(points, ks, order, R2B): """Evaluates Bernstein polynomials or its derivatives according to reference coordinates. :arg points: array of points in BARYCENTRIC COORDINATES :arg ks: exponents defining the Bernstein polynomial :arg alpha: derivative order (returns all derivatives of this specified order) :arg R2B: linear mapping from reference to barycentric coordinates :returns: dictionary mapping from derivative tuples to arrays of Bernstein polynomial values at given points. """ points = numpy.asarray(points) ks = tuple(ks) N, d_1 = points.shape assert d_1 == len(ks) # Collect derivatives according to barycentric coordinates Db_map = { alpha: bernstein_db(points, ks, alpha) for alpha in mis(d_1, order) } # Arrange derivative tensor (barycentric coordinates) dtype = numpy.array(list(Db_map.values())).dtype Db_shape = (d_1, ) * order Db_tensor = numpy.empty(Db_shape + (N, ), dtype=dtype) for ds in numpy.ndindex(Db_shape): alpha = [0] * d_1 for d in ds: alpha[d] += 1 Db_tensor[ds + (slice(None), )] = Db_map[tuple(alpha)] # Coordinate transformation: barycentric -> reference result = {} for alpha in mis(d_1 - 1, order): values = Db_tensor for d, k in enumerate(alpha): for _ in range(k): values = R2B[:, d].dot(values) result[alpha] = values return result
def make_entity_permutations(dim, npoints): r"""Make orientation-permutation map for the given simplex dimension, dim, and the number of points along each axis As an example, we first compute the orientation of a triangular cell: + + | \ | \ 1 0 47 42 | \ | \ +--2---+ +--43--+ FIAT canonical Mapped example physical cell Suppose that the facets of the physical cell are canonically ordered as: C = [43, 42, 47] FIAT facet to Physical facet map is given by: M = [42, 47, 43] Then the orientation of the cell is computed as: C.index(M[0]) = 1; C.remove(M[0]) C.index(M[1]) = 1; C.remove(M[1]) C.index(M[2]) = 0; C.remove(M[2]) o = (1 * 2!) + (1 * 1!) + (0 * 0!) = 3 For npoints = 3, there are 6 DoFs: 5 0 3 4 1 3 0 1 2 2 4 5 FIAT canonical Physical cell canonical The permutation associated with o = 3 then is: [2, 4, 5, 1, 3, 0] The output of this function contains one such permutation for each orientation for the given simplex dimension and the number of points along each axis. """ if npoints <= 0: return {o: [] for o in range(np.math.factorial(dim + 1))} a = np.array(sorted(mis(dim + 1, npoints - 1)), dtype=int)[:, ::-1] index_perms = sorted(itertools.permutations(range(dim + 1))) perms = {} for o, index_perm in enumerate(index_perms): perm = np.lexsort(np.transpose(a[:, index_perm])) perms[o] = perm.tolist() return perms
def point_evaluation_ciarlet(fiat_element, order, refcoords, entity): # Coordinates on the reference entity (SymPy) esd, = refcoords.shape Xi = sp.symbols('X Y Z')[:esd] # Coordinates on the reference cell cell = fiat_element.get_reference_element() X = cell.get_entity_transform(*entity)(Xi) # Evaluate expansion set at SymPy point poly_set = fiat_element.get_nodal_basis() degree = poly_set.get_embedded_degree() base_values = poly_set.get_expansion_set().tabulate(degree, [X]) m = len(base_values) assert base_values.shape == (m, 1) base_values_sympy = np.array(list(base_values.flat)) # Find constant polynomials def is_const(expr): try: float(expr) return True except TypeError: return False const_mask = np.array(list(map(is_const, base_values_sympy))) # Convert SymPy expression to GEM mapper = gem.node.Memoizer(sympy2gem) mapper.bindings = {s: gem.Indexed(refcoords, (i,)) for i, s in enumerate(Xi)} base_values = gem.ListTensor(list(map(mapper, base_values.flat))) # Populate result dict, creating precomputed coefficient # matrices for each derivative tuple. result = {} for i in range(order + 1): for alpha in mis(cell.get_spatial_dimension(), i): D = form_matrix_product(poly_set.get_dmats(), alpha) table = np.dot(poly_set.get_coeffs(), np.transpose(D)) assert table.shape[-1] == m zerocols = np.isclose(abs(table).max(axis=tuple(range(table.ndim - 1))), 0.0) if all(np.logical_or(const_mask, zerocols)): vals = base_values_sympy[const_mask] result[alpha] = gem.Literal(table[..., const_mask].dot(vals)) else: beta = tuple(gem.Index() for s in table.shape[:-1]) k = gem.Index() result[alpha] = gem.ComponentTensor( gem.IndexSum( gem.Product(gem.Indexed(gem.Literal(table), beta + (k,)), gem.Indexed(base_values, (k,))), (k,) ), beta ) return result
def bernstein_Dx(points, ks, order, R2B): """Evaluates Bernstein polynomials or its derivatives according to reference coordinates. :arg points: array of points in BARYCENTRIC COORDINATES :arg ks: exponents defining the Bernstein polynomial :arg alpha: derivative order (returns all derivatives of this specified order) :arg R2B: linear mapping from reference to barycentric coordinates :returns: dictionary mapping from derivative tuples to arrays of Bernstein polynomial values at given points. """ points = numpy.asarray(points) ks = tuple(ks) N, d_1 = points.shape assert d_1 == len(ks) # Collect derivatives according to barycentric coordinates Db_map = {alpha: bernstein_db(points, ks, alpha) for alpha in mis(d_1, order)} # Arrange derivative tensor (barycentric coordinates) dtype = numpy.array(list(Db_map.values())).dtype Db_shape = (d_1,) * order Db_tensor = numpy.empty(Db_shape + (N,), dtype=dtype) for ds in numpy.ndindex(Db_shape): alpha = [0] * d_1 for d in ds: alpha[d] += 1 Db_tensor[ds + (slice(None),)] = Db_map[tuple(alpha)] # Coordinate transformation: barycentric -> reference result = {} for alpha in mis(d_1 - 1, order): values = Db_tensor for d, k in enumerate(alpha): for _ in range(k): values = R2B[:, d].dot(values) result[alpha] = values return result
def basis_evaluation(self, order, ps, entity=None): # Default entity if entity is None: entity = (self.cell.get_dimension(), 0) entity_dim, entity_id = entity # Factor entity assert isinstance(entity_dim, tuple) assert len(entity_dim) == len(self.factors) shape = tuple( len(c.get_topology()[d]) for c, d in zip(self.cell.cells, entity_dim)) entities = list(zip(entity_dim, numpy.unravel_index(entity_id, shape))) # Factor point set ps_factors = factor_point_set(self.cell, entity_dim, ps) # Subelement results factor_results = [ fe.basis_evaluation(order, ps_, e) for fe, ps_, e in zip(self.factors, ps_factors, entities) ] # Spatial dimension dimension = self.cell.get_spatial_dimension() # A list of slices that are used to select dimensions # corresponding to each subelement. dim_slices = TensorProductCell._split_slices( [c.get_spatial_dimension() for c in self.cell.cells]) # A list of multiindices, one multiindex per subelement, each # multiindex describing the shape of basis functions of the # subelement. alphas = [fe.get_indices() for fe in self.factors] result = {} for derivative in range(order + 1): for Delta in mis(dimension, derivative): # Split the multiindex for the subelements deltas = [Delta[s] for s in dim_slices] # GEM scalars (can have free indices) for collecting # the contributions from the subelements. scalars = [] for fr, delta, alpha in zip(factor_results, deltas, alphas): # Turn basis shape to free indices, select the # right derivative entry, and collect the result. scalars.append(gem.Indexed(fr[delta], alpha)) # Multiply the values from the subelements and wrap up # non-point indices into shape. result[Delta] = gem.ComponentTensor( reduce(gem.Product, scalars), tuple(chain(*alphas))) return result
def restrict_tpe(element, domain, take_closure): # The restriction of a TPE to a codim subentity is the direct sum # of TPEs where the factors have been restricted in such a way # that the sum of those restrictions is codim. # # For example, to restrict an interval x interval to edges (codim 1) # we construct # # R(I, 0)⊗R(I, 1) ⊕ R(I, 1)⊗R(I, 0) # # If take_closure is true, the restriction wants to select dofs on # entities with dim >= codim >= 1 (for the edge example) # so we get # # R(I, 0)⊗R(I, 1) ⊕ R(I, 1)⊗R(I, 0) ⊕ R(I, 0)⊗R(I, 0) factors = element.factors dimension = element.cell.get_spatial_dimension() # Figure out which codim entity we're selecting codim = r_to_codim(domain, dimension) # And the range of codims. upper = 1 + (dimension if (take_closure and domain != "interior") else codim) # restrictions on each factor taken from n-tuple that sums to the # target codim (as long as the codim <= dim_factor) restrictions = tuple( candidate for candidate in chain(*(mis(len(factors), c) for c in range(codim, upper))) if all(d <= factor.cell.get_dimension() for d, factor in zip(candidate, factors))) elements = [] for decomposition in restrictions: # Recurse, but don't take closure in recursion (since we # handled it already). new_factors = tuple( restrict(factor, codim_to_r(codim, factor.cell.get_dimension()), take_closure=False) for factor, codim in zip(factors, decomposition)) # If one of the factors was empty then the whole TPE is empty, # so skip. if all(f is not null_element for f in new_factors): elements.append(finat.TensorProductElement(new_factors)) if elements: return finat.EnrichedElement(elements) else: return null_element
def __init__(self, ref_el, degree): nodes = [] dim = ref_el.get_spatial_dimension() Q = quadrature.make_quadrature(ref_el, 2 * (degree + 1)) f_at_qpts = numpy.ones(len(Q.wts)) nodes.append(functional.IntegralMoment(ref_el, Q, f_at_qpts)) vertices = ref_el.get_vertices() midpoint = tuple(sum(numpy.array(vertices)) / len(vertices)) for k in range(1, degree + 1): # Loop over all multi-indices of degree k. for alpha in mis(dim, k): nodes.append(functional.PointDerivative(ref_el, midpoint, alpha)) entity_ids = {d: {e: [] for e in ref_el.sub_entities[d]} for d in range(dim + 1)} entity_ids[dim][0] = list(range(len(nodes))) super(DiscontinuousTaylorDualSet, self).__init__(nodes, ref_el, entity_ids)
def _merge_evaluations(self, factor_results): # Spatial dimension dimension = self.cell.get_spatial_dimension() # Derivative order order = max(map(sum, chain(*factor_results))) # A list of slices that are used to select dimensions # corresponding to each subelement. dim_slices = TensorProductCell._split_slices([c.get_spatial_dimension() for c in self.cell.cells]) # A list of multiindices, one multiindex per subelement, each # multiindex describing the shape of basis functions of the # subelement. alphas = [fe.get_indices() for fe in self.factors] # A list of multiindices, one multiindex per subelement, each # multiindex describing the value shape of the subelement. zetas = [fe.get_value_indices() for fe in self.factors] result = {} for derivative in range(order + 1): for Delta in mis(dimension, derivative): # Split the multiindex for the subelements deltas = [Delta[s] for s in dim_slices] # GEM scalars (can have free indices) for collecting # the contributions from the subelements. scalars = [] for fr, delta, alpha, zeta in zip(factor_results, deltas, alphas, zetas): # Turn basis shape to free indices, select the # right derivative entry, and collect the result. scalars.append(gem.Indexed(fr[delta], alpha + zeta)) # Multiply the values from the subelements and wrap up # non-point indices into shape. result[Delta] = gem.ComponentTensor( reduce(gem.Product, scalars), tuple(chain(*(alphas + zetas))) ) return result
def _merge_evaluations(self, factor_results): # Spatial dimension dimension = self.cell.get_spatial_dimension() # Derivative order order = max(map(sum, chain(*factor_results))) # A list of slices that are used to select dimensions # corresponding to each subelement. dim_slices = TensorProductCell._split_slices([c.get_spatial_dimension() for c in self.cell.cells]) # A list of multiindices, one multiindex per subelement, each # multiindex describing the shape of basis functions of the # subelement. alphas = [fe.get_indices() for fe in self.factors] # A list of multiindices, one multiindex per subelement, each # multiindex describing the value shape of the subelement. zetas = [fe.get_value_indices() for fe in self.factors] result = {} for derivative in range(order + 1): for Delta in mis(dimension, derivative): # Split the multiindex for the subelements deltas = [Delta[s] for s in dim_slices] # GEM scalars (can have free indices) for collecting # the contributions from the subelements. scalars = [] for fr, delta, alpha, zeta in zip(factor_results, deltas, alphas, zetas): # Turn basis shape to free indices, select the # right derivative entry, and collect the result. scalars.append(gem.Indexed(fr[delta], alpha + zeta)) # Multiply the values from the subelements and wrap up # non-point indices into shape. result[Delta] = gem.ComponentTensor( reduce(gem.Product, scalars), tuple(chain(*(alphas + zetas))) ) return result
def basis_evaluation(self, order, ps, entity=None, coordinate_mapping=None): '''Return code for evaluating the element at known points on the reference element. :param order: return derivatives up to this order. :param ps: the point set. :param entity: the cell entity on which to tabulate. ''' # Build everything in sympy vs, xx, _ = self._basis # and convert -- all this can be used for each derivative! phys_verts = coordinate_mapping.physical_vertices() phys_points = gem.partial_indexed( coordinate_mapping.physical_points(ps, entity=entity), ps.indices) repl = dict( (vs[idx], phys_verts[idx]) for idx in numpy.ndindex(vs.shape)) repl.update(zip(xx, phys_points)) mapper = gem.node.Memoizer(sympy2gem) mapper.bindings = repl result = {} for i in range(order + 1): alphas = mis(2, i) for alpha in alphas: dphis = self._basis_deriv(xx, alpha) result[alpha] = gem.ListTensor(list(map(mapper, dphis))) return result
def tabulate(self, order, points, entity=None): 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) points = list(map(transform, points)) phivals = {} dim = self.flat_el.get_spatial_dimension() if dim <= 1: raise NotImplementedError( 'no tabulate method for serendipity elements of dimension 1 or less.' ) if dim >= 4: raise NotImplementedError( 'tabulate does not support higher dimensions than 3.') for o in range(order + 1): alphas = mis(dim, o) for alpha in alphas: try: callable = self.basis_callable[alpha] except KeyError: polynomials = diff(self.basis[(0, ) * dim], *zip(variables, alpha)) callable = lambdify(variables[:dim], polynomials, modules="numpy", dummify=True) self.basis[alpha] = polynomials self.basis_callable[alpha] = callable points = np.asarray(points) T = np.asarray( callable(*(points[:, i] for i in range(points.shape[1])))) phivals[alpha] = T return phivals
import numpy from FIAT.polynomial_set import mis from FIAT.reference_element import default_simplex from FIAT.quadrature import make_quadrature order = 1 quadrature = make_quadrature(default_simplex(1), order) from FIAT.lagrange import Lagrange degree = 1 element = Lagrange(default_simplex(1), degree) vertices = [n.get_point_dict().keys()[0] for n in element.dual.get_nodes()] quadpts = numpy.array(quadrature.get_points(), dtype=numpy.float64) quadwts = numpy.array(quadrature.get_weights(), dtype=numpy.float64) numQuadPts = len(quadpts) evals = element.get_nodal_basis().tabulate(quadrature.get_points(), 1) basis = numpy.array(evals[mis(1, 0)[0]], dtype=numpy.float64).transpose() numBasis = element.get_nodal_basis().get_num_members() basisDeriv = numpy.array([evals[alpha] for alpha in mis(1, 1)], dtype=numpy.float64).transpose() print "order: %d" % order print "degree: %d" % degree print "numQuadPts: %d" % numQuadPts print "basis:" print basis print "basisDeriv:" print basisDeriv
def tabulate(self, order, points, entity=None): """Return tabulated values of derivatives up to a 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``, tabulated values are computed by geometrically approximating which facet the points are on. .. note :: Performing illegal tabulations on this element will result in either a tabulation table of `numpy.nan` arrays (`entity=None` case), or insertions of the `TraceError` exception class. This is due to the fact that performing cell-wise tabulations, or asking for any order of derivative evaluations, are not mathematically well-defined. """ sd = self.ref_el.get_spatial_dimension() facet_sd = sd - 1 # Initializing dictionary with zeros phivals = {} for i in range(order + 1): alphas = mis(sd, i) for alpha in alphas: phivals[alpha] = np.zeros(shape=(self.space_dimension(), len(points))) evalkey = (0,) * sd # If entity is None, identify facet using numerical tolerance and # return the tabulated values if entity is None: # NOTE: Numerical approximation of the facet id is currently only # implemented for simplex reference cells. if self.ref_el.get_shape() not in [TRIANGLE, TETRAHEDRON]: raise NotImplementedError( "Tabulating this element on a %s cell without providing " "an entity is not currently supported." % type(self.ref_el) ) # Attempt to identify which facet (if any) the given points are on vertices = self.ref_el.vertices coordinates = barycentric_coordinates(points, vertices) unique_facet, success = extract_unique_facet(coordinates) # If not successful, return NaNs if not success: for key in phivals: phivals[key] = np.full(shape=(self.space_dimension(), len(points)), fill_value=np.nan) return phivals # Otherwise, extract non-zero values and insertion indices else: # Map points to the reference facet new_points = map_to_reference_facet(points, vertices, unique_facet) # Retrieve values by tabulating the DG element element = self.dg_elements[facet_sd] nf = element.space_dimension() nonzerovals, = element.tabulate(order, new_points).values() indices = slice(nf * unique_facet, nf * (unique_facet + 1)) else: entity_dim, _ = entity # If the user is directly specifying cell-wise tabulation, return # TraceErrors in dict for appropriate handling in the form compiler if entity_dim not in self.dg_elements: for key in phivals: msg = "The HDivTrace element can only be tabulated on facets." phivals[key] = TraceError(msg) return phivals else: # Retrieve function evaluations (order = 0 case) offset = 0 for facet_dim in sorted(self.dg_elements): element = self.dg_elements[facet_dim] nf = element.space_dimension() num_facets = len(self.ref_el.get_topology()[facet_dim]) # Loop over the number of facets until we find a facet # with matching dimension and id for i in range(num_facets): # Found it! Grab insertion indices if (facet_dim, i) == entity: nonzerovals, = element.tabulate(0, points).values() indices = slice(offset, offset + nf) offset += nf # If asking for gradient evaluations, insert TraceError in # gradient slots if order > 0: msg = "Gradients on trace elements are not well-defined." for key in phivals: if key != evalkey: phivals[key] = TraceError(msg) # Insert non-zero values in appropriate place phivals[evalkey][indices, :] = nonzerovals return phivals
def initialize(self, spaceDim): """ Initialize reference finite-element cell from a tensor product of 1-D Lagrange elements. """ self._setupGeometry(spaceDim) if self.cellDim > 0: quadrature = self._setupQuadrature() element = self._setupElement() dim = self.cellDim # Get coordinates of vertices (dual basis) vertices = numpy.array(self._setupVertices(element), dtype=numpy.float64) # Evaluate basis functions at quadrature points from FIAT.polynomial_set import mis quadpts = numpy.array(quadrature.get_points(), dtype=numpy.float64) quadwts = numpy.array(quadrature.get_weights(), dtype=numpy.float64) numQuadPts = len(quadpts) evals = element.get_nodal_basis().tabulate(quadrature.get_points(), 1) basis = numpy.array(evals[mis(1, 0)[0]], dtype=numpy.float64).transpose() numBasis = element.get_nodal_basis().get_num_members() # Evaluate derivatives of basis functions at quadrature points basisDeriv = numpy.array([evals[alpha] for alpha in mis(1, 1)], dtype=numpy.float64).transpose() self.numQuadPts = numQuadPts**dim self.numCorners = numBasis**dim if dim == 1: # Set order of vertices and basis functions. # Corners vertexOrder = [0, 1] # Edges p = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p) self.vertices = numpy.zeros((self.numCorners, dim)) n = 0 for p in vertexOrder: self.vertices[n][0] = vertices[p] n += 1 if not n == self.numCorners: raise RuntimeError('Invalid 1-D vertex ordering: '+str(n)+ \ ' should be '+str(self.numCorners)) self.quadPts = numpy.zeros((numQuadPts, dim)) self.quadWts = numpy.zeros((numQuadPts,)) self.basis = numpy.zeros((numQuadPts, numBasis)) self.basisDeriv = numpy.zeros((numQuadPts, numBasis, dim)) # Order of quadrature points doesn't matter # Order of basis functions should match vertices for isoparametric n = 0 for p in range(0, numQuadPts): self.quadPts[n][0] = quadpts[p] self.quadWts[n] = quadwts[p] m = 0 for (bp) in vertexOrder: self.basis[n][m] = basis[p][bp] self.basisDeriv[n][m][0] = basisDeriv[p][bp][0] m += 1 if not m == self.numCorners: raise RuntimeError('Invalid 2-D basis tabulation: '+str(m)+ \ ' should be '+str(self.numCorners)) n += 1 if not n == self.numQuadPts: raise RuntimeError('Invalid 2-D quadrature: '+str(n)+ \ ' should be '+str(self.numQuadPts)) elif dim == 2: # Set order of vertices and basis functions. # Corners vertexOrder = [(0,0), (1,0), (1,1), (0,1)] # Edges # Bottom p = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.zeros(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q) # Right p = numpy.ones(numBasis-2, dtype=numpy.int32) q = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p,q) # Top p = numpy.arange(numBasis-1, 1, step=-1, dtype=numpy.int32) q = numpy.ones(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q) # Left p = numpy.zeros(numBasis-2, dtype=numpy.int32) q = numpy.arange(numBasis-1, 1, step=-1, dtype=numpy.int32) vertexOrder += zip(p,q) # Face p = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p,q) self.vertices = numpy.zeros((self.numCorners, dim)) n = 0 for (p,q) in vertexOrder: self.vertices[n][0] = vertices[p] self.vertices[n][1] = vertices[q] n += 1 if not n == self.numCorners: raise RuntimeError('Invalid 2-D vertex ordering: '+str(n)+ \ ' should be '+str(self.numCorners)) self.quadPts = numpy.zeros((numQuadPts*numQuadPts, dim)) self.quadWts = numpy.zeros((numQuadPts*numQuadPts,)) self.basis = numpy.zeros((numQuadPts*numQuadPts, numBasis*numBasis)) self.basisDeriv = numpy.zeros((numQuadPts*numQuadPts, numBasis*numBasis, dim)) # Order of quadrature points doesn't matter # Order of basis functions should match vertices for isoparametric n = 0 for q in range(0, numQuadPts): for p in range(0, numQuadPts): self.quadPts[n][0] = quadpts[p] self.quadPts[n][1] = quadpts[q] self.quadWts[n] = quadwts[p]*quadwts[q] m = 0 for (bp,bq) in vertexOrder: self.basis[n][m] = basis[p][bp]*basis[q][bq] self.basisDeriv[n][m][0] = basisDeriv[p][bp][0]*basis[q][bq] self.basisDeriv[n][m][1] = basis[p][bp]*basisDeriv[q][bq][0] m += 1 if not m == self.numCorners: raise RuntimeError('Invalid 2-D basis tabulation: '+str(m)+ \ ' should be '+str(self.numCorners)) n += 1 if not n == self.numQuadPts: raise RuntimeError('Invalid 2-D quadrature: '+str(n)+ \ ' should be '+str(self.numQuadPts)) elif dim == 3: # Set order of vertices and basis functions. # Corners vertexOrder = [(0,0,0), (0,1,0), (1,1,0), (1,0,0), (0,0,1), (1,0,1), (1,1,1), (0,1,1)] # Edges # Bottom front p = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.zeros(numBasis-2, dtype=numpy.int32) r = numpy.zeros(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Bottom right p = numpy.ones(numBasis-2, dtype=numpy.int32) q = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.zeros(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Bottom back p = numpy.arange(numBasis-1, 1, step=-1, dtype=numpy.int32) q = numpy.ones(numBasis-2, dtype=numpy.int32) r = numpy.zeros(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Bottom left p = numpy.zeros(numBasis-2, dtype=numpy.int32) q = numpy.arange(numBasis-1, 1, step=-1, dtype=numpy.int32) r = numpy.zeros(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Top front p = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.zeros(numBasis-2, dtype=numpy.int32) r = numpy.ones(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Top right p = numpy.ones(numBasis-2, dtype=numpy.int32) q = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.ones(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Top back p = numpy.arange(numBasis-1, 1, step=-1, dtype=numpy.int32) q = numpy.ones(numBasis-2, dtype=numpy.int32) r = numpy.ones(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Top left p = numpy.zeros(numBasis-2, dtype=numpy.int32) q = numpy.arange(numBasis-1, 1, step=-1, dtype=numpy.int32) r = numpy.ones(numBasis-2, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Middle left front p = numpy.zeros(numBasis-2, dtype=numpy.int32) q = numpy.zeros(numBasis-2, dtype=numpy.int32) r = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Middle right front p = numpy.ones(numBasis-2, dtype=numpy.int32) q = numpy.zeros(numBasis-2, dtype=numpy.int32) r = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Middle right back p = numpy.ones(numBasis-2, dtype=numpy.int32) q = numpy.ones(numBasis-2, dtype=numpy.int32) r = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Middle left back p = numpy.zeros(numBasis-2, dtype=numpy.int32) q = numpy.ones(numBasis-2, dtype=numpy.int32) r = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p,q,r) # Face if numBasis > 2: # Left / Right ip = numpy.arange(0, 2, dtype=numpy.int32) p = numpy.tile(ip, ((numBasis-2)*(numBasis-2), 1)).transpose() iq = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.tile(iq, (1, 2*(numBasis-2))) ir = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.tile(ir, (2, numBasis-2)).transpose() vertexOrder += zip(p.ravel(),q.ravel(),r.ravel()) # Front / Back ip = numpy.arange(2, numBasis, dtype=numpy.int32) p = numpy.tile(ip, (1, 2*(numBasis-2))) iq = numpy.arange(0, 2, dtype=numpy.int32) q = numpy.tile(iq, ((numBasis-2)*(numBasis-2), 1)).transpose() ir = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.tile(ir, (2, numBasis-2)).transpose() vertexOrder += zip(p.ravel(),q.ravel(),r.ravel()) # Bottom / Top ip = numpy.arange(2, numBasis, dtype=numpy.int32) p = numpy.tile(ip, (1, 2*(numBasis-2))) iq = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.tile(iq, (2, numBasis-2)).transpose() ir = numpy.arange(0, 2, dtype=numpy.int32) r = numpy.tile(ir, ((numBasis-2)*(numBasis-2), 1)).transpose() vertexOrder += zip(p.ravel(),q.ravel(),r.ravel()) # Interior ip = numpy.arange(2, numBasis, dtype=numpy.int32) p = numpy.tile(ip, (1, (numBasis-2)*(numBasis-2))) iq = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.tile(iq, ((numBasis-2), numBasis-2)).transpose() ir = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.tile(ir, ((numBasis-2)*(numBasis-2), 1)).transpose() vertexOrder += zip(p.ravel(),q.ravel(),r.ravel()) self.vertices = numpy.zeros((self.numCorners, dim)) n = 0 for (p,q,r) in vertexOrder: self.vertices[n][0] = vertices[p] self.vertices[n][1] = vertices[q] self.vertices[n][2] = vertices[r] n += 1 if not n == self.numCorners: raise RuntimeError('Invalid 3-D vertex ordering: '+str(n)+ \ ' should be '+str(self.numCorners)) self.quadPts = numpy.zeros((numQuadPts*numQuadPts*numQuadPts, dim)) self.quadWts = numpy.zeros((numQuadPts*numQuadPts*numQuadPts,)) self.basis = numpy.zeros((numQuadPts*numQuadPts*numQuadPts, numBasis*numBasis*numBasis)) self.basisDeriv = numpy.zeros((numQuadPts*numQuadPts*numQuadPts, numBasis*numBasis*numBasis, dim)) # Order of quadrature points doesn't matter # Order of basis functions should match vertices for isoparametric n = 0 for r in range(0, numQuadPts): for q in range(0, numQuadPts): for p in range(0, numQuadPts): self.quadPts[n][0] = quadpts[p] self.quadPts[n][1] = quadpts[q] self.quadPts[n][2] = quadpts[r] self.quadWts[n] = quadwts[p]*quadwts[q]*quadwts[r] m = 0 for (bp,bq,br) in vertexOrder: self.basis[n][m] = basis[p][bp]*basis[q][bq]*basis[r][br] self.basisDeriv[n][m][0] = basisDeriv[p][bp][0]*basis[q][bq]*basis[r][br] self.basisDeriv[n][m][1] = basis[p][bp]*basisDeriv[q][bq][0]*basis[r][br] self.basisDeriv[n][m][2] = basis[p][bp]*basis[q][bq]*basisDeriv[r][br][0] m += 1 if not m == self.numCorners: raise RuntimeError('Invalid 3-D basis tabulation: '+str(m)+ \ ' should be '+str(self.numCorners)) n += 1 if not n == self.numQuadPts: raise RuntimeError('Invalid 3-D quadrature: '+str(n)+ \ ' should be '+str(self.numQuadPts)) self.vertices = numpy.reshape(self.vertices, (self.numCorners, dim)) self.quadPts = numpy.reshape(self.quadPts, (self.numQuadPts, dim)) self.quadWts = numpy.reshape(self.quadWts, (self.numQuadPts)) self.basis = numpy.reshape(self.basis, (self.numQuadPts, self.numCorners)) self.basisDeriv = numpy.reshape(self.basisDeriv, (self.numQuadPts, self.numCorners, dim)) else: # Need 0-D quadrature for boundary conditions of 1-D meshes self.cellDim = 0 self.numCorners = 1 self.numQuadPts = 1 self.basis = numpy.array([1.0], dtype=numpy.float64) self.basisDeriv = numpy.array([1.0], dtype=numpy.float64) self.quadPts = numpy.array([0.0], dtype=numpy.float64) self.quadWts = numpy.array([1.0], dtype=numpy.float64) from pylith.mpi.Communicator import mpi_comm_world comm = mpi_comm_world() if 0 == comm.rank: self._info.line("Cell geometry: ") self._info.line(self.geometry) self._info.line("Vertices: ") self._info.line(self.vertices) self._info.line("Quad pts:") self._info.line(self.quadPts) self._info.line("Quad wts:") self._info.line(self.quadWts) self._info.line("Basis fns @ quad pts ):") self._info.line(self.basis) self._info.line("Basis fn derivatives @ quad pts:") self._info.line(self.basisDeriv) self._info.log() return
def initialize(self, spaceDim): """ Initialize reference finite-element cell from a tensor product of 1-D Lagrange elements. """ self._setupGeometry(spaceDim) if self.cellDim > 0: quadrature = self._setupQuadrature() element = self._setupElement() dim = self.cellDim # Get coordinates of vertices (dual basis) vertices = numpy.array(self._setupVertices(element), dtype=numpy.float64) # Evaluate basis functions at quadrature points from FIAT.polynomial_set import mis quadpts = numpy.array(quadrature.get_points(), dtype=numpy.float64) quadwts = numpy.array(quadrature.get_weights(), dtype=numpy.float64) numQuadPts = len(quadpts) evals = element.get_nodal_basis().tabulate(quadrature.get_points(), 1) basis = numpy.array(evals[mis(1, 0)[0]], dtype=numpy.float64).transpose() numBasis = element.get_nodal_basis().get_num_members() # Evaluate derivatives of basis functions at quadrature points basisDeriv = numpy.array([evals[alpha] for alpha in mis(1, 1)], dtype=numpy.float64).transpose() self.numQuadPts = numQuadPts**dim self.numCorners = numBasis**dim if dim == 1: # Set order of vertices and basis functions. # Corners vertexOrder = [0, 1] # Edges p = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p) self.vertices = numpy.zeros((self.numCorners, dim)) n = 0 for p in vertexOrder: self.vertices[n][0] = vertices[p] n += 1 if not n == self.numCorners: raise RuntimeError('Invalid 1-D vertex ordering: '+str(n)+ \ ' should be '+str(self.numCorners)) self.quadPts = numpy.zeros((numQuadPts, dim)) self.quadWts = numpy.zeros((numQuadPts, )) self.basis = numpy.zeros((numQuadPts, numBasis)) self.basisDeriv = numpy.zeros((numQuadPts, numBasis, dim)) # Order of quadrature points doesn't matter # Order of basis functions should match vertices for isoparametric n = 0 for p in range(0, numQuadPts): self.quadPts[n][0] = quadpts[p] self.quadWts[n] = quadwts[p] m = 0 for (bp) in vertexOrder: self.basis[n][m] = basis[p][bp] self.basisDeriv[n][m][0] = basisDeriv[p][bp][0] m += 1 if not m == self.numCorners: raise RuntimeError('Invalid 2-D basis tabulation: '+str(m)+ \ ' should be '+str(self.numCorners)) n += 1 if not n == self.numQuadPts: raise RuntimeError('Invalid 2-D quadrature: '+str(n)+ \ ' should be '+str(self.numQuadPts)) elif dim == 2: # Set order of vertices and basis functions. # Corners vertexOrder = [(0, 0), (1, 0), (1, 1), (0, 1)] # Edges # Bottom p = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.zeros(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q) # Right p = numpy.ones(numBasis - 2, dtype=numpy.int32) q = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p, q) # Top p = numpy.arange(numBasis - 1, 1, step=-1, dtype=numpy.int32) q = numpy.ones(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q) # Left p = numpy.zeros(numBasis - 2, dtype=numpy.int32) q = numpy.arange(numBasis - 1, 1, step=-1, dtype=numpy.int32) vertexOrder += zip(p, q) # Face p = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p, q) self.vertices = numpy.zeros((self.numCorners, dim)) n = 0 for (p, q) in vertexOrder: self.vertices[n][0] = vertices[p] self.vertices[n][1] = vertices[q] n += 1 if not n == self.numCorners: raise RuntimeError('Invalid 2-D vertex ordering: '+str(n)+ \ ' should be '+str(self.numCorners)) self.quadPts = numpy.zeros((numQuadPts * numQuadPts, dim)) self.quadWts = numpy.zeros((numQuadPts * numQuadPts, )) self.basis = numpy.zeros( (numQuadPts * numQuadPts, numBasis * numBasis)) self.basisDeriv = numpy.zeros( (numQuadPts * numQuadPts, numBasis * numBasis, dim)) # Order of quadrature points doesn't matter # Order of basis functions should match vertices for isoparametric n = 0 for q in range(0, numQuadPts): for p in range(0, numQuadPts): self.quadPts[n][0] = quadpts[p] self.quadPts[n][1] = quadpts[q] self.quadWts[n] = quadwts[p] * quadwts[q] m = 0 for (bp, bq) in vertexOrder: self.basis[n][m] = basis[p][bp] * basis[q][bq] self.basisDeriv[n][m][ 0] = basisDeriv[p][bp][0] * basis[q][bq] self.basisDeriv[n][m][ 1] = basis[p][bp] * basisDeriv[q][bq][0] m += 1 if not m == self.numCorners: raise RuntimeError('Invalid 2-D basis tabulation: '+str(m)+ \ ' should be '+str(self.numCorners)) n += 1 if not n == self.numQuadPts: raise RuntimeError('Invalid 2-D quadrature: '+str(n)+ \ ' should be '+str(self.numQuadPts)) elif dim == 3: # Set order of vertices and basis functions. # Corners vertexOrder = [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 0, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)] # Edges # Bottom front p = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.zeros(numBasis - 2, dtype=numpy.int32) r = numpy.zeros(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Bottom right p = numpy.ones(numBasis - 2, dtype=numpy.int32) q = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.zeros(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Bottom back p = numpy.arange(numBasis - 1, 1, step=-1, dtype=numpy.int32) q = numpy.ones(numBasis - 2, dtype=numpy.int32) r = numpy.zeros(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Bottom left p = numpy.zeros(numBasis - 2, dtype=numpy.int32) q = numpy.arange(numBasis - 1, 1, step=-1, dtype=numpy.int32) r = numpy.zeros(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Top front p = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.zeros(numBasis - 2, dtype=numpy.int32) r = numpy.ones(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Top right p = numpy.ones(numBasis - 2, dtype=numpy.int32) q = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.ones(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Top back p = numpy.arange(numBasis - 1, 1, step=-1, dtype=numpy.int32) q = numpy.ones(numBasis - 2, dtype=numpy.int32) r = numpy.ones(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Top left p = numpy.zeros(numBasis - 2, dtype=numpy.int32) q = numpy.arange(numBasis - 1, 1, step=-1, dtype=numpy.int32) r = numpy.ones(numBasis - 2, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Middle left front p = numpy.zeros(numBasis - 2, dtype=numpy.int32) q = numpy.zeros(numBasis - 2, dtype=numpy.int32) r = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Middle right front p = numpy.ones(numBasis - 2, dtype=numpy.int32) q = numpy.zeros(numBasis - 2, dtype=numpy.int32) r = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Middle right back p = numpy.ones(numBasis - 2, dtype=numpy.int32) q = numpy.ones(numBasis - 2, dtype=numpy.int32) r = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Middle left back p = numpy.zeros(numBasis - 2, dtype=numpy.int32) q = numpy.ones(numBasis - 2, dtype=numpy.int32) r = numpy.arange(2, numBasis, dtype=numpy.int32) vertexOrder += zip(p, q, r) # Face if numBasis > 2: # Left / Right ip = numpy.arange(0, 2, dtype=numpy.int32) p = numpy.tile(ip, ((numBasis - 2) * (numBasis - 2), 1)).transpose() iq = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.tile(iq, (1, 2 * (numBasis - 2))) ir = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.tile(ir, (2, numBasis - 2)).transpose() vertexOrder += zip(p.ravel(), q.ravel(), r.ravel()) # Front / Back ip = numpy.arange(2, numBasis, dtype=numpy.int32) p = numpy.tile(ip, (1, 2 * (numBasis - 2))) iq = numpy.arange(0, 2, dtype=numpy.int32) q = numpy.tile(iq, ((numBasis - 2) * (numBasis - 2), 1)).transpose() ir = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.tile(ir, (2, numBasis - 2)).transpose() vertexOrder += zip(p.ravel(), q.ravel(), r.ravel()) # Bottom / Top ip = numpy.arange(2, numBasis, dtype=numpy.int32) p = numpy.tile(ip, (1, 2 * (numBasis - 2))) iq = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.tile(iq, (2, numBasis - 2)).transpose() ir = numpy.arange(0, 2, dtype=numpy.int32) r = numpy.tile(ir, ((numBasis - 2) * (numBasis - 2), 1)).transpose() vertexOrder += zip(p.ravel(), q.ravel(), r.ravel()) # Interior ip = numpy.arange(2, numBasis, dtype=numpy.int32) p = numpy.tile(ip, (1, (numBasis - 2) * (numBasis - 2))) iq = numpy.arange(2, numBasis, dtype=numpy.int32) q = numpy.tile(iq, ((numBasis - 2), numBasis - 2)).transpose() ir = numpy.arange(2, numBasis, dtype=numpy.int32) r = numpy.tile(ir, ((numBasis - 2) * (numBasis - 2), 1)).transpose() vertexOrder += zip(p.ravel(), q.ravel(), r.ravel()) self.vertices = numpy.zeros((self.numCorners, dim)) n = 0 for (p, q, r) in vertexOrder: self.vertices[n][0] = vertices[p] self.vertices[n][1] = vertices[q] self.vertices[n][2] = vertices[r] n += 1 if not n == self.numCorners: raise RuntimeError('Invalid 3-D vertex ordering: '+str(n)+ \ ' should be '+str(self.numCorners)) self.quadPts = numpy.zeros( (numQuadPts * numQuadPts * numQuadPts, dim)) self.quadWts = numpy.zeros( (numQuadPts * numQuadPts * numQuadPts, )) self.basis = numpy.zeros((numQuadPts * numQuadPts * numQuadPts, numBasis * numBasis * numBasis)) self.basisDeriv = numpy.zeros( (numQuadPts * numQuadPts * numQuadPts, numBasis * numBasis * numBasis, dim)) # Order of quadrature points doesn't matter # Order of basis functions should match vertices for isoparametric n = 0 for r in range(0, numQuadPts): for q in range(0, numQuadPts): for p in range(0, numQuadPts): self.quadPts[n][0] = quadpts[p] self.quadPts[n][1] = quadpts[q] self.quadPts[n][2] = quadpts[r] self.quadWts[ n] = quadwts[p] * quadwts[q] * quadwts[r] m = 0 for (bp, bq, br) in vertexOrder: self.basis[n][m] = basis[p][bp] * basis[q][ bq] * basis[r][br] self.basisDeriv[n][m][0] = basisDeriv[p][bp][ 0] * basis[q][bq] * basis[r][br] self.basisDeriv[n][m][1] = basis[p][ bp] * basisDeriv[q][bq][0] * basis[r][br] self.basisDeriv[n][m][2] = basis[p][ bp] * basis[q][bq] * basisDeriv[r][br][0] m += 1 if not m == self.numCorners: raise RuntimeError('Invalid 3-D basis tabulation: '+str(m)+ \ ' should be '+str(self.numCorners)) n += 1 if not n == self.numQuadPts: raise RuntimeError('Invalid 3-D quadrature: '+str(n)+ \ ' should be '+str(self.numQuadPts)) self.vertices = numpy.reshape(self.vertices, (self.numCorners, dim)) self.quadPts = numpy.reshape(self.quadPts, (self.numQuadPts, dim)) self.quadWts = numpy.reshape(self.quadWts, (self.numQuadPts)) self.basis = numpy.reshape(self.basis, (self.numQuadPts, self.numCorners)) self.basisDeriv = numpy.reshape( self.basisDeriv, (self.numQuadPts, self.numCorners, dim)) else: # Need 0-D quadrature for boundary conditions of 1-D meshes self.cellDim = 0 self.numCorners = 1 self.numQuadPts = 1 self.basis = numpy.array([1.0], dtype=numpy.float64) self.basisDeriv = numpy.array([1.0], dtype=numpy.float64) self.quadPts = numpy.array([0.0], dtype=numpy.float64) self.quadWts = numpy.array([1.0], dtype=numpy.float64) from pylith.mpi.Communicator import mpi_comm_world comm = mpi_comm_world() if 0 == comm.rank: self._info.line("Cell geometry: ") self._info.line(self.geometry) self._info.line("Vertices: ") self._info.line(self.vertices) self._info.line("Quad pts:") self._info.line(self.quadPts) self._info.line("Quad wts:") self._info.line(self.quadWts) self._info.line("Basis fns @ quad pts ):") self._info.line(self.basis) self._info.line("Basis fn derivatives @ quad pts:") self._info.line(self.basisDeriv) self._info.log() return
def tabulate(self, order, points, entity=None): """Return tabulated values of derivatives up to a 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``, tabulated values are computed by geometrically approximating which facet the points are on. .. note :: Performing illegal tabulations on this element will result in either a tabulation table of `numpy.nan` arrays (`entity=None` case), or insertions of the `TraceError` exception class. This is due to the fact that performing cell-wise tabulations, or asking for any order of derivative evaluations, are not mathematically well-defined. """ sd = self.ref_el.get_spatial_dimension() facet_sd = sd - 1 # Initializing dictionary with zeros phivals = {} for i in range(order + 1): alphas = mis(sd, i) for alpha in alphas: phivals[alpha] = np.zeros(shape=(self.space_dimension(), len(points))) evalkey = (0,) * sd # If entity is None, identify facet using numerical tolerance and # return the tabulated values if entity is None: # NOTE: Numerical approximation of the facet id is currently only # implemented for simplex reference cells. if self.ref_el.get_shape() not in [TRIANGLE, TETRAHEDRON]: raise NotImplementedError( "Tabulating this element on a %s cell without providing " "an entity is not currently supported." % type(self.ref_el) ) # Attempt to identify which facet (if any) the given points are on vertices = self.ref_el.vertices coordinates = barycentric_coordinates(points, vertices) unique_facet, success = extract_unique_facet(coordinates) # If not successful, return NaNs if not success: for key in phivals: phivals[key] = np.full(shape=(self.space_dimension(), len(points)), fill_value=np.nan) return phivals # Otherwise, extract non-zero values and insertion indices else: # Map points to the reference facet new_points = map_to_reference_facet(points, vertices, unique_facet) # Retrieve values by tabulating the DG element element = self.dg_elements[facet_sd] nf = element.space_dimension() nonzerovals, = element.tabulate(order, new_points).values() indices = slice(nf * unique_facet, nf * (unique_facet + 1)) else: entity_dim, _ = entity # If the user is directly specifying cell-wise tabulation, return # TraceErrors in dict for appropriate handling in the form compiler if entity_dim not in self.dg_elements: for key in phivals: msg = "The HDivTrace element can only be tabulated on facets." phivals[key] = TraceError(msg) return phivals else: # Retrieve function evaluations (order = 0 case) offset = 0 for facet_dim in sorted(self.dg_elements): element = self.dg_elements[facet_dim] nf = element.space_dimension() num_facets = len(self.ref_el.get_topology()[facet_dim]) # Loop over the number of facets until we find a facet # with matching dimension and id for i in range(num_facets): # Found it! Grab insertion indices if (facet_dim, i) == entity: nonzerovals, = element.tabulate(0, points).values() indices = slice(offset, offset + nf) offset += nf # If asking for gradient evaluations, insert TraceError in # gradient slots if order > 0: msg = "Gradients on trace elements are not well-defined." for key in phivals: if key != evalkey: phivals[key] = TraceError(msg) # Insert non-zero values in appropriate place phivals[evalkey][indices, :] = nonzerovals return phivals
def make_entity_permutations(dim, npoints): if npoints <= 0: return {o: [] for o in range(np.math.factorial(dim + 1))} # DG nodes are numbered, in order of significance, # - by g0: entity dim (vertices first, then edges, then ...) # - by g1: entity ids (DoFs on entities of smaller ids first) # - lexicographically as in CG # # Example: # dim = 2, degree = 3 (npoints = degree + 1) # # facet ids Lexicographic DG (degree = 3) # + DoF numbering DoF numbering # | \ 9 2 # | \ 0 7 8 6 4 # 1 | \ 4 5 6 5 9 3 # | \ 0 1 2 3 0 7 8 1 # +--------+ # 2 # where DG degrees of freedom are numbered to geometrically # coincide with CG degrees of freedom. # # In the below we will show example outputs corresponding to # the above example. a = np.array(sorted(mis(dim + 1, npoints - 1)), dtype=int) # >>> a # [[0, 0, 3], # [0, 1, 2], # (3,0,0) # [0, 2, 1], # # [0, 3, 0], # (2,0,1)(2,1,0) # [1, 0, 2], # # [1, 1, 1], # (1,0,2)(1,1,1)(1,2,0) # [1, 2, 0], # # [2, 0, 1], # (0,0,3)(0,1,2)(0,2,1)(0,3,0) # [2, 1, 0], # # [3, 0, 0]] # Lattice points that a represents # a.shape[0] = number of DoFs # a.shape[1] = dim + 1 # sum of each row = degree (bary centric lattice coordinates) # Flip along the axis 1 for convenience. # This would make: np.lexsort(a.transpose()) = [0, 1, 2, ..., 9]. a = a[:, ::-1] index_perms = sorted(itertools.permutations(range(dim + 1))) # >>> index_perms # [[0, 1, 2], # [0, 2, 1], # [1, 0, 2], # [1, 2, 0], # [2, 0, 1], # [2, 1, 0]] # First separate by entity dimension. g0 = dim - (a == 0).astype(int).sum(axis=1) # >>> g0 # [ 0, 1, 1, 0, 1, 2, 1, 1, 1, 0] # 0 for vertices # # 1 for edges # # 2 for cell # Then separate by entity number. g1 = np.zeros_like(g0) for d in range(dim + 1): on_facet_d = (a[:, d] == 0).astype(int) g1 += d * on_facet_d # The above logic is consistent with the FIAT entity numbering # convention ("entities that are not incident to vertex 0 are # numbered first, then ..."), but vertices are not numbered using # the same logic in FIAT, so we need to fix: g1[g0 == 0] = -g1[g0 == 0] # >>> g1 # [-3, 2, 2,-2, 1, 0, 0, 1, 0,-1] # Compoute the map from the DG DoFs to the lattice points on the cell. # For each entity dimension, DoFs that have smaller numbers in g1 # will be assigned smaller DG node numbers. # Order first by g0, then by g1, and finally by a (lexicographically as in CG) g0 = g0.reshape((a.shape[0], 1)) g1 = g1.reshape((a.shape[0], 1)) dg_to_lattice = np.lexsort( np.transpose(np.concatenate((a, g1, g0), axis=1))) # >>> dg_to_lattice # [ 0, 3, 9, 6, 8, 4, 7, 1, 2, 5] # Compute the inverse map. lattice_to_dg = np.empty_like(dg_to_lattice) for i, im in enumerate(dg_to_lattice): lattice_to_dg[im] = i # >>> lattice_to_dg # [ 0, 7, 8, 1, 5, 9, 3, 6, 4, 2] perms = {} for o, index_perm in enumerate(index_perms): # First compute permutation in lattice point numbers in lattice point order (as we do for CG cell DoFs) perm = np.lexsort(np.transpose(a[:, index_perm])) # Then convert to DG DoF numbers in DG DoF order: # lattice_to_dg[perm] -> convert lattice point numbers to DG DoF numbers # lattice_to_dg[perm][dg_to_lattice] -> reorder for DG DoF order # # Example: # Under one CW rotation, a DG element on a physical cell # is mapped to the FIAT reference as: # # 0 # 7 5 # 8 9 6 # 1 3 4 2 # # Under the same transformation, the lattice points would # be mapped as: # # 0 # 1 4 # 2 5 7 # 3 6 8 9 # # In this case we will have: # # perm = [3, 6, 8, 9, 2, 5, 7, 1, 4, 0] # lattice_to_dg[perm] = [1, 3, 4, 2, 8, 9, 6, 7, 5, 0] # lattice_to_dg[perm][dg_to_lattice] = [1, 2, 0, 6, 5, 8, 7, 3, 4, 9] # # Note: # Sane thing to do is to just number DG dofs on a lattice. perm = lattice_to_dg[perm][dg_to_lattice] perms[o] = perm.tolist() return perms
def tabulate(self, order, points, entity=None): """Return tabulated values of derivatives up to given order of basis functions at given points.""" if entity is None: entity = (self.ref_el.get_dimension(), 0) entity_dim, entity_id = entity shape = tuple(len(c.get_topology()[d]) for c, d in zip(self.ref_el.cells, entity_dim)) idA, idB = numpy.unravel_index(entity_id, shape) # Factor the entity argument to get entities of the component elements entityA_dim, entityB_dim = entity_dim entityA = (entityA_dim, idA) entityB = (entityB_dim, idB) pointsAdim, pointsBdim = [c.get_spatial_dimension() for c in self.ref_el.construct_subelement(entity_dim).cells] pointsA = [point[:pointsAdim] for point in points] pointsB = [point[pointsAdim:pointsAdim + pointsBdim] for point in points] Asdim = self.A.ref_el.get_spatial_dimension() Bsdim = self.B.ref_el.get_spatial_dimension() # Note that for entities other than cells, the following # tabulations are already appropriately zero-padded so no # additional zero padding is required. Atab = self.A.tabulate(order, pointsA, entityA) Btab = self.B.tabulate(order, pointsB, entityB) npoints = len(points) # allow 2 scalar-valued FE spaces, or 1 scalar-valued, # 1 vector-valued. Combining 2 vector-valued spaces # into a tensor-valued space via an outer-product # seems to be a sensible general option, but I don't # know how to handle the nestedness of the arrays # if someone then tries to make a new "tensor finite # element" where one component is already a # tensor-valued space! A_valuedim = len(self.A.value_shape()) # scalar: 0, vector: 1 B_valuedim = len(self.B.value_shape()) # scalar: 0, vector: 1 if A_valuedim + B_valuedim > 1: raise NotImplementedError("tabulate does not support two vector-valued inputs") result = {} for i in range(order + 1): alphas = mis(Asdim+Bsdim, i) # thanks, Rob! for alpha in alphas: if A_valuedim == 0 and B_valuedim == 0: # for each point, get outer product of (A's basis # functions f1, f2, ... evaluated at that point) # with (B's basis functions g1, g2, ... evaluated # at that point). This gives temp[point][f_i][g_j]. # Flatten this, so bfs are # in the order f1g1, f1g2, ..., f2g1, f2g2, ... # which is compatible with the entity_dofs order. # We now have temp[point][full basis function] # Transpose this to get temp[bf][point], # and we are done. temp = numpy.array([numpy.outer( Atab[alpha[0:Asdim]][..., j], Btab[alpha[Asdim:Asdim+Bsdim]][..., j]) .ravel() for j in range(npoints)]) result[alpha] = temp.transpose() elif A_valuedim == 1 and B_valuedim == 0: # similar to above, except A's basis functions # are now vector-valued. numpy.outer flattens the # array, so it's like taking the OP of # f1_x, f1_y, f2_x, f2_y, ... with g1, g2, ... # this gives us # temp[point][f1x, f1y, f2x, f2y, ...][g_j]. # reshape once to get temp[point][f_i][x/y][g_j] # transpose to get temp[point][x/y][f_i][g_j] # reshape to flatten the last two indices, this # gives us temp[point][x/y][full bf_i] # finally, transpose the first and last indices # to get temp[bf_i][x/y][point], and we are done. temp = numpy.array([numpy.outer( Atab[alpha[0:Asdim]][..., j], Btab[alpha[Asdim:Asdim+Bsdim]][..., j]) for j in range(npoints)]) assert temp.shape[1] % 2 == 0 temp2 = temp.reshape((temp.shape[0], temp.shape[1]//2, 2, temp.shape[2]))\ .transpose(0, 2, 1, 3)\ .reshape((temp.shape[0], 2, -1))\ .transpose(2, 1, 0) result[alpha] = temp2 elif A_valuedim == 0 and B_valuedim == 1: # as above, with B's functions now vector-valued. # we now do... [numpy.outer ... for ...] gives # temp[point][f_i][g1x,g1y,g2x,g2y,...]. # reshape to temp[point][f_i][g_j][x/y] # flatten middle: temp[point][full bf_i][x/y] # transpose to temp[bf_i][x/y][point] temp = numpy.array([numpy.outer( Atab[alpha[0:Asdim]][..., j], Btab[alpha[Asdim:Asdim+Bsdim]][..., j]) for j in range(len(Atab[alpha[0:Asdim]][0]))]) assert temp.shape[2] % 2 == 0 temp2 = temp.reshape((temp.shape[0], temp.shape[1], temp.shape[2]//2, 2))\ .reshape((temp.shape[0], -1, 2))\ .transpose(1, 2, 0) result[alpha] = temp2 return result
def tabulate(self, order, points, entity=None): """Return tabulated values of derivatives up to given order of basis functions at given points.""" if entity is None: entity = (self.ref_el.get_dimension(), 0) entity_dim, entity_id = entity shape = tuple( len(c.get_topology()[d]) for c, d in zip(self.ref_el.cells, entity_dim)) idA, idB = numpy.unravel_index(entity_id, shape) # Factor the entity argument to get entities of the component elements entityA_dim, entityB_dim = entity_dim entityA = (entityA_dim, idA) entityB = (entityB_dim, idB) pointsAdim, pointsBdim = [ c.get_spatial_dimension() for c in self.ref_el.construct_subelement(entity_dim).cells ] pointsA = [point[:pointsAdim] for point in points] pointsB = [ point[pointsAdim:pointsAdim + pointsBdim] for point in points ] Asdim = self.A.ref_el.get_spatial_dimension() Bsdim = self.B.ref_el.get_spatial_dimension() # Note that for entities other than cells, the following # tabulations are already appropriately zero-padded so no # additional zero padding is required. Atab = self.A.tabulate(order, pointsA, entityA) Btab = self.B.tabulate(order, pointsB, entityB) npoints = len(points) # allow 2 scalar-valued FE spaces, or 1 scalar-valued, # 1 vector-valued. Combining 2 vector-valued spaces # into a tensor-valued space via an outer-product # seems to be a sensible general option, but I don't # know how to handle the nestedness of the arrays # if someone then tries to make a new "tensor finite # element" where one component is already a # tensor-valued space! A_valuedim = len(self.A.value_shape()) # scalar: 0, vector: 1 B_valuedim = len(self.B.value_shape()) # scalar: 0, vector: 1 if A_valuedim + B_valuedim > 1: raise NotImplementedError( "tabulate does not support two vector-valued inputs") result = {} for i in range(order + 1): alphas = mis(Asdim + Bsdim, i) # thanks, Rob! for alpha in alphas: if A_valuedim == 0 and B_valuedim == 0: # for each point, get outer product of (A's basis # functions f1, f2, ... evaluated at that point) # with (B's basis functions g1, g2, ... evaluated # at that point). This gives temp[point][f_i][g_j]. # Flatten this, so bfs are # in the order f1g1, f1g2, ..., f2g1, f2g2, ... # which is compatible with the entity_dofs order. # We now have temp[point][full basis function] # Transpose this to get temp[bf][point], # and we are done. temp = numpy.array([ numpy.outer(Atab[alpha[0:Asdim]][..., j], Btab[alpha[Asdim:Asdim + Bsdim]][..., j]).ravel() for j in range(npoints) ]) result[alpha] = temp.transpose() elif A_valuedim == 1 and B_valuedim == 0: # similar to above, except A's basis functions # are now vector-valued. numpy.outer flattens the # array, so it's like taking the OP of # f1_x, f1_y, f2_x, f2_y, ... with g1, g2, ... # this gives us # temp[point][f1x, f1y, f2x, f2y, ...][g_j]. # reshape once to get temp[point][f_i][x/y][g_j] # transpose to get temp[point][x/y][f_i][g_j] # reshape to flatten the last two indices, this # gives us temp[point][x/y][full bf_i] # finally, transpose the first and last indices # to get temp[bf_i][x/y][point], and we are done. temp = numpy.array([ numpy.outer(Atab[alpha[0:Asdim]][..., j], Btab[alpha[Asdim:Asdim + Bsdim]][..., j]) for j in range(npoints) ]) assert temp.shape[1] % 2 == 0 temp2 = temp.reshape((temp.shape[0], temp.shape[1]//2, 2, temp.shape[2]))\ .transpose(0, 2, 1, 3)\ .reshape((temp.shape[0], 2, -1))\ .transpose(2, 1, 0) result[alpha] = temp2 elif A_valuedim == 0 and B_valuedim == 1: # as above, with B's functions now vector-valued. # we now do... [numpy.outer ... for ...] gives # temp[point][f_i][g1x,g1y,g2x,g2y,...]. # reshape to temp[point][f_i][g_j][x/y] # flatten middle: temp[point][full bf_i][x/y] # transpose to temp[bf_i][x/y][point] temp = numpy.array([ numpy.outer(Atab[alpha[0:Asdim]][..., j], Btab[alpha[Asdim:Asdim + Bsdim]][..., j]) for j in range(len(Atab[alpha[0:Asdim]][0])) ]) assert temp.shape[2] % 2 == 0 temp2 = temp.reshape((temp.shape[0], temp.shape[1], temp.shape[2]//2, 2))\ .reshape((temp.shape[0], -1, 2))\ .transpose(1, 2, 0) result[alpha] = temp2 return result
def tabulate(self, order, points, entity=None): """Return tabulated values of derivatives up to a 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``, tabulated values are computed by geometrically approximating which facet the points are on. .. note :: Performing illegal tabulations on this element will result in either a tabulation table of `numpy.nan` arrays (`entity=None` case), or insertions of the `TraceError` exception class. This is due to the fact that performing cell-wise tabulations, or asking for any order of derivative evaluations, are not mathematically well-defined. """ facet_dim = self.ref_el.get_spatial_dimension() - 1 sdim = self.space_dimension() nf = self.facet_element.space_dimension() # Initializing dictionary with zeros phivals = {} for i in range(order + 1): alphas = mis(self.ref_el.get_spatial_dimension(), i) for alpha in alphas: phivals[alpha] = np.zeros(shape=(sdim, len(points))) evalkey = (0, ) * (facet_dim + 1) # If entity is None, identify facet using numerical tolerance and # return the tabulated values if entity is None: # Attempt to identify which facet (if any) the given points are on vertices = self.ref_el.vertices coordinates = barycentric_coordinates(points, vertices) (unique_facet, success) = extract_unique_facet(coordinates) # If successful, insert evaluations if success: # Map points to the reference facet new_points = map_to_reference_facet(points, vertices, unique_facet) # Retrieve values by tabulating the DiscontinuousLagrange element nonzerovals = list( self.facet_element.tabulate(order, new_points).values())[0] phivals[evalkey][nf * unique_facet:nf * (unique_facet + 1), :] = nonzerovals # Otherwise, return NaNs else: for key in phivals.keys(): phivals[key] = np.full(shape=(sdim, len(points)), fill_value=np.nan) return phivals entity_dim, entity_id = entity # If the user is directly specifying cell-wise tabulation, return TraceErrors in dict for # appropriate handling in the form compiler if entity_dim != facet_dim: for key in phivals.keys(): phivals[key] = TraceError( "Attempting to tabulate a %d-entity. Expecting a %d-entitiy" % (entity_dim, facet_dim)) return phivals else: # Retrieve function evaluations (order = 0 case) nonzerovals = list( self.facet_element.tabulate(0, points).values())[0] phivals[evalkey][nf * entity_id:nf * (entity_id + 1), :] = nonzerovals # If asking for gradient evaluations, insert TraceError in gradient evaluations if order > 0: for key in phivals.keys(): if key != evalkey: phivals[key] = TraceError( "Gradient evaluations are illegal on trace elements." ) return phivals