def apply_single_function_pullbacks(r, element): """Apply an appropriate pullback to something in physical space :arg r: An expression wrapped in ReferenceValue. :arg element: The element this expression lives in. :returns: a pulled back expression.""" mapping = element.mapping() if r.ufl_shape != element.reference_value_shape(): error("Expecting reference space expression with shape '%s', got '%s'" % (element.reference_value_shape(), r.ufl_shape)) if mapping in {"physical", "identity", "contravariant Piola", "covariant Piola", "double contravariant Piola", "double covariant Piola", "L2 Piola"}: # Base case in recursion through elements. If the element # advertises a mapping we know how to handle, do that # directly. f = apply_known_single_pullback(r, element) if f.ufl_shape != element.value_shape(): error("Expecting pulled back expression with shape '%s', got '%s'" % (element.value_shape(), f.ufl_shape)) return f elif mapping in {"symmetries", "undefined"}: # Need to pull back each unique piece of the reference space thing gsh = element.value_shape() rsh = r.ufl_shape if mapping == "symmetries": subelem = element.sub_elements()[0] fcm = element.flattened_sub_element_mapping() offsets = (product(subelem.reference_value_shape()) * i for i in fcm) elements = repeat(subelem) else: elements = sub_elements_with_mappings(element) # Python >= 3.8 has an initial keyword argument to # accumulate, but 3.7 does not. offsets = chain([0], accumulate(product(e.reference_value_shape()) for e in elements)) rflat = as_vector([r[idx] for idx in numpy.ndindex(rsh)]) g_components = [] # For each unique piece in reference space, apply the appropriate pullback for offset, subelem in zip(offsets, elements): sub_rsh = subelem.reference_value_shape() rm = product(sub_rsh) rsub = [rflat[offset + i] for i in range(rm)] rsub = as_tensor(numpy.asarray(rsub).reshape(sub_rsh)) rmapped = apply_single_function_pullbacks(rsub, subelem) # Flatten into the pulled back expression for the whole thing g_components.extend([rmapped[idx] for idx in numpy.ndindex(rmapped.ufl_shape)]) # And reshape appropriately f = as_tensor(numpy.asarray(g_components).reshape(gsh)) if f.ufl_shape != element.value_shape(): error("Expecting pulled back expression with shape '%s', got '%s'" % (element.value_shape(), f.ufl_shape)) return f else: error("Unhandled mapping type '%s'" % mapping)
def extract_subelement_component(self, i): """Extract direct subelement index and subelement relative component index for a given component index.""" if isinstance(i, int): i = (i, ) self._check_component(i) # Select between indexing modes if len(self.value_shape()) == 1: # Indexing into a long vector of flattened subelement # shapes j, = i # Find subelement for this index for sub_element_index, e in enumerate(self._sub_elements): sh = e.value_shape() si = product(sh) if j < si: break j -= si if j < 0: error("Moved past last value component!") # Convert index into a shape tuple st = shape_to_strides(sh) component = unflatten_index(j, st) else: # Indexing into a multidimensional tensor where subelement # index is first axis sub_element_index = i[0] if sub_element_index >= len(self._sub_elements): error("Illegal component index (dimension %d)." % sub_element_index) component = i[1:] return (sub_element_index, component)
def extract_subelement_reference_component(self, i): """Extract direct subelement index and subelement relative reference_component index for a given reference_component index.""" if isinstance(i, int): i = (i, ) self._check_reference_component(i) # Select between indexing modes assert len(self.reference_value_shape()) == 1 # Indexing into a long vector of flattened subelement shapes j, = i # Find subelement for this index for sub_element_index, e in enumerate(self._sub_elements): sh = e.reference_value_shape() si = product(sh) if j < si: break j -= si if j < 0: error("Moved past last value reference_component!") # Convert index into a shape tuple st = shape_to_strides(sh) reference_component = unflatten_index(j, st) return (sub_element_index, reference_component)
def extract_subelement_component(self, i): """Extract direct subelement index and subelement relative component index for a given component index.""" if isinstance(i, int): i = (i,) self._check_component(i) # Select between indexing modes if len(self.value_shape()) == 1: # Indexing into a long vector of flattened subelement # shapes j, = i # Find subelement for this index for sub_element_index, e in enumerate(self._sub_elements): sh = e.value_shape() si = product(sh) if j < si: break j -= si if j < 0: error("Moved past last value component!") # Convert index into a shape tuple st = shape_to_strides(sh) component = unflatten_index(j, st) else: # Indexing into a multidimensional tensor where subelement # index is first axis sub_element_index = i[0] if sub_element_index >= len(self._sub_elements): error("Illegal component index (dimension %d)." % sub_element_index) component = i[1:] return (sub_element_index, component)
def extract_subelement_reference_component(self, i): """Extract direct subelement index and subelement relative reference_component index for a given reference_component index.""" if isinstance(i, int): i = (i,) self._check_reference_component(i) # Select between indexing modes assert len(self.reference_value_shape()) == 1 # Indexing into a long vector of flattened subelement shapes j, = i # Find subelement for this index for sub_element_index, e in enumerate(self._sub_elements): sh = e.reference_value_shape() si = product(sh) if j < si: break j -= si if j < 0: error("Moved past last value reference_component!") # Convert index into a shape tuple st = shape_to_strides(sh) reference_component = unflatten_index(j, st) return (sub_element_index, reference_component)
def reshape_to_nested_list(components, shape): if len(shape) == 0: assert len(components) == 1 return [components[0]] elif len(shape) == 1: assert len(components) == shape[0] return components else: n = product(shape[1:]) return [reshape_to_nested_list(components[n * i:n * (i + 1)], shape[1:]) for i in range(shape[0])]
def symmetry(self): """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` meaning that component :math:`c_0` is represented by component :math:`c_1`. A component is a tuple of one or more ints.""" # Build symmetry map from symmetries of subelements sm = {} # Base index of the current subelement into mixed value j = 0 for e in self._sub_elements: sh = e.value_shape() st = shape_to_strides(sh) # Map symmetries of subelement into index space of this # element for c0, c1 in e.symmetry().items(): j0 = flatten_multiindex(c0, st) + j j1 = flatten_multiindex(c1, st) + j sm[(j0, )] = (j1, ) # Update base index for next element j += product(sh) if j != product(self.value_shape()): error("Size mismatch in symmetry algorithm.") return sm or EmptyDict
def symmetry(self): """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` meaning that component :math:`c_0` is represented by component :math:`c_1`. A component is a tuple of one or more ints.""" # Build symmetry map from symmetries of subelements sm = {} # Base index of the current subelement into mixed value j = 0 for e in self._sub_elements: sh = e.value_shape() st = shape_to_strides(sh) # Map symmetries of subelement into index space of this # element for c0, c1 in e.symmetry().items(): j0 = flatten_multiindex(c0, st) + j j1 = flatten_multiindex(c1, st) + j sm[(j0,)] = (j1,) # Update base index for next element j += product(sh) if j != product(self.value_shape()): error("Size mismatch in symmetry algorithm.") return sm or EmptyDict
def split(v): """UFL operator: If v is a Coefficient or Argument in a mixed space, returns a tuple with the function components corresponding to the subelements.""" # Default range is all of v begin = 0 end = None if isinstance(v, Indexed): # Special case: split previous output of split again # Consistent with simple element, just return function in a tuple return (v, ) elif isinstance(v, ListTensor): # Special case: split previous output of split again ops = v.ufl_operands if all(isinstance(comp, Indexed) for comp in ops): args = [comp.ufl_operands[0] for comp in ops] if all(args[0] == args[i] for i in range(1, len(args))): # Get innermost terminal here and its element v = args[0] # Get relevant range of v components begin, = ops[0].ufl_operands[1] end, = ops[-1].ufl_operands[1] begin = int(begin) end = int(end) + 1 else: error("Don't know how to split %s." % (v, )) else: error("Don't know how to split %s." % (v, )) # Special case: simple element, just return function in a tuple element = v.ufl_element() if not isinstance(element, MixedElement): assert end is None return (v, ) if isinstance(element, TensorElement): if element.symmetry(): error("Split not implemented for symmetric tensor elements.") if len(v.ufl_shape) != 1: error( "Don't know how to split tensor valued mixed functions without flattened index space." ) # Compute value size and set default range end value_size = product(element.value_shape()) if end is None: end = value_size else: # Recursively dive into mixedelement in to subelement # corresponding to beginning of range j = begin while True: sub_i, j = element.extract_subelement_component(j) element = element.sub_elements()[sub_i] # Then break when we find the subelement that covers the whole range if product(element.value_shape()) == (end - begin): break # Build expressions representing the subfunction of v for each subelement offset = begin sub_functions = [] for i, e in enumerate(element.sub_elements()): # Get shape, size, indices, and v components # corresponding to subelement value shape = e.value_shape() strides = shape_to_strides(shape) rank = len(shape) sub_size = product(shape) subindices = [ flatten_multiindex(c, strides) for c in compute_indices(shape) ] components = [v[k + offset] for k in subindices] # Shape components into same shape as subelement if rank == 0: subv, = components elif rank <= 1: subv = as_vector(components) elif rank == 2: subv = as_matrix([ components[i * shape[1]:(i + 1) * shape[1]] for i in range(shape[0]) ]) else: error( "Don't know how to split functions with sub functions of rank %d." % rank) offset += sub_size sub_functions.append(subv) if end != offset: error( "Function splitting failed to extract components for whole intended range. Something is wrong." ) return tuple(sub_functions)
def is_zeros_table(table, rtol=default_rtol, atol=default_atol): return (product(table.shape) == 0 or numpy.allclose(table, numpy.zeros(table.shape), rtol=rtol, atol=atol))
def reference_value_size(self): "Return the integer product of the reference value shape." return product(self.reference_value_shape())
def __init__(self, family, cell=None, degree=None, shape=None, symmetry=None, quad_scheme=None): """Create tensor element (repeated mixed element with optional symmetries). :arg family: The family string, or an existing FiniteElement. :arg cell: The geometric cell (ignored if family is a FiniteElement). :arg degree: The polynomial degree (ignored if family is a FiniteElement). :arg shape: The shape of the element (defaults to a square tensor given by the geometric dimension of the cell). :arg symmetry: Optional symmetries. :arg quad_scheme: Optional quadrature scheme (ignored if family is a FiniteElement).""" if isinstance(family, FiniteElementBase): sub_element = family cell = sub_element.cell() else: if cell is not None: cell = as_cell(cell) # Create scalar sub element sub_element = FiniteElement(family, cell, degree, quad_scheme=quad_scheme) # Set default shape if not specified if shape is None: if cell is None: error("Cannot infer tensor shape without a cell.") dim = cell.geometric_dimension() shape = (dim, dim) if symmetry is None: symmetry = EmptyDict elif symmetry is True: # Construct default symmetry dict for matrix elements if not (len(shape) == 2 and shape[0] == shape[1]): error("Cannot set automatic symmetry for non-square tensor.") symmetry = dict(((i, j), (j, i)) for i in range(shape[0]) for j in range(shape[1]) if i > j) else: if not isinstance(symmetry, dict): error("Expecting symmetry to be None (unset), True, or dict.") # Validate indices in symmetry dict for i, j in symmetry.items(): if len(i) != len(j): error("Non-matching length of symmetry index tuples.") for k in range(len(i)): if not (i[k] >= 0 and j[k] >= 0 and i[k] < shape[k] and j[k] < shape[k]): error("Symmetry dimensions out of bounds.") # Compute all index combinations for given shape indices = compute_indices(shape) # Compute mapping from indices to sub element number, # accounting for symmetry sub_elements = [] sub_element_mapping = {} for index in indices: if index in symmetry: continue sub_element_mapping[index] = len(sub_elements) sub_elements += [sub_element] # Update mapping for symmetry for index in indices: if index in symmetry: sub_element_mapping[index] = sub_element_mapping[ symmetry[index]] flattened_sub_element_mapping = [ sub_element_mapping[index] for i, index in enumerate(indices) ] # Compute value shape value_shape = shape # Compute reference value shape based on symmetries if symmetry: # Flatten and subtract symmetries reference_value_shape = (product(shape) - len(symmetry), ) self._mapping = "symmetries" else: # Do not flatten if there are no symmetries reference_value_shape = shape self._mapping = "identity" value_shape = value_shape + sub_element.value_shape() reference_value_shape = reference_value_shape + sub_element.reference_value_shape( ) # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape, reference_value_shape=reference_value_shape) self._family = sub_element.family() self._degree = sub_element.degree() self._sub_element = sub_element self._shape = shape self._symmetry = symmetry self._sub_element_mapping = sub_element_mapping self._flattened_sub_element_mapping = flattened_sub_element_mapping # Cache repr string self._repr = "TensorElement(%s, shape=%s, symmetry=%s)" % ( repr(sub_element), repr(self._shape), repr(self._symmetry))
def value_size(self): "Return the integer product of the value shape." return product(self.value_shape())
def value_size(self): "Return the integer product of the value shape." return product(self.value_shape())
def __init__(self, *elements, **kwargs): "Create mixed finite element from given list of elements" if type(self) is MixedElement: if kwargs: error( "Not expecting keyword arguments to MixedElement constructor." ) # Un-nest arguments if we get a single argument with a list of elements if len(elements) == 1 and isinstance(elements[0], (tuple, list)): elements = elements[0] # Interpret nested tuples as sub-mixedelements recursively elements = [ MixedElement(e) if isinstance(e, (tuple, list)) else e for e in elements ] self._sub_elements = elements # Pick the first cell, for now all should be equal cells = tuple( sorted(set(element.cell() for element in elements) - set([None]))) self._cells = cells if cells: cell = cells[0] # Require that all elements are defined on the same cell if not all(c == cell for c in cells[1:]): error("Sub elements must live on the same cell.") else: cell = None # Check that all elements use the same quadrature scheme TODO: # We can allow the scheme not to be defined. if len(elements) == 0: quad_scheme = None else: quad_scheme = elements[0].quadrature_scheme() if not all(e.quadrature_scheme() == quad_scheme for e in elements): error( "Quadrature scheme mismatch for sub elements of mixed element." ) # Compute value sizes in global and reference configurations value_size_sum = sum( product(s.value_shape()) for s in self._sub_elements) reference_value_size_sum = sum( product(s.reference_value_shape()) for s in self._sub_elements) # Default value shape: Treated simply as all subelement values # unpacked in a vector. value_shape = kwargs.get('value_shape', (value_size_sum, )) # Default reference value shape: Treated simply as all # subelement reference values unpacked in a vector. reference_value_shape = kwargs.get('reference_value_shape', (reference_value_size_sum, )) # Validate value_shape (deliberately not for subclasses # VectorElement and TensorElement) if type(self) is MixedElement: # This is not valid for tensor elements with symmetries, # assume subclasses deal with their own validation if product(value_shape) != value_size_sum: error("Provided value_shape doesn't match the " "total value size of all subelements.") # Initialize element data degrees = {e.degree() for e in self._sub_elements} - {None} degree = max_degree(degrees) if degrees else None FiniteElementBase.__init__(self, "Mixed", cell, degree, quad_scheme, value_shape, reference_value_shape) # Cache repr string if type(self) is MixedElement: self._repr = "MixedElement(%s)" % (", ".join( repr(e) for e in self._sub_elements), )
def build_element_tables(num_points, quadrature_rules, cell, integral_type, entitytype, modified_terminals, rtol=default_rtol, atol=default_atol): """Build the element tables needed for a list of modified terminals. Input: entitytype - str modified_terminals - ordered sequence of unique modified terminals FIXME: Document Output: tables - dict(name: table) mt_table_names - dict(ModifiedTerminal: name) """ mt_table_names = {} tables = {} table_origins = {} # Add to element tables analysis = {} for mt in modified_terminals: # FIXME: Use a namedtuple for res res = get_modified_terminal_element(mt) if res: analysis[mt] = res # Build element numbering using topological # ordering so subelements get priority from ffc.analysis import extract_sub_elements, sort_elements, _compute_element_numbers all_elements = [res[0] for res in analysis.values()] unique_elements = sort_elements(extract_sub_elements(all_elements)) element_numbers = _compute_element_numbers(unique_elements) def add_table(res): element, avg, local_derivatives, flat_component = res # Build name for this particular table element_number = element_numbers[element] name = generate_psi_table_name( num_points, element_number, avg, entitytype, local_derivatives, flat_component) # Extract the values of the table from ffc table format if name not in tables: tables[name] = get_ffc_table_values( quadrature_rules[num_points][0], cell, integral_type, element, avg, entitytype, local_derivatives, flat_component) # Track table origin for custom integrals: table_origins[name] = res return name for mt in modified_terminals: res = analysis.get(mt) if not res: continue element, avg, local_derivatives, flat_component = res # Generate tables for each subelement in topological ordering, # using same avg and local_derivatives, for each component. # We want the first table to be the innermost subelement so that's # the one the optimized tables get the name from and so that's # the one the table origins point to for custom integrals. # This results in some superfluous tables but those will be # removed before code generation and it's not believed to be # a bottleneck. for subelement in sort_elements(extract_sub_elements([element])): for fc in range(product(subelement.reference_value_shape())): subres = (subelement, avg, local_derivatives, fc) name_ignored = add_table(subres) # Generate table and store table name with modified terminal name = add_table(res) mt_table_names[mt] = name return tables, mt_table_names, table_origins
def reference_value_size(self): "Return the integer product of the reference value shape." return product(self.reference_value_shape())
def __init__(self, family, cell=None, degree=None, shape=None, symmetry=None, quad_scheme=None): """Create tensor element (repeated mixed element with optional symmetries). :arg family: The family string, or an existing FiniteElement. :arg cell: The geometric cell (ignored if family is a FiniteElement). :arg degree: The polynomial degree (ignored if family is a FiniteElement). :arg shape: The shape of the element (defaults to a square tensor given by the geometric dimension of the cell). :arg symmetry: Optional symmetries. :arg quad_scheme: Optional quadrature scheme (ignored if family is a FiniteElement).""" if isinstance(family, FiniteElementBase): sub_element = family cell = sub_element.cell() else: if cell is not None: cell = as_cell(cell) # Create scalar sub element sub_element = FiniteElement(family, cell, degree, quad_scheme=quad_scheme) if sub_element.value_shape() != (): error("Expecting only scalar valued subelement for TensorElement.") # Set default shape if not specified if shape is None: if cell is None: error("Cannot infer tensor shape without a cell.") dim = cell.geometric_dimension() shape = (dim, dim) if symmetry is None: symmetry = EmptyDict elif symmetry is True: # Construct default symmetry dict for matrix elements if not (len(shape) == 2 and shape[0] == shape[1]): error("Cannot set automatic symmetry for non-square tensor.") symmetry = dict(((i, j), (j, i)) for i in range(shape[0]) for j in range(shape[1]) if i > j) else: if not isinstance(symmetry, dict): error("Expecting symmetry to be None (unset), True, or dict.") # Validate indices in symmetry dict for i, j in symmetry.items(): if len(i) != len(j): error("Non-matching length of symmetry index tuples.") for k in range(len(i)): if not (i[k] >= 0 and j[k] >= 0 and i[k] < shape[k] and j[k] < shape[k]): error("Symmetry dimensions out of bounds.") # Compute all index combinations for given shape indices = compute_indices(shape) # Compute mapping from indices to sub element number, # accounting for symmetry sub_elements = [] sub_element_mapping = {} for index in indices: if index in symmetry: continue sub_element_mapping[index] = len(sub_elements) sub_elements += [sub_element] # Update mapping for symmetry for index in indices: if index in symmetry: sub_element_mapping[index] = sub_element_mapping[symmetry[index]] flattened_sub_element_mapping = [sub_element_mapping[index] for i, index in enumerate(indices)] # Compute value shape value_shape = shape # Compute reference value shape based on symmetries if symmetry: # Flatten and subtract symmetries reference_value_shape = (product(shape)-len(symmetry),) self._mapping = "symmetries" else: # Do not flatten if there are no symmetries reference_value_shape = shape self._mapping = "identity" # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape, reference_value_shape=reference_value_shape) self._family = sub_element.family() self._degree = sub_element.degree() self._sub_element = sub_element self._shape = shape self._symmetry = symmetry self._sub_element_mapping = sub_element_mapping self._flattened_sub_element_mapping = flattened_sub_element_mapping # Cache repr string self._repr = "TensorElement(%s, shape=%s, symmetry=%s)" % ( repr(sub_element), repr(self._shape), repr(self._symmetry))
def apply_single_function_pullbacks(g): element = g.ufl_element() mapping = element.mapping() r = ReferenceValue(g) gsh = g.ufl_shape rsh = r.ufl_shape if mapping == "physical": # TODO: Is this right for immersed things assert gsh == rsh return r # Shortcut the "identity" case which includes Expression and # Constant from dolfin that may be ill-formed without a domain # (until we get that fixed) if mapping == "identity": assert rsh == gsh return r gsize = product(gsh) rsize = product(rsh) # Create some geometric objects for reuse domain = g.ufl_domain() J = Jacobian(domain) detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) # Create contravariant transform for reuse (note that detJ is the # _signed_ (pseudo-)determinant) transform_hdiv = (1.0 / detJ) * J # Shortcut simple cases for a more efficient representation, # including directly Piola-mapped elements and mixed elements of # any combination of affinely mapped elements without symmetries if mapping == "symmetries": fcm = element.flattened_sub_element_mapping() assert gsize >= rsize assert len(fcm) == gsize assert sorted(set(fcm)) == sorted(range(rsize)) g_components = [r[fcm[i]] for i in range(gsize)] g_components = reshape_to_nested_list(g_components, gsh) f = as_tensor(g_components) assert f.ufl_shape == g.ufl_shape return f elif mapping == "contravariant Piola": assert transform_hdiv.ufl_shape == (gsize, rsize) i, j = indices(2) f = as_vector(transform_hdiv[i, j] * r[j], i) # f = as_tensor(transform_hdiv[i, j]*r[k,j], (k,i)) # FIXME: Handle Vector(Piola) here? assert f.ufl_shape == g.ufl_shape return f elif mapping == "covariant Piola": assert Jinv.ufl_shape == (rsize, gsize) i, j = indices(2) f = as_vector(Jinv[j, i] * r[j], i) # f = as_tensor(Jinv[j, i]*r[k,j], (k,i)) # FIXME: Handle Vector(Piola) here? assert f.ufl_shape == g.ufl_shape return f elif mapping == "double covariant Piola": i, j, m, n = indices(4) f = as_tensor(Jinv[m, i] * r[m, n] * Jinv[n, j], (i, j)) assert f.ufl_shape == g.ufl_shape return f elif mapping == "double contravariant Piola": i, j, m, n = indices(4) f = as_tensor( (1.0 / detJ) * (1.0 / detJ) * J[i, m] * r[m, n] * J[j, n], (i, j)) assert f.ufl_shape == g.ufl_shape return f elif mapping == "L2 Piola": assert rsh == gsh return r / detJ # By placing components in a list and using as_vector at the end, # we're assuming below that both global function g and its # reference value r have vector shape, which is the case for most # elements with the exceptions: # - TensorElements # - All cases with scalar subelements and without symmetries # are covered by the shortcut above # (ONLY IF REFERENCE VALUE SHAPE PRESERVES TENSOR RANK) # - All cases with scalar subelements and without symmetries are # covered by the shortcut above g_components = [None] * gsize gpos = 0 rpos = 0 r = as_vector([r[idx] for idx in numpy.ndindex(r.ufl_shape)]) for subelm in sub_elements_with_mappings(element): gm = product(subelm.value_shape()) rm = product(subelm.reference_value_shape()) mp = subelm.mapping() if mp == "identity": assert gm == rm for i in range(gm): g_components[gpos + i] = r[rpos + i] elif mp == "symmetries": """ tensor_element.value_shape() == (2,2) tensor_element.reference_value_shape() == (3,) tensor_element.symmetry() == { (1,0): (0,1) } tensor_element.component_mapping() == { (0,0): 0, (0,1): 1, (1,0): 1, (1,1): 2 } tensor_element.flattened_component_mapping() == { 0: 0, 1: 1, 2: 1, 3: 2 } """ fcm = subelm.flattened_sub_element_mapping() assert gm >= rm assert len(fcm) == gm assert sorted(set(fcm)) == sorted(range(rm)) for i in range(gm): g_components[gpos + i] = r[rpos + fcm[i]] elif mp == "contravariant Piola": assert transform_hdiv.ufl_shape == (gm, rm) # Get reference value vector corresponding to this subelement: rv = as_vector([r[rpos + k] for k in range(rm)]) # Apply transform with IndexSum over j for each row j = Index() for i in range(gm): g_components[gpos + i] = transform_hdiv[i, j] * rv[j] elif mp == "covariant Piola": assert Jinv.ufl_shape == (rm, gm) # Get reference value vector corresponding to this subelement: rv = as_vector([r[rpos + k] for k in range(rm)]) # Apply transform with IndexSum over j for each row j = Index() for i in range(gm): g_components[gpos + i] = Jinv[j, i] * rv[j] elif mp == "double covariant Piola": # components are flatten, map accordingly rv = as_vector([r[rpos + k] for k in range(rm)]) (gdim, _) = subelm.value_shape() (rdim, _) = subelm.reference_value_shape() for i in range(gdim): for j in range(gdim): gv = 0 # int times Index is not allowed. so sum by hand for m in range(rdim): for n in range(rdim): gv += Jinv[m, i] * rv[m * rdim + n] * Jinv[n, j] g_components[gpos + i * gdim + j] = gv elif mp == "double contravariant Piola": # components are flatten, map accordingly rv = as_vector([r[rpos + k] for k in range(rm)]) (gdim, _) = subelm.value_shape() (rdim, _) = subelm.reference_value_shape() for i in range(gdim): for j in range(gdim): gv = 0 # int times Index is not allowed. so sum by hand for m in range(rdim): for n in range(rdim): gv += ((1.0 / detJ) * (1.0 / detJ) * J[i, m] * rv[m * rdim + n] * J[j, n]) g_components[gpos + i * gdim + j] = gv elif mp == "L2 Piola": assert gm == rm for i in range(gm): g_components[gpos + i] = r[rpos + i] / detJ else: error("Unknown subelement mapping type %s for element %s." % (mp, str(subelm))) gpos += gm rpos += rm # Wrap up components in a vector, must return same shape as input # function g f = as_tensor(numpy.asarray(g_components).reshape(gsh)) assert f.ufl_shape == g.ufl_shape return f
def _num_components(element): "Compute number of components for element." return product(element.value_shape())
def expression_to_dolfin_expression(expr, generic_function_members, mesh_function_members): "Generates code for a dolfin::Expression subclass for a single expression." # TODO: Make this configurable through global dolfin 'debug mode' parameter? add_runtime_checks = True # Check and flattern provided expression expr, expr_shape = flatten_and_check_expression(expr) # Extract code fragments from the expr generic_function_member_names = [ item[0] for item in generic_function_members ] fragments, members = expression_to_code_fragments( expr, ["values", "x"], generic_function_member_names, mesh_function_members) # Generate code for value_rank value_shape_code = [ " _value_shape.push_back(%d);" % value_dim for value_dim in expr_shape ] evalcode = [] # Runtime checks (TODO: Better to check these when updating the value instead...) if add_runtime_checks: for name, shape in generic_function_members: dim = product(shape) evalcode.append(" if (shared_%s->value_size() != %d)" % (name, dim)) evalcode.append(" dolfin_error(\"generated code\",") evalcode.append(" \"calling eval\", ") evalcode.append( " \"Expecting value size %d for parameter \\'%s\\'\");" % (dim, name)) evalcode.append(" if (shared_%s.get() == this)" % name) evalcode.append(" dolfin_error(\"generated code\",") evalcode.append(" \"calling eval\",") evalcode.append( " \"Circular eval call detected. Cannot use itself as parameter \\'%s\\' within eval\");" % name) evalcode.append("") # Generate code for evaluating genericfunction members for name, shape in generic_function_members: dim = product(shape) # Setup output array and call eval evalcode.append(" double %s__data_[%d];" % (name, dim)) evalcode.append(" Array<double> %s__array_(%d, %s__data_);" % (name, dim, name)) evalcode.append(" shared_%s->eval(%s__array_, x);" % (name, name)) # Ensure const access through userdefined name if shape: # Vector valued result evalcode.append(" const Array<double> & %s = %s__array_;" % (name, name)) else: # Scalar valued result evalcode.append(" const double %s = %s__array_[0];" % (name, name)) evalcode.append("") # Lookup in MeshFunction<typename>(mesh, tdim) for name, typename in mesh_function_members: evalcode.append(" const %s %s = (*shared_%s)[cell.index];" % (typename, name, name)) # Generate code for the actual expression evaluation evalcode.extend(" values[%d] = %s;" % (i, c) for i, c in enumerate(expr)) # Adapt evalcode to with/without cell argument if possible evalcode = "\n".join(evalcode) evalcode_cell = evalcode.replace("__array_, x", "__array_, x, cell") if mesh_function_members: # TODO: Reuse code in Function::eval(values, x) which looks up cell from x? evalcode = [] evalcode.append(" dolfin_error(\"generated code\",") evalcode.append(" \"calling eval\", ") evalcode.append( " \"Need cell to evaluate this Expression\");") evalcode = "\n".join(evalcode) # Connect the code fragments using the expression template code fragments["evalcode"] = evalcode fragments["evalcode_cell"] = evalcode_cell fragments["value_shape"] = "\n".join(value_shape_code) # Assign classname classname = "Expression_" + hashlib.sha1( fragments["evalcode"].encode("utf-8")).hexdigest() fragments["classname"] = classname # Produce the C++ code for the expression class code = _expression_template % fragments return classname, code, members
def split(v): """UFL operator: If v is a Coefficient or Argument in a mixed space, returns a tuple with the function components corresponding to the subelements.""" # Default range is all of v begin = 0 end = None if isinstance(v, Indexed): # Special case: split previous output of split again # Consistent with simple element, just return function in a tuple return (v,) elif isinstance(v, ListTensor): # Special case: split previous output of split again ops = v.ufl_operands if all(isinstance(comp, Indexed) for comp in ops): args = [comp.ufl_operands[0] for comp in ops] if all(args[0] == args[i] for i in range(1, len(args))): # Get innermost terminal here and its element v = args[0] # Get relevant range of v components begin, = ops[0].ufl_operands[1] end, = ops[-1].ufl_operands[1] begin = int(begin) end = int(end) + 1 else: error("Don't know how to split %s." % (v,)) else: error("Don't know how to split %s." % (v,)) # Special case: simple element, just return function in a tuple element = v.ufl_element() if not isinstance(element, MixedElement): assert end is None return (v,) if isinstance(element, TensorElement): if element.symmetry(): error("Split not implemented for symmetric tensor elements.") if len(v.ufl_shape) != 1: error("Don't know how to split tensor valued mixed functions without flattened index space.") # Compute value size and set default range end value_size = product(element.value_shape()) if end is None: end = value_size else: # Recursively dive into mixedelement in to subelement # corresponding to beginning of range j = begin while True: sub_i, j = element.extract_subelement_component(j) element = element.sub_elements()[sub_i] # Then break when we find the subelement that covers the whole range if product(element.value_shape()) == (end - begin): break # Build expressions representing the subfunction of v for each subelement offset = begin sub_functions = [] for i, e in enumerate(element.sub_elements()): # Get shape, size, indices, and v components # corresponding to subelement value shape = e.value_shape() strides = shape_to_strides(shape) rank = len(shape) sub_size = product(shape) subindices = [flatten_multiindex(c, strides) for c in compute_indices(shape)] components = [v[k + offset] for k in subindices] # Shape components into same shape as subelement if rank == 0: subv, = components elif rank <= 1: subv = as_vector(components) elif rank == 2: subv = as_matrix([components[i*shape[1]: (i+1)*shape[1]] for i in range(shape[0])]) else: error("Don't know how to split functions with sub functions of rank %d." % rank) offset += sub_size sub_functions.append(subv) if end != offset: error("Function splitting failed to extract components for whole intended range. Something is wrong.") return tuple(sub_functions)
def __init__(self, *elements, **kwargs): "Create mixed finite element from given list of elements" if type(self) is MixedElement: if kwargs: error("Not expecting keyword arguments to MixedElement constructor.") # Un-nest arguments if we get a single argument with a list of elements if len(elements) == 1 and isinstance(elements[0], (tuple, list)): elements = elements[0] # Interpret nested tuples as sub-mixedelements recursively elements = [MixedElement(e) if isinstance(e, (tuple, list)) else e for e in elements] self._sub_elements = elements # Pick the first cell, for now all should be equal cells = tuple(sorted(set(element.cell() for element in elements) - set([None]))) self._cells = cells if cells: cell = cells[0] # Require that all elements are defined on the same cell if not all(c == cell for c in cells[1:]): error("Sub elements must live on the same cell.") else: cell = None # Check that all elements use the same quadrature scheme TODO: # We can allow the scheme not to be defined. quad_scheme = elements[0].quadrature_scheme() if not all(e.quadrature_scheme() == quad_scheme for e in elements): error("Quadrature scheme mismatch for sub elements of mixed element.") # Compute value sizes in global and reference configurations value_size_sum = sum(product(s.value_shape()) for s in self._sub_elements) reference_value_size_sum = sum(product(s.reference_value_shape()) for s in self._sub_elements) # Default value shape: Treated simply as all subelement values # unpacked in a vector. value_shape = kwargs.get('value_shape', (value_size_sum,)) # Default reference value shape: Treated simply as all # subelement reference values unpacked in a vector. reference_value_shape = kwargs.get('reference_value_shape', (reference_value_size_sum,)) # Validate value_shape (deliberately not for subclasses # VectorElement and TensorElement) if type(self) is MixedElement: # This is not valid for tensor elements with symmetries, # assume subclasses deal with their own validation if product(value_shape) != value_size_sum: error("Provided value_shape doesn't match the " "total value size of all subelements.") # Initialize element data degrees = {e.degree() for e in self._sub_elements} - {None} degree = max_degree(degrees) if degrees else None FiniteElementBase.__init__(self, "Mixed", cell, degree, quad_scheme, value_shape, reference_value_shape) # Cache repr string if type(self) is MixedElement: self._repr = "MixedElement(%s)" % ( ", ".join(repr(e) for e in self._sub_elements),)
def _num_components(element): "Compute number of components for element." return product(element.value_shape())