def product(self, o, ops): if len(ops) != 2: error("Expecting two operands.") # Get the simple cases out of the way if len(ops[0]) == 1: # True scalar * something a, = ops[0] return [Product(a, b) for b in ops[1]] elif len(ops[1]) == 1: # Something * true scalar b, = ops[1] return [Product(a, b) for a in ops[0]] # Neither of operands are true scalars, this is the tricky part o0, o1 = o.ufl_operands # Get shapes and index shapes fi = o.ufl_free_indices fi0 = o0.ufl_free_indices fi1 = o1.ufl_free_indices fid = o.ufl_index_dimensions fid0 = o0.ufl_index_dimensions fid1 = o1.ufl_index_dimensions # Need to map each return component to one component of o0 and one component of o1. indices = compute_indices(fid) # Compute which component of o0 is used in component (comp,ind) of o # Compute strides within free index spaces ist0 = shape_to_strides(fid0) ist1 = shape_to_strides(fid1) # Map o0 and o1 indices to o indices indmap0 = [fi.index(i) for i in fi0] indmap1 = [fi.index(i) for i in fi1] indks = [(flatten_multiindex([ind[i] for i in indmap0], ist0), flatten_multiindex([ind[i] for i in indmap1], ist1)) for ind in indices] # Build products for scalar components results = [Product(ops[0][k0], ops[1][k1]) for k0, k1 in indks] return results
def form_argument(self, v, i): "Create new symbols for expressions that represent new values." symmetry = v.ufl_element().symmetry() if symmetry: # Build symbols with symmetric components skipped symbols = [] mapped_symbols = {} for c in compute_indices(v.ufl_shape): # Build mapped component mc with symmetries from element considered mc = symmetry.get(c, c) # Get existing symbol or create new and store with mapped component mc as key s = mapped_symbols.get(mc) if s is None: s = self.new_symbol() mapped_symbols[mc] = s symbols.append(s) else: n = self.V_sizes[i] symbols = self.new_symbols(n) return symbols
def dependency_sorting(deplist, rank): #print "deplist = ", deplist def split(deps, state): left = [] todo = [] for dep in deps: if dep - state: left.append(dep) else: todo.append(dep) return todo, left deplistlist = [] state = set() left = deplist # --- Initialization time #state.remove("x") precompute, left = split(left, state) deplistlist.append(precompute) state.add("x") precompute_quad, left = split(left, state) deplistlist.append(precompute_quad) # Permutations of 0/1 dependence of arguments indices = compute_indices((2, ) * rank) for bfs in indices[1:]: # skip (0,...,0), already handled that for i, bf in reversed(list(enumerate(bfs))): n = "v%d" % i if bf: if n in state: state.remove(n) else: state.add(n) next, left = split(left, state) deplistlist.append(next) # --- Runtime state.add("c") state.add("w") state.remove("x") runtime, left = split(left, state) deplistlist.append(runtime) state.add("x") runtime_quad, left = split(left, state) deplistlist.append(runtime_quad) indices = compute_indices((2, ) * rank) for bfs in indices[1:]: # skip (0,...,0), already handled that for i, bf in reversed(list(enumerate(bfs))): n = "v%d" % i if bf: state.add(n) else: if n in state: state.remove(n) next, left = split(left, state) deplistlist.append(next) ufl_assert(not left, "Shouldn't have anything left!") #print #print "Created deplistlist:" #for deps in deplistlist: # print # print "--- New stage:" # print "\n".join(map(str, deps)) #print return deplistlist
def dependency_sorting(deplist, rank): def split(deps, state): left = [] todo = [] for dep in deps: if dep - state: left.append(dep) else: todo.append(dep) return todo, left deplistlist = [] state = set() left = deplist # --- Initialization time precompute, left = split(left, state) deplistlist.append(precompute) state.add("x") precompute_quad, left = split(left, state) deplistlist.append(precompute_quad) # Permutations of 0/1 dependence of arguments indices = compute_indices((2,)*rank) for bfs in indices[1:]: # skip (0,...,0), already handled that for i, bf in reversed(enumerate(bfs)): n = "v%d" % i if bf: if n in state: state.remove(n) else: state.add(n) next, left = split(left, state) deplistlist.append(next) # --- Runtime state.add("c") state.add("w") state.remove("x") runtime, left = split(left, state) deplistlist.append(runtime) state.add("x") runtime_quad, left = split(left, state) deplistlist.append(runtime_quad) indices = compute_indices((2,)*rank) for bfs in indices[1:]: # skip (0,...,0), already handled that for i, bf in reversed(enumerate(bfs)): n = "v%d" % i if bf: state.add(n) else: if n in state: state.remove(n) next, left = split(left, state) deplistlist.append(next) if left: error("Shouldn't have anything left!") return deplistlist
def map_component_tensor_arg_components(tensor): """Build a map from flattened components to subexpression. Builds integer list mapping between flattended components of tensor and its underlying indexed subexpression.""" assert isinstance(tensor, ComponentTensor) # AKA tensor = as_tensor(indexed, multiindex) indexed, multiindex = tensor.ufl_operands e1 = indexed e2 = tensor # e2 = as_tensor(e1, multiindex) mi = [i for i in multiindex if isinstance(i, Index)] # Get tensor and index shapes sh1 = e1.ufl_shape # (sh)ape of e1 sh2 = e2.ufl_shape # (sh)ape of e2 fi1 = e1.ufl_free_indices # (f)ree (i)ndices of e1 fi2 = e2.ufl_free_indices # ... fid1 = e1.ufl_index_dimensions # (f)ree (i)ndex (d)imensions of e1 fid2 = e2.ufl_index_dimensions # ... # Compute total shape (tsh) of e1 and e2 tsh1 = sh1 + fid1 tsh2 = sh2 + fid2 r1 = len(tsh1) # 'total rank' or e1 r2 = len(tsh2) # ... str1 = shape_to_strides(tsh1) assert not sh1 assert sh2 assert len(mi) == len(multiindex) assert ufl.product(tsh1) == ufl.product(tsh2) assert fi1 assert all(i in fi1 for i in fi2) nmui = len(multiindex) assert nmui == len(sh2) # Build map from fi2/fid2 position (-offset nmui) to fi1/fid1 position p2_to_p1_map = [None] * r2 for k, i in enumerate(fi2): p2_to_p1_map[k + nmui] = fi1.index(i) # Build map from fi1/fid1 position to mi position for k, i in enumerate(mi): p2_to_p1_map[k] = fi1.index(mi[k].count()) # Build map from flattened e1 component to flattened e2 component perm2 = compute_indices(tsh2) ni2 = ufl.product(tsh2) # Situation: e2 = as_tensor(e1, mi) d2 = [None] * ni2 p1 = [None] * r1 for c2, p2 in enumerate(perm2): for k2, k1 in enumerate(p2_to_p1_map): p1[k1] = p2[k2] c1 = flatten_multiindex(p1, str1) d2[c2] = c1 # Consistency checks assert all(isinstance(x, int) for x in d2) assert len(set(d2)) == len(d2) return d2
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 __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 map_indexed_arg_components(indexed): """Build a map from flattened components to subexpression. Builds integer list mapping between flattened components of indexed expression and its underlying tensor-valued subexpression.""" assert isinstance(indexed, Indexed) # AKA indexed = tensor[multiindex] tensor, multiindex = indexed.ufl_operands # AKA e1 = e2[multiindex] # (this renaming is historical, but kept for consistency with all the variables *1,*2 below) e2 = tensor e1 = indexed # Get tensor and index shape sh1 = e1.ufl_shape sh2 = e2.ufl_shape fi1 = e1.ufl_free_indices fi2 = e2.ufl_free_indices fid1 = e1.ufl_index_dimensions fid2 = e2.ufl_index_dimensions # Compute regular and total shape tsh1 = sh1 + fid1 tsh2 = sh2 + fid2 # r1 = len(tsh1) r2 = len(tsh2) # str1 = shape_to_strides(tsh1) str2 = shape_to_strides(tsh2) assert not sh1 assert sh2 # Must have shape to be indexed in the first place assert ufl.product(tsh1) <= ufl.product(tsh2) # Build map from fi2/fid2 position (-offset nmui) to fi1/fid1 position ind2_to_ind1_map = [None] * len(fi2) for k, i in enumerate(fi2): ind2_to_ind1_map[k] = fi1.index(i) # Build map from fi1/fid1 position to mi position nmui = len(multiindex) multiindex_to_ind1_map = [None] * nmui for k, i in enumerate(multiindex): if isinstance(i, Index): multiindex_to_ind1_map[k] = fi1.index(i.count()) # Build map from flattened e1 component to flattened e2 component perm1 = compute_indices(tsh1) ni1 = ufl.product(tsh1) # Situation: e1 = e2[mi] d1 = [None] * ni1 p2 = [None] * r2 assert len(sh2) == nmui for k, i in enumerate(multiindex): if isinstance(i, FixedIndex): p2[k] = int(i) for c1, p1 in enumerate(perm1): for k, i in enumerate(multiindex): if isinstance(i, Index): p2[k] = p1[multiindex_to_ind1_map[k]] for k, i in enumerate(ind2_to_ind1_map): p2[nmui + k] = p1[i] c2 = flatten_multiindex(p2, str2) d1[c1] = c2 # Consistency checks assert all(isinstance(x, int) for x in d1) assert len(set(d1)) == len(d1) return d1
def rebuild_with_scalar_subexpressions(G, targets=None): """Build a new expression2index mapping where each subexpression is scalar valued. Input: - G.e2i - G.V - G.V_symbols - G.total_unique_symbols Output: - NV - Array with reverse mapping from index to expression - nvs - Tuple of ne2i indices corresponding to the last vertex of G.V Old output now no longer returned but possible to restore if needed: - ne2i - Mapping from scalar subexpressions to a contiguous unique index - W - Array with reconstructed scalar subexpressions for each original symbol """ # From simplefsi3d.ufl: # GRAPH SIZE: len(G.V), G.total_unique_symbols # GRAPH SIZE: 16251 635272 # GRAPH SIZE: 473 8210 # GRAPH SIZE: 9663 238021 # GRAPH SIZE: 88913 3448634 # 3.5 M!!! # Algorithm to apply to each subexpression reconstruct_scalar_subexpressions = ReconstructScalarSubexpressions() # Array to store the scalar subexpression in for each symbol W = numpy.empty(G.total_unique_symbols, dtype=object) # Iterate over each graph node in order for i, v in enumerate(G.V): # Find symbols of v components vs = G.V_symbols[i] # Skip if there's nothing new here (should be the case for indexing types) if all(W[s] is not None for s in vs): continue if is_modified_terminal(v): # if v.ufl_free_indices: # error("Expecting no free indices.") sh = v.ufl_shape if sh: # Store each terminal expression component. # We may not actually need all of these later, # but that will be optimized away. # Note: symmetries will be dealt with in the value numbering. ws = [v[c] for c in compute_indices(sh)] else: # Store single modified terminal expression component if len(vs) != 1: error("Expecting single symbol for scalar valued modified terminal.") ws = [v] # FIXME: Replace ws[:] with 0's if its table is empty # Possible redesign: loop over modified terminals only first, # then build tables for them, set W[s] = 0.0 for modified terminals with zero table, # then loop over non-(modified terminal)s to reconstruct expression. else: # Find symbols of operands sops = [] for j, vop in enumerate(v.ufl_operands): if isinstance(vop, MultiIndex): # TODO: Store MultiIndex in G.V and allocate a symbol to it for this to work if not isinstance(v, IndexSum): error("Not expecting a %s." % type(v)) sops.append(()) else: # TODO: Build edge datastructure and use instead? # k = G.E[i][j] k = G.e2i[vop] sops.append(G.V_symbols[k]) # Fetch reconstructed operand expressions wops = [tuple(W[k] for k in so) for so in sops] # Reconstruct scalar subexpressions of v ws = reconstruct_scalar_subexpressions(v, wops) # Store all scalar subexpressions for v symbols if len(vs) != len(ws): error("Expecting one symbol for each expression.") # Store each new scalar subexpression in W at the index of its symbol handled = set() for s, w in zip(vs, ws): if W[s] is None: W[s] = w handled.add(s) else: assert s in handled # Result of symmetry! # Find symbols of requested targets or final v from input graph if targets is None: targets = [G.V[-1]] # Attempt to extend this to multiple target expressions scalar_target_expressions = [] for target in targets: ti = G.e2i[target] vs = G.V_symbols[ti] # Sanity check: assert that we've handled these symbols if any(W[s] is None for s in vs): error("Expecting that all symbols in vs are handled at this point.") scalar_target_expressions.append([W[s] for s in vs]) # Return the scalar expressions for each of the components assert len(scalar_target_expressions) == 1 # TODO: Currently expected by callers, fix those first return scalar_target_expressions[0] # ... TODO: then return list
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 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 _modified_terminal(self, v, i): """Modifiers: terminal - the underlying Terminal object global_derivatives - tuple of ints, each meaning derivative in that global direction local_derivatives - tuple of ints, each meaning derivative in that local direction reference_value - bool, whether this is represented in reference frame averaged - None, 'facet' or 'cell' restriction - None, '+' or '-' component - tuple of ints, the global component of the Terminal flat_component - single int, flattened local component of the Terminal, considering symmetry """ # (1) mt.terminal.ufl_shape defines a core indexing space UNLESS mt.reference_value, # in which case the reference value shape of the element must be used. # (2) mt.terminal.ufl_element().symmetry() defines core symmetries # (3) averaging and restrictions define distinct symbols, no additional symmetries # (4) two or more grad/reference_grad defines distinct symbols with additional symmetries # v is not necessary scalar here, indexing in (0,...,0) picks the first scalar component # to analyse, which should be sufficient to get the base shape and derivatives if v.ufl_shape: mt = analyse_modified_terminal(v[(0,) * len(v.ufl_shape)]) else: mt = analyse_modified_terminal(v) # Get derivatives num_ld = len(mt.local_derivatives) num_gd = len(mt.global_derivatives) assert not (num_ld and num_gd) if num_ld: domain = mt.terminal.ufl_domain() tdim = domain.topological_dimension() d_components = compute_indices((tdim,) * num_ld) elif num_gd: domain = mt.terminal.ufl_domain() gdim = domain.geometric_dimension() d_components = compute_indices((gdim,) * num_gd) else: d_components = [()] # Get base shape without the derivative axes base_components = compute_indices(mt.base_shape) # Build symbols with symmetric components and derivatives skipped symbols = [] mapped_symbols = {} for bc in base_components: for dc in d_components: # Build mapped component mc with symmetries from element and derivatives combined mbc = mt.base_symmetry.get(bc, bc) mdc = tuple(sorted(dc)) mc = mbc + mdc # Get existing symbol or create new and store with mapped component mc as key s = mapped_symbols.get(mc) if s is None: s = self.new_symbol() mapped_symbols[mc] = s symbols.append(s) # Consistency check before returning symbols assert not v.ufl_free_indices if product(v.ufl_shape) != len(symbols): error("Internal error in value numbering.") return symbols
def _mapped(self, t): # Check that we have a valid input object if not isinstance(t, Terminal): error("Expecting a Terminal.") # Get modifiers accumulated by previous handler calls ngrads = self._ngrads restricted = self._restricted avg = self._avg if avg != "": error("Averaging not implemented.") # FIXME # These are the global (g) and reference (r) values if isinstance(t, FormArgument): g = t r = ReferenceValue(g) elif isinstance(t, GeometricQuantity): g = t r = g else: error("Unexpected type {0}.".format(type(t).__name__)) # Some geometry mapping objects we may need multiple times below domain = t.ufl_domain() J = Jacobian(domain) detJ = JacobianDeterminant(domain) K = JacobianInverse(domain) # Restrict geometry objects if applicable if restricted: J = J(restricted) detJ = detJ(restricted) K = K(restricted) # Create Hdiv mapping from possibly restricted geometry objects Mdiv = (1.0 / detJ) * J # Get component indices of global and reference terminal objects gtsh = g.ufl_shape # rtsh = r.ufl_shape gtcomponents = compute_indices(gtsh) # rtcomponents = compute_indices(rtsh) # Create core modified terminal, with eventual # layers of grad applied directly to the terminal, # then eventual restriction applied last for i in range(ngrads): g = Grad(g) r = ReferenceGrad(r) if restricted: g = g(restricted) r = r(restricted) # Get component indices of global and reference objects with # grads applied gsh = g.ufl_shape # rsh = r.ufl_shape # gcomponents = compute_indices(gsh) # rcomponents = compute_indices(rsh) # Get derivative component indices dsh = gsh[len(gtsh):] dcomponents = compute_indices(dsh) # Create nested array to hold expressions for global # components mapped from reference values def ndarray(shape): if len(shape) == 0: return [None] elif len(shape) == 1: return [None] * shape[-1] else: return [ndarray(shape[1:]) for i in range(shape[0])] global_components = ndarray(gsh) # Compute mapping from reference values for each global component for gtc in gtcomponents: if isinstance(t, FormArgument): # Find basic subelement and element-local component # ec, element, eoffset = t.ufl_element().extract_component2(gtc) # FIXME: Translate this correctly eoffset = 0 ec, element = t.ufl_element().extract_reference_component(gtc) # Select mapping M from element, pick row emapping = # M[ec,:], or emapping = [] if no mapping if isinstance(element, MixedElement): error("Expecting a basic element here.") mapping = element.mapping() if mapping == "contravariant Piola": # S == HDiv: # Handle HDiv elements with contravariant piola # mapping contravariant_hdiv_mapping = (1/det J) * # J * PullbackOf(o) ec, = ec emapping = Mdiv[ec, :] elif mapping == "covariant Piola": # S == HCurl: # Handle HCurl elements with covariant piola mapping # covariant_hcurl_mapping = JinvT * PullbackOf(o) ec, = ec emapping = K[:, ec] # Column of K is row of K.T elif mapping == "identity": emapping = None else: error("Unknown mapping {0}".format(mapping)) elif isinstance(t, GeometricQuantity): eoffset = 0 emapping = None else: error("Unexpected type {0}.".format(type(t).__name__)) # Create indices # if rtsh: # i = Index() if len(dsh) != ngrads: error("Mismatch between derivative shape and ngrads.") if ngrads: ii = indices(ngrads) else: ii = () # Apply mapping row to reference object if emapping: # Mapped, always nonscalar terminal Not # using IndexSum for the mapping row dot product to # keep it simple, because we don't have a slice type emapped_ops = [emapping[s] * Indexed(r, MultiIndex((FixedIndex(eoffset + s),) + ii)) for s in range(len(emapping))] emapped = sum(emapped_ops[1:], emapped_ops[0]) elif gtc: # Nonscalar terminal, unmapped emapped = Indexed(r, MultiIndex((FixedIndex(eoffset),) + ii)) elif ngrads: # Scalar terminal, unmapped, with derivatives emapped = Indexed(r, MultiIndex(ii)) else: # Scalar terminal, unmapped, no derivatives emapped = r for di in dcomponents: # Multiply derivative mapping rows, parameterized by # free column indices dmapping = as_ufl(1) for j in range(ngrads): dmapping *= K[ii[j], di[j]] # Row of K is column of JinvT # Compute mapping from reference values for this # particular global component global_value = dmapping * emapped # Apply index sums # if rtsh: # global_value = IndexSum(global_value, MultiIndex((i,))) # for j in range(ngrads): # Applied implicitly in the dmapping * emapped above # global_value = IndexSum(global_value, MultiIndex((ii[j],))) # This is the component index into the full object # with grads applied gc = gtc + di # Insert in nested list comp = global_components for i in gc[:-1]: comp = comp[i] comp[0 if gc == () else gc[-1]] = global_value # Wrap nested list in as_tensor unless we have a scalar # expression if gsh: tensor = as_tensor(global_components) else: tensor, = global_components return tensor
def dependency_sorting(deplist, rank): #print "deplist = ", deplist def split(deps, state): left = [] todo = [] for dep in deps: if dep - state: left.append(dep) else: todo.append(dep) return todo, left deplistlist = [] state = set() left = deplist # --- Initialization time #state.remove("x") precompute, left = split(left, state) deplistlist.append(precompute) state.add("x") precompute_quad, left = split(left, state) deplistlist.append(precompute_quad) # Permutations of 0/1 dependence of arguments indices = compute_indices((2,)*rank) for bfs in indices[1:]: # skip (0,...,0), already handled that for i, bf in reversed(list(enumerate(bfs))): n = "v%d" % i if bf: if n in state: state.remove(n) else: state.add(n) next, left = split(left, state) deplistlist.append(next) # --- Runtime state.add("c") state.add("w") state.remove("x") runtime, left = split(left, state) deplistlist.append(runtime) state.add("x") runtime_quad, left = split(left, state) deplistlist.append(runtime_quad) indices = compute_indices((2,)*rank) for bfs in indices[1:]: # skip (0,...,0), already handled that for i, bf in reversed(list(enumerate(bfs))): n = "v%d" % i if bf: state.add(n) else: if n in state: state.remove(n) next, left = split(left, state) deplistlist.append(next) ufl_assert(not left, "Shouldn't have anything left!") #print #print "Created deplistlist:" #for deps in deplistlist: # print # print "--- New stage:" # print "\n".join(map(str, deps)) #print return deplistlist
def __init__(self, family, domain, degree, shape=None, symmetry=None, quad_scheme=None): "Create tensor element (repeated mixed element with optional symmetries)" if domain is not None: domain = as_domain(domain) # Set default shape if not specified if shape is None: ufl_assert(domain is not None, "Cannot infer vector dimension without a domain.") dim = domain.geometric_dimension() shape = (dim, dim) # Construct default symmetry for matrix elements if symmetry == True: ufl_assert(len(shape) == 2 and shape[0] == shape[1], "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 ) # Validate indices in symmetry dict if isinstance(symmetry, dict): for i,j in symmetry.iteritems(): ufl_assert(len(i) == len(j), "Non-matching length of symmetry index tuples.") for k in range(len(i)): ufl_assert(i[k] >= 0 and j[k] >= 0 and i[k] < shape[k] and j[k] < shape[k], "Symmetry dimensions out of bounds.") else: ufl_assert(symmetry is None, "Expecting symmetry to be None, True, or dict.") # Compute all index combinations for given shape indices = compute_indices(shape) # Compute sub elements and mapping from indices # to sub elements, accounting for symmetry sub_element = FiniteElement(family, domain, degree, quad_scheme) sub_elements = [] sub_element_mapping = {} for index in indices: if symmetry and index in symmetry: continue sub_element_mapping[index] = len(sub_elements) sub_elements += [sub_element] # Update mapping for symmetry for index in indices: if symmetry and index in symmetry: sub_element_mapping[index] = sub_element_mapping[symmetry[index]] # Get common family name (checked in FiniteElement.__init__) family = sub_element.family() # Compute value shape value_shape = shape + sub_element.value_shape() # Initialize element data super(TensorElement, self).__init__(sub_elements, value_shape=value_shape) self._family = family self._degree = degree self._sub_element = sub_element self._shape = shape self._symmetry = symmetry self._sub_element_mapping = sub_element_mapping # Cache repr string self._repr = "TensorElement(%r, %r, %r, %r, %r, %r)" % \ (self._family, self._domain, self._degree, self._shape, self._symmetry, quad_scheme)
def map_component_tensor_arg_components(tensor): """Build integer list mapping between flattended components of tensor and its underlying indexed subexpression.""" assert isinstance(tensor, ComponentTensor) # AKA tensor = as_tensor(indexed, multiindex) indexed, multiindex = tensor.ufl_operands e1 = indexed e2 = tensor # e2 = as_tensor(e1, multiindex) mi = [i for i in multiindex if isinstance(i, Index)] # Get tensor and index shapes sh1 = e1.ufl_shape # (sh)ape of e1 sh2 = e2.ufl_shape # (sh)ape of e2 fi1 = e1.ufl_free_indices # (f)ree (i)ndices of e1 fi2 = e2.ufl_free_indices # ... fid1 = e1.ufl_index_dimensions # (f)ree (i)ndex (d)imensions of e1 fid2 = e2.ufl_index_dimensions # ... # Compute total shape (tsh) of e1 and e2 tsh1 = sh1 + fid1 tsh2 = sh2 + fid2 r1 = len(tsh1) # 'total rank' or e1 r2 = len(tsh2) # ... str1 = shape_to_strides(tsh1) assert not sh1 assert sh2 assert len(mi) == len(multiindex) assert product(tsh1) == product(tsh2) assert fi1 assert all(i in fi1 for i in fi2) nmui = len(multiindex) assert nmui == len(sh2) # Build map from fi2/fid2 position (-offset nmui) to fi1/fid1 position p2_to_p1_map = [None] * r2 for k, i in enumerate(fi2): p2_to_p1_map[k + nmui] = fi1.index(i) # Build map from fi1/fid1 position to mi position for k, i in enumerate(mi): p2_to_p1_map[k] = fi1.index(mi[k].count()) # Build map from flattened e1 component to flattened e2 component perm2 = compute_indices(tsh2) ni2 = product(tsh2) # Situation: e2 = as_tensor(e1, mi) d2 = [None] * ni2 p1 = [None] * r1 for c2, p2 in enumerate(perm2): for k2, k1 in enumerate(p2_to_p1_map): p1[k1] = p2[k2] c1 = flatten_multiindex(p1, str1) d2[c2] = c1 # Consistency checks assert all(isinstance(x, int) for x in d2) assert len(set(d2)) == len(d2) return d2
def map_indexed_arg_components(indexed): """Build integer list mapping between flattended components of indexed expression and its underlying tensor-valued subexpression.""" assert isinstance(indexed, Indexed) # AKA indexed = tensor[multiindex] tensor, multiindex = indexed.ufl_operands # AKA e1 = e2[multiindex] # (this renaming is historical, but kept for consistency with all the variables *1,*2 below) e2 = tensor e1 = indexed # Get tensor and index shape sh1 = e1.ufl_shape sh2 = e2.ufl_shape fi1 = e1.ufl_free_indices fi2 = e2.ufl_free_indices fid1 = e1.ufl_index_dimensions fid2 = e2.ufl_index_dimensions # Compute regular and total shape tsh1 = sh1 + fid1 tsh2 = sh2 + fid2 # r1 = len(tsh1) r2 = len(tsh2) # str1 = shape_to_strides(tsh1) str2 = shape_to_strides(tsh2) assert not sh1 assert sh2 # Must have shape to be indexed in the first place assert product(tsh1) <= product(tsh2) # Build map from fi2/fid2 position (-offset nmui) to fi1/fid1 position ind2_to_ind1_map = [None] * len(fi2) for k, i in enumerate(fi2): ind2_to_ind1_map[k] = fi1.index(i) # Build map from fi1/fid1 position to mi position nmui = len(multiindex) multiindex_to_ind1_map = [None] * nmui for k, i in enumerate(multiindex): if isinstance(i, Index): multiindex_to_ind1_map[k] = fi1.index(i.count()) # Build map from flattened e1 component to flattened e2 component perm1 = compute_indices(tsh1) ni1 = product(tsh1) # Situation: e1 = e2[mi] d1 = [None] * ni1 p2 = [None] * r2 assert len(sh2) == nmui for k, i in enumerate(multiindex): if isinstance(i, FixedIndex): p2[k] = int(i) for c1, p1 in enumerate(perm1): for k, i in enumerate(multiindex): if isinstance(i, Index): p2[k] = p1[multiindex_to_ind1_map[k]] for k, i in enumerate(ind2_to_ind1_map): p2[nmui + k] = p1[i] c2 = flatten_multiindex(p2, str2) d1[c1] = c2 # Consistency checks assert all(isinstance(x, int) for x in d1) assert len(set(d1)) == len(d1) return d1