def __init__(self, *elements, **kwargs): "Create mixed finite element from given list of elements" # 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 # TODO: Figure out proper checks of domain consistency # FIXME: Do something if elements are defined on different regions domains = set(element.domain() for element in elements) - set((None,)) top_domains = set(domain.top_domain() for domain in domains) if len(top_domains) == 0: domain = None elif len(top_domains) == 1: domain = list(top_domains)[0] else: # Require that all elements are defined on the same top-level domain # TODO: is this too strict? disallows mixed elements on different meshes error("Sub elements must live in the same top level domain.") # Check that domains have same geometric dimension if domain is not None: gdim = domain.geometric_dimension() ufl_assert(all(domain.geometric_dimension() == gdim for domain in domains), "Sub elements must live in the same geometric dimension.") # 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() ufl_assert(all(e.quadrature_scheme() == quad_scheme for e in elements),\ "Quadrature scheme mismatch for sub elements of mixed element.") # Compute value shape value_size_sum = sum(product(s.value_shape()) for s in self._sub_elements) # Default value dimension: Treated simply as all subelement # values unpacked in a vector. value_shape = kwargs.get('value_shape', (value_size_sum,)) # Validate value_shape if type(self) is MixedElement: # This is not valid for tensor elements with symmetries, # assume subclasses deal with their own validation ufl_assert(product(value_shape) == value_size_sum, "Provided value_shape doesn't match the total "\ "value size of all subelements.") # Initialize element data degree = max(e.degree() for e in self._sub_elements) super(MixedElement, self).__init__("Mixed", domain, degree, quad_scheme, value_shape) # Cache repr string self._repr = "MixedElement(*%r, **{'value_shape': %r })" %\ (self._sub_elements, self._value_shape)
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 k, e in enumerate(self._sub_elements): sh = e.value_shape() si = product(sh) if j < si: break j -= si ufl_assert(j >= 0, "Moved past last value component!") # Convert index into a shape tuple j = index_to_component(j, sh) else: # Indexing into a multidimensional tensor # where subelement index is first axis k = i[0] ufl_assert(k < len(self._sub_elements), "Illegal component index (dimension %d)." % k) j = i[1:] return (k, j)
def symmetry(self): """Return the symmetry dict, which is a mapping c0 -> c1 meaning that component c0 is represented by component c1. 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() # Map symmetries of subelement into index space of this element for c0, c1 in e.symmetry().iteritems(): j0 = component_to_index(c0, sh) + j j1 = component_to_index(c1, sh) + j sm[(j0,)] = (j1,) # Update base index for next element j += product(sh) ufl_assert(j == product(self.value_shape()), "Size mismatch in symmetry algorithm.") return sm or EmptyDict
def tensor_constant(self, o): #print("\n\nVisiting TensorConstant: " + repr(o)) # Map o to object with proper element and numbering o = self._function_replace_map[o] # Get the components components = self.component() # Safety checks. ffc_assert(len(components) == len(o.shape()), \ "The number of components '%s' must be equal to the number of shapes '%s' for TensorConstant." % (repr(components), repr(o.shape()))) # Let the UFL element handle the component map. component = o.element()._sub_element_mapping[components] # Handle restriction (offset by value shape). if self.restriction == "-": component += product(o.shape()) # Let child class create constant symbol coefficient = format["coefficient"](o.count(), component) return self._create_symbol(coefficient, CONST)
def __new__(cls, *operands): # Make sure everything is an Expr operands = [as_ufl(o) for o in operands] # Make sure everything is scalar #ufl_assert(not any(o.shape() for o in operands), # "Product can only represent products of scalars.") if any(o.shape() for o in operands): error("Product can only represent products of scalars.") # No operands? Return one. if not operands: return IntValue(1) # Got one operand only? Just return it. if len(operands) == 1: return operands[0] # Got any zeros? Return zero. if any(isinstance(o, Zero) for o in operands): free_indices = unique_indices(tuple(chain(*(o.free_indices() for o in operands)))) index_dimensions = subdict(mergedicts([o.index_dimensions() for o in operands]), free_indices) return Zero((), free_indices, index_dimensions) # Merge scalars, but keep nonscalars sorted scalars = [] nonscalars = [] for o in operands: if isinstance(o, ScalarValue): scalars.append(o) else: nonscalars.append(o) if scalars: # merge scalars p = as_ufl(product(s._value for s in scalars)) # only scalars? if not nonscalars: return p # merged scalar is unity? if p == 1: scalars = [] # Left with one nonscalar operand only after merging scalars? if len(nonscalars) == 1: return nonscalars[0] else: scalars = [p] # Sort operands in a canonical order (NB! This is fragile! Small changes here can have large effects.) operands = scalars + sorted_expr(nonscalars) # Replace n-repeated operands foo with foo**n newoperands = [] op, nop = operands[0], 1 for o in operands[1:] + [None]: if o == op: # op is repeated, count number of repetitions nop += 1 else: if nop == 1: # op is not repeated newoperands.append(op) elif op.free_indices(): # We can't simplify products to powers if the operands has # free indices, because of complications in differentiation. # op repeated, but has free indices, so we don't simplify newoperands.extend([op]*nop) else: # op repeated, make it a power newoperands.append(op**nop) # Reset op as o op, nop = o, 1 operands = newoperands # Left with one operand only after simplifications? if len(operands) == 1: return operands[0] # Construct and initialize a new Product object self = AlgebraOperator.__new__(cls) self._init(*operands) return self
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.""" # Special case: simple element, just return function in a tuple element = v.element() if not isinstance(element, MixedElement): return (v, ) if isinstance(element, TensorElement): s = element.symmetry() if s: # FIXME: How should this be defined? Should we return one subfunction # for each value component or only for those not mapped to another? # I think split should ignore the symmetry. error("Split not implemented for symmetric tensor elements.") # Compute value size value_size = product(element.value_shape()) actual_value_size = value_size # Extract sub coefficient offset = 0 sub_functions = [] for i, e in enumerate(element.sub_elements()): shape = e.value_shape() rank = len(shape) if rank == 0: # This subelement is a scalar, always maps to a single value subv = v[offset] offset += 1 elif rank == 1: # This subelement is a vector, always maps to a sequence of values sub_size, = shape components = [v[j] for j in range(offset, offset + sub_size)] subv = as_vector(components) offset += sub_size elif rank == 2: # This subelement is a tensor, possibly with symmetries, slightly more complicated... # Size of this subvalue sub_size = product(shape) # If this subelement is a symmetric element, subtract symmetric components s = None if isinstance(e, TensorElement): s = e.symmetry() s = s or EmptyDict # If we do this, we must fix the size computation in MixedElement.__init__ as well #actual_value_size -= len(s) #sub_size -= len(s) #print s # Build list of lists of value components components = [] for ii in range(shape[0]): row = [] for jj in range(shape[1]): # Map component (i,j) through symmetry mapping c = (ii, jj) c = s.get(c, c) i, j = c # Extract component c of this subvalue from global tensor v if v.rank() == 1: # Mapping into a flattened vector k = offset + i * shape[1] + j component = v[k] #print "k, offset, i, j, shape, component", k, offset, i, j, shape, component elif v.rank() == 2: # Mapping into a concatenated tensor (is this a figment of my imagination?) error("Not implemented.") row_offset, col_offset = 0, 0 # TODO k = (row_offset + i, col_offset + j) component = v[k] row.append(component) components.append(row) # Make a matrix of the components subv = as_matrix(components) offset += sub_size else: # TODO: Handle rank > 2? Or is there such a thing? error( "Don't know how to split functions with sub functions of rank %d (yet)." % rank) #for indices in compute_indices(shape): # #k = offset + sum(i*s for (i,s) in izip(indices, shape[1:] + (1,))) # vs.append(v[indices]) sub_functions.append(subv) ufl_assert(actual_value_size == offset, "Logic breach in function splitting.") return tuple(sub_functions)
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.""" # Special case: simple element, just return function in a tuple element = v.element() if not isinstance(element, MixedElement): return (v,) if isinstance(element, TensorElement): s = element.symmetry() if s: # FIXME: How should this be defined? Should we return one subfunction # for each value component or only for those not mapped to another? # I think split should ignore the symmetry. error("Split not implemented for symmetric tensor elements.") # Compute value size value_size = product(element.value_shape()) actual_value_size = value_size # Extract sub coefficient offset = 0 sub_functions = [] for i, e in enumerate(element.sub_elements()): shape = e.value_shape() rank = len(shape) if rank == 0: # This subelement is a scalar, always maps to a single value subv = v[offset] offset += 1 elif rank == 1: # This subelement is a vector, always maps to a sequence of values sub_size, = shape components = [v[j] for j in range(offset, offset + sub_size)] subv = as_vector(components) offset += sub_size elif rank == 2: # This subelement is a tensor, possibly with symmetries, slightly more complicated... # Size of this subvalue sub_size = product(shape) # If this subelement is a symmetric element, subtract symmetric components s = None if isinstance(e, TensorElement): s = e.symmetry() s = s or EmptyDict # If we do this, we must fix the size computation in MixedElement.__init__ as well #actual_value_size -= len(s) #sub_size -= len(s) #print s # Build list of lists of value components components = [] for ii in range(shape[0]): row = [] for jj in range(shape[1]): # Map component (i,j) through symmetry mapping c = (ii, jj) c = s.get(c, c) i, j = c # Extract component c of this subvalue from global tensor v if v.rank() == 1: # Mapping into a flattened vector k = offset + i*shape[1] + j component = v[k] #print "k, offset, i, j, shape, component", k, offset, i, j, shape, component elif v.rank() == 2: # Mapping into a concatenated tensor (is this a figment of my imagination?) error("Not implemented.") row_offset, col_offset = 0, 0 # TODO k = (row_offset + i, col_offset + j) component = v[k] row.append(component) components.append(row) # Make a matrix of the components subv = as_matrix(components) offset += sub_size else: # TODO: Handle rank > 2? Or is there such a thing? error("Don't know how to split functions with sub functions of rank %d (yet)." % rank) #for indices in compute_indices(shape): # #k = offset + sum(i*s for (i,s) in izip(indices, shape[1:] + (1,))) # vs.append(v[indices]) sub_functions.append(subv) ufl_assert(actual_value_size == offset, "Logic breach in function splitting.") return tuple(sub_functions)