def dicts_lt(a, b): na = 0 if a is None else len(a) nb = 0 if b is None else len(b) if na != nb: return len(a) < len(b) for ia, ib in zip(sorted_by_key(a), sorted_by_key(b)): # Assuming keys are sortable (usually str) if ia[0] != ib[0]: return (ia[0].__class__.__name__, ia[0]) < (ib[0].__class__.__name__, ib[0]) # Hack to preserve type sorting in py3 # Assuming values are sortable if ia[1] != ib[1]: return (ia[1].__class__.__name__, ia[1]) < (ib[1].__class__.__name__, ib[1]) # Hack to preserve type sorting in py3
def _extract_entity_dofs(element, indices): # FIXME: Readability counts entity_dofs = element.entity_dofs() dofs = {} for (dim, entities) in sorted_by_key(entity_dofs): dofs[dim] = {} for (entity, all_dofs) in sorted_by_key(entities): dofs[dim][entity] = [] for index in all_dofs: if index in indices: # print "index = ", index i = indices.index(index) dofs[dim][entity] += [i] return dofs
def get_var_occurrences(self): """Determine the number of minimum number of times all variables occurs in the expression. Returns a dictionary of variables and the number of times they occur. x*x + x returns {x:1}, x + y returns {}. """ # NOTE: This function is only used if the numerator of a # Fraction is a Sum. # Get occurrences in first expression. d0 = self.vrs[0].get_var_occurrences() for var in self.vrs[1:]: # Get the occurrences. d = var.get_var_occurrences() # Delete those variables in d0 that are not in d. for k, v in list(d0.items()): if k not in d: del d0[k] # Set the number of occurrences equal to the smallest # number. for k, v in sorted_by_key(d): if k in d0: d0[k] = min(d0[k], v) return d0
def reduce_vartype(self, var_type): """Reduce expression with given var_type. It returns a list of tuples [(found, remain)], where 'found' is an expression that only has variables of type == var_type. If no variables are found, found=(). The 'remain' part contains the leftover after division by 'found' such that: self = Sum([f*r for f,r in self.reduce_vartype(Type)]). """ found = {} # Loop members and reduce them by vartype. for v in self.vrs: for f, r in v.reduce_vartype(var_type): if f in found: found[f].append(r) else: found[f] = [r] # Create the return value. returns = [] for f, r in sorted_by_key(found): if len(r) > 1: # Use expand to group expressions. r = create_sum(r) elif r: r = r.pop() returns.append((f, r)) return sorted(returns)
def _indices(element, restriction_domain, tdim): "Extract basis functions indices that correspond to restriction_domain." # FIXME: The restriction_domain argument in FFC/UFL needs to be re-thought and # cleaned-up. # If restriction_domain is "interior", pick basis functions associated with # cell. if restriction_domain == "interior": return element.entity_dofs()[tdim][0] # Pick basis functions associated with # the topological degree of the restriction_domain and of all lower # dimensions. if restriction_domain == "facet": rdim = tdim-1 elif restriction_domain == "face": rdim = 2 elif restriction_domain == "edge": rdim = 1 elif restriction_domain == "vertex": rdim = 0 else: error("Restriction to domain: %s, is not supported." % repr(restriction_domain)) entity_dofs = element.entity_dofs() indices = [] for dim in range(rdim + 1): entities = entity_dofs[dim] for (entity, index) in sorted_by_key(entities): indices += index return indices
def _overlap(l, d): "Check if a member in list l is in the value (list) of dictionary d." for m in l: for k, v in sorted_by_key(d): if m in v: return True return False
def sum(self, o, *operands): code = {} # Loop operands that has to be summend. for op in operands: # If entries does already exist we can add the code, # otherwise just dump them in the element tensor. for key, val in sorted(op.items()): if key in code: code[key].append(val) else: code[key] = [val] # Add sums and group if necessary. for key, val in sorted_by_key(code): if len(val) > 1: code[key] = create_sum(val) elif val: code[key] = val[0] else: error("Where did the values go?") # If value is zero just ignore it. if abs(code[key].val) < format["epsilon"]: del code[key] return code
def dicts_lt(a, b): na = 0 if a is None else len(a) nb = 0 if b is None else len(b) if na != nb: return len(a) < len(b) for ia, ib in zip(sorted_by_key(a), sorted_by_key(b)): # Assuming keys are sortable (usually str) if ia[0] != ib[0]: return (ia[0].__class__.__name__, ia[0]) < ( ib[0].__class__.__name__, ib[0] ) # Hack to preserve type sorting in py3 # Assuming values are sortable if ia[1] != ib[1]: return (ia[1].__class__.__name__, ia[1]) < ( ib[1].__class__.__name__, ib[1] ) # Hack to preserve type sorting in py3
def _simplify_expression(integral, geo_consts, psi_tables_map): for points, terms, functions, ip_consts, coordinate, conditionals in integral: # NOTE: sorted is needed to pass the regression tests on the buildbots # but it might be inefficient for speed. # A solution could be to only compare the output of evaluating the # integral, not the header files. for loop, (data, entry_vals) in sorted_by_key(terms): t_set, u_weights, u_psi_tables, u_nzcs, basis_consts = data new_entry_vals = [] psi_tables = set() # NOTE: sorted is needed to pass the regression tests on the buildbots # but it might be inefficient for speed. # A solution could be to only compare the output of evaluating the # integral, not the header files. for entry, val, ops in sorted(entry_vals): value = optimise_code(val, ip_consts, geo_consts, t_set) # Check if value is zero if value.val: new_entry_vals.append((entry, value, value.ops())) psi_tables.update( set([ psi_tables_map[b] for b in value.get_unique_vars(BASIS) ])) terms[loop][0][2] = psi_tables terms[loop][1] = new_entry_vals
def generate_aux_constants(constant_decl, name, var_type, print_ops=False): "A helper tool to generate code for constant declarations." format_comment = format["comment"] code = [] append = code.append ops = 0 for num, expr in sorted((v, k) for k, v in sorted_by_key(constant_decl)): # debug("expr orig: " + str(expr)) # print "\nnum: ", num # print "expr orig: " + repr(expr) # print "expr exp: " + str(expr.expand()) # Expand and reduce expression (If we don't already get reduced expressions.) expr = expr.expand().reduce_ops() # debug("expr opt: " + str(expr)) # print "expr opt: " + str(expr) if print_ops: op = expr.ops() ops += op append(format_comment("Number of operations: %d" %op)) append(var_type(name(num), str(expr))) append("") else: ops += expr.ops() append(var_type(name(num), str(expr))) return (ops, code)
def _indices(element, restriction_domain, tdim): "Extract basis functions indices that correspond to restriction_domain." # FIXME: The restriction_domain argument in FFC/UFL needs to be re-thought and # cleaned-up. # If restriction_domain is "interior", pick basis functions associated with # cell. if restriction_domain == "interior": return element.entity_dofs()[tdim][0] # Pick basis functions associated with # the topological degree of the restriction_domain and of all lower # dimensions. if restriction_domain == "facet": rdim = tdim - 1 elif restriction_domain == "face": rdim = 2 elif restriction_domain == "edge": rdim = 1 elif restriction_domain == "vertex": rdim = 0 else: error("Restriction to domain: %s, is not supported." % repr(restriction_domain)) entity_dofs = element.entity_dofs() indices = [] for dim in range(rdim + 1): entities = entity_dofs[dim] for (entity, index) in sorted_by_key(entities): indices += index return indices
def compile_with_error_control(forms, object_names, reserved_objects, prefix, parameters): """ Compile forms and additionally generate and compile forms required for performing goal-oriented error control For linear problems, the input forms should be a bilinear form (a) and a linear form (L) specifying the variational problem and additionally a linear form (M) specifying the goal functional. For nonlinear problems, the input should be linear form (F) and a functional (M) specifying the goal functional. *Arguments* forms (tuple) Three (linear case) or two (nonlinear case) forms specifying the primal problem and the goal object_names (dict) Map from object ids to object names reserved_names (dict) Map from reserved object names to object ids prefix (string) Basename of header file parameters (dict) Parameters for form compilation """ # Check input arguments F, M, u = prepare_input_arguments(forms, object_names, reserved_objects) # Generate forms to be used for the error control from ffc.errorcontrol.errorcontrolgenerators import UFLErrorControlGenerator generator = UFLErrorControlGenerator(F, M, u) ec_forms = generator.generate_all_error_control_forms() # Check that there are no conflicts between user defined and # generated names ec_names = generator.ec_names if set(object_names.values()) & set(ec_names.values()): comment = "%s are reserved error control names." % str( sorted(ec_names.values())) error("Conflict between user defined and generated names: %s" % comment) # Add names generated for error control to object_names for (objid, name) in sorted_by_key(ec_names): object_names[objid] = name # Compile error control and input (pde + goal) forms as normal forms = generator.primal_forms() code_h, code_c = compile_form(ec_forms + forms, object_names, prefix, parameters) return code_h, code_c
def compile_with_error_control(forms, object_names, reserved_objects, prefix, parameters): """ Compile forms and additionally generate and compile forms required for performing goal-oriented error control For linear problems, the input forms should be a bilinear form (a) and a linear form (L) specifying the variational problem and additionally a linear form (M) specifying the goal functional. For nonlinear problems, the input should be linear form (F) and a functional (M) specifying the goal functional. *Arguments* forms (tuple) Three (linear case) or two (nonlinear case) forms specifying the primal problem and the goal object_names (dict) Map from object ids to object names reserved_names (dict) Map from reserved object names to object ids prefix (string) Basename of header file parameters (dict) Parameters for form compilation """ # Check input arguments F, M, u = prepare_input_arguments(forms, object_names, reserved_objects) # Generate forms to be used for the error control from ffc.errorcontrol.errorcontrolgenerators import UFLErrorControlGenerator generator = UFLErrorControlGenerator(F, M, u) ec_forms = generator.generate_all_error_control_forms() # Check that there are no conflicts between user defined and # generated names ec_names = generator.ec_names if set(object_names.values()) & set(ec_names.values()): comment = "%s are reserved error control names." % str(sorted(ec_names.values())) error("Conflict between user defined and generated names: %s" % comment) # Add names generated for error control to object_names for (objid, name) in sorted_by_key(ec_names): object_names[objid] = name # Compile error control and input (pde + goal) forms as normal forms = generator.primal_forms() code_h, code_c = compile_form(ec_forms + forms, object_names, prefix, parameters) return code_h, code_c
def _precompute_expressions(integral, geo_consts, optimisation): for points, terms, functions, ip_consts, coordinate, conditionals in integral: for loop, (data, entry_vals) in sorted_by_key(terms): t_set, u_weights, u_psi_tables, u_nzcs, basis_consts = data new_entry_vals = [] for entry, val, ops in entry_vals: value = _extract_variables(val, basis_consts, ip_consts, geo_consts, t_set, optimisation) # Check if value is zero if value.val: new_entry_vals.append((entry, value, value.ops())) terms[loop][1] = new_entry_vals
def reduction_possible(variables): """Find the variable that occurs in the most products, if more variables occur the same number of times and in the same products add them to list.""" # Find the variable that appears in the most products max_val = 1 max_var = "" max_vars = [] for key, val in sorted_by_key(variables): if max_val < val[0]: max_val = val[0] max_var = key # If we found a variable that appears in products multiple times, check if # other variables appear in the exact same products if max_var: for key, val in sorted_by_key(variables): # Check if we have more variables in the same products if max_val == val[0] and variables[max_var][1] == val[1]: max_vars.append(key) return max_vars
def debug_dict(d, title=""): "Pretty-print dictionary." if not title: title = "Dictionary" info("") begin(title) info("") for (key, value) in sorted_by_key(d): info(key) info("-" * len(key)) info(str(value)) info("") end()
def group_form_integrals(form, domains): """Group integrals by domain and type, performing canonical simplification. :arg form: the :class:`~.Form` to group the integrals of. :arg domains: an iterable of :class:`~.Domain`\s. :returns: A new :class:`~.Form` with gathered integrands. """ # Group integrals by domain and type integrals_by_domain_and_type = \ group_integrals_by_domain_and_type(form.integrals(), domains) integrals = [] for domain in domains: for integral_type in ufl.measure.integral_types(): # Get integrals with this domain and type ddt_integrals = integrals_by_domain_and_type.get( (domain, integral_type)) if ddt_integrals is None: continue # Group integrals by subdomain id, after splitting e.g. # f*dx((1,2)) + g*dx((2,3)) -> f*dx(1) + (f+g)*dx(2) + g*dx(3) # (note: before this call, 'everywhere' is a valid subdomain_id, # and after this call, 'otherwise' is a valid subdomain_id) single_subdomain_integrals = \ rearrange_integrals_by_single_subdomains(ddt_integrals) for subdomain_id, ss_integrals in sorted_by_key( single_subdomain_integrals): # Accumulate integrands of integrals that share the # same compiler data integrands_and_cds = \ accumulate_integrands_with_same_metadata(ss_integrals) for integrand, metadata in integrands_and_cds: integrals.append( Integral(integrand, integral_type, domain, subdomain_id, metadata, None)) return Form(integrals)
def _simplify_expression(integral, geo_consts, psi_tables_map): for points, terms, functions, ip_consts, coordinate, conditionals in integral: # NOTE: sorted is needed to pass the regression tests on the buildbots # but it might be inefficient for speed. # A solution could be to only compare the output of evaluating the # integral, not the header files. for loop, (data, entry_vals) in sorted_by_key(terms): t_set, u_weights, u_psi_tables, u_nzcs, basis_consts = data new_entry_vals = [] psi_tables = set() # NOTE: sorted is needed to pass the regression tests on the buildbots # but it might be inefficient for speed. # A solution could be to only compare the output of evaluating the # integral, not the header files. for entry, val, ops in sorted(entry_vals): value = optimise_code(val, ip_consts, geo_consts, t_set) # Check if value is zero if value.val: new_entry_vals.append((entry, value, value.ops())) psi_tables.update(set([psi_tables_map[b] for b in value.get_unique_vars(BASIS)])) terms[loop][0][2] = psi_tables terms[loop][1] = new_entry_vals
def _indices(element, restriction_domain, dim=0): "Extract basis functions indices that correspond to restriction_domain." # FIXME: The restriction_domain argument in FFC/UFL needs to be re-thought and # cleaned-up. # If restriction_domain is "interior", pick basis functions associated with # cell. if restriction_domain == "interior" and dim: return element.entity_dofs()[dim][0] # If restriction_domain is a ufl.Cell, pick basis functions associated with # the topological degree of the restriction_domain and of all lower # dimensions. if isinstance(restriction_domain, ufl.Cell): dim = restriction_domain.topological_dimension() entity_dofs = element.entity_dofs() indices = [] for dim in range(restriction_domain.topological_dimension() + 1): entities = entity_dofs[dim] for (entity, index) in sorted_by_key(entities): indices += index return indices # Just extract all indices to make handling in RestrictedElement # uniform. #elif isinstance(restriction_domain, ufl.Measure): # indices = [] # entity_dofs = element.entity_dofs() # for dim, entities in entity_dofs.items(): # for entity, index in entities.items(): # indices += index # return indices else: error("Restriction to domain: %s, is not supported." % repr(restriction_domain))
def tabulate(self, order, points): result = self._element.tabulate(order, points) extracted = {} for (dtuple, values) in sorted_by_key(result): extracted[dtuple] = numpy.array([values[i] for i in self._indices]) return extracted
def reduce_ops(self): "Reduce the number of operations needed to evaluate the sum." # global ind # ind += " " # print "\n%sreduce_ops, start" % ind if self._reduced: return self._reduced # NOTE: Assuming that sum has already been expanded. # TODO: Add test for this and handle case if it is not. # TODO: The entire function looks expensive, can it be optimised? # TODO: It is not necessary to create a new Sum if we do not have more # than one Fraction. # First group all fractions in the sum. new_sum = _group_fractions(self) if new_sum._prec != 3: # sum self._reduced = new_sum.reduce_ops() return self._reduced # Loop all variables of the sum and collect the number of common # variables that can be factored out. common_vars = {} for var in new_sum.vrs: # Get dictonary of occurrences and add the variable and the number # of occurrences to common dictionary. for k, v in sorted_by_key(var.get_var_occurrences()): # print # print ind + "var: ", var # print ind + "k: ", k # print ind + "v: ", v if k in common_vars: common_vars[k].append((v, var)) else: common_vars[k] = [(v, var)] # print # print "common vars: " # for k,v in common_vars.items(): # print "k: ", k # print "v: ", v # print # Determine the maximum reduction for each variable # sorted as: {(x*x*y, x*y*z, 2*y):[2, [y]]}. terms_reductions = {} for k, v in sorted_by_key(common_vars): # print # print ind + "k: ", k # print ind + "v: ", v # If the number of expressions that can be reduced is only one # there is nothing to be done. if len(v) > 1: # TODO: Is there a better way to compute the reduction gain # and the number of occurrences we should remove? # Get the list of number of occurences of 'k' in expressions # in 'v'. occurrences = [t[0] for t in v] # Determine the favorable number of occurences and an estimate # of the maximum reduction for current variable. fav_occur = 0 reduc = 0 for i in set(occurrences): # Get number of terms that has a number of occcurences equal # to or higher than the current number. num_terms = len([o for o in occurrences if o >= i]) # An estimate of the reduction in operations is: # (number_of_terms - 1) * number_occurrences. new_reduc = (num_terms-1)*i if new_reduc > reduc: reduc = new_reduc fav_occur = i # Extract the terms of v where the number of occurrences is # equal to or higher than the most favorable number of occurrences. terms = sorted([t[1] for t in v if t[0] >= fav_occur]) # We need to reduce the expression with the favorable number of # occurrences of the current variable. red_vars = [k]*fav_occur # If the list of terms is already present in the dictionary, # add the reduction count and the variables. if tuple(terms) in terms_reductions: terms_reductions[tuple(terms)][0] += reduc terms_reductions[tuple(terms)][1] += red_vars else: terms_reductions[tuple(terms)] = [reduc, red_vars] # print "\nterms_reductions: " # for k,v in terms_reductions.items(): # print "k: ", create_sum(k) # print "v: ", v # print "red: self: ", self if terms_reductions: # Invert dictionary of terms. reductions_terms = dict([((v[0], tuple(v[1])), k) for k, v in six.iteritems(terms_reductions)]) # Create a sorted list of those variables that give the highest # reduction. sorted_reduc_var = sorted(six.iterkeys(reductions_terms), reverse=True) # sorted_reduc_var = [k for k, v in six.iteritems(reductions_terms)] # print # print ind + "raw" # for k in sorted_reduc_var: # print ind, k[0], k[1] # sorted_reduc_var.sort() # sorted_reduc_var.sort(lambda x, y: cmp(x[0], y[0])) # sorted_reduc_var.reverse() # print ind + "sorted" # for k in sorted_reduc_var: # print ind, k[0], k[1] # Create a new dictionary of terms that should be reduced, if some # terms overlap, only pick the one which give the highest reduction to # ensure that a*x*x + b*x*x + x*x*y + 2*y -> x*x*(a + b + y) + 2*y NOT # x*x*(a + b) + y*(2 + x*x). reduction_vars = {} rejections = {} for var in sorted_reduc_var: terms = reductions_terms[var] if _overlap(terms, reduction_vars) or _overlap(terms, rejections): rejections[var[1]] = terms else: reduction_vars[var[1]] = terms # print "\nreduction_vars: " # for k,v in reduction_vars.items(): # print "k: ", k # print "v: ", v # Reduce each set of terms with appropriate variables. all_reduced_terms = [] reduced_expressions = [] for reduc_var, terms in sorted(six.iteritems(reduction_vars)): # Add current terms to list of all variables that have been reduced. all_reduced_terms += list(terms) # Create variable that we will use to reduce the terms. reduction_var = None if len(reduc_var) > 1: reduction_var = create_product(list(reduc_var)) else: reduction_var = reduc_var[0] # Reduce all terms that need to be reduced. reduced_terms = [t.reduce_var(reduction_var) for t in terms] # Create reduced expression. reduced_expr = None if len(reduced_terms) > 1: # Try to reduce the reduced terms further. reduced_expr = create_product([reduction_var, create_sum(reduced_terms).reduce_ops()]) else: reduced_expr = create_product(reduction_var, reduced_terms[0]) # Add reduced expression to list of reduced expressions. reduced_expressions.append(reduced_expr) # Create list of terms that should not be reduced. dont_reduce_terms = [] for v in new_sum.vrs: if not v in all_reduced_terms: dont_reduce_terms.append(v) # Create expression from terms that was not reduced. not_reduced_expr = None if dont_reduce_terms and len(dont_reduce_terms) > 1: # Try to reduce the remaining terms that were not reduced at first. not_reduced_expr = create_sum(dont_reduce_terms).reduce_ops() elif dont_reduce_terms: not_reduced_expr = dont_reduce_terms[0] # Create return expression. if not_reduced_expr: self._reduced = create_sum(reduced_expressions + [not_reduced_expr]) elif len(reduced_expressions) > 1: self._reduced = create_sum(reduced_expressions) else: self._reduced = reduced_expressions[0] # # NOTE: Only switch on for debugging. # if not self._reduced.expand() == self.expand(): # print reduced_expressions[0] # print reduced_expressions[0].expand() # print "self: ", self # print "red: ", repr(self._reduced) # print "self.exp: ", self.expand() # print "red.exp: ", self._reduced.expand() # error("Reduced expression is not equal to original expression.") return self._reduced # Return self if we don't have any variables for which we can reduce # the sum. self._reduced = self return self._reduced
def _evaluate_basis_at_quadrature_points(psi_tables, gdim, element_data, form_prefix, num_vertices, num_cells): "Generate code for calling evaluate basis (derivatives) at quadrature points" # Prefetch formats to speed up code generation f_comment = format["comment"] f_declaration = format["declaration"] f_static_array = format["static array"] f_loop = format["generate loop"] f_eval_basis_decl = format["eval_basis_decl"] f_eval_basis_init = format["eval_basis_init"] f_eval_basis = format["eval_basis"] f_eval_basis_copy = format["eval_basis_copy"] f_eval_derivs_decl = format["eval_derivs_decl"] f_eval_derivs_init = format["eval_derivs_init"] f_eval_derivs = format["eval_derivs"] f_eval_derivs_copy = format["eval_derivs_copy"] code = [] # Extract prefixes for tables prefixes = sorted(set(table.split("_")[0] for table in psi_tables)) # Use lower case prefix for form name form_prefix = form_prefix.lower() # The psi_tables used by the quadrature code are for scalar # components of specific derivatives, while tabulate_basis_all and # tabulate_basis_derivatives_all return data including all # possible components and derivatives. We therefore need to # iterate over prefixes (= elements), call tabulate_basis_all or # tabulate_basis_derivatives all, and then extract the relevant # data and fill in the psi_tables. We therefore need to extract # for each prefix, which tables need to be filled in. # For each unique prefix, check which derivatives and components # are used used_derivatives_and_components = {} for prefix in prefixes: used_derivatives_and_components[prefix] = {} for table in psi_tables: if prefix not in table: continue # Check for derivative if "_D" in table: d = table.split("_D")[1].split("_")[0] n = sum([int(_d) for _d in d]) # FIXME: Assume at most 9 derivatives... else: n = 0 # Check for component if "_C" in table: c = table.split("_C")[1].split("_")[0] else: c = None # Note that derivative has been used if n not in used_derivatives_and_components[prefix]: used_derivatives_and_components[prefix][n] = set() used_derivatives_and_components[prefix][n].add(c) # Generate code for setting quadrature weights code += [f_comment("Set quadrature weights")] code += [f_declaration("const double*", "W", "quadrature_weights")] code += [""] # Generate code for calling evaluate_basis_[derivatives_]all for prefix in prefixes: # Get element data for current element counter = int(prefix.split("FE")[1]) space_dim = element_data[counter]["num_element_dofs"] value_size = element_data[counter]["physical_value_size"] element_classname = element_data[counter]["classname"] # Iterate over derivative orders for n, components in sorted_by_key(used_derivatives_and_components[prefix]): # components are a set and need to be sorted components = sorted(components) # Code for evaluate_basis_all (n = 0 which means it's not # a derivative) if n == 0: code += [f_comment("--- Evaluation of basis functions ---")] code += [""] # Compute variables for code generation eval_stride = value_size eval_size = space_dim * eval_stride table_size = num_cells * space_dim # Iterate over components and initialize tables for c in components: # Set name of table if c is None: table_name = prefix else: table_name = prefix + "_C%s" % c # Generate code for declaration of table code += [f_comment("Create table %s for basis function values on all cells" % table_name)] code += [f_eval_basis_decl % {"table_name": table_name}] code += [f_eval_basis_init % {"table_name": table_name, "table_size": table_size}] code += [""] # Iterate over cells in macro element and evaluate basis for cell_number in range(num_cells): # Compute variables for code generation eval_name = "%s_values_%d" % (prefix, cell_number) table_offset = cell_number * space_dim vertex_offset = cell_number * num_vertices * gdim # Generate block of code for loop block = [] # Generate code for calling evaluate_basis_all block += [f_eval_basis % {"classname": element_classname, "eval_name": eval_name, "gdim": gdim, "vertex_offset": vertex_offset}] # Iterate over components and extract values for c in components: # Set name of table and component offset if c is None: table_name = prefix eval_offset = 0 else: table_name = prefix + "_C%s" % c eval_offset = int(c) # Generate code for copying values block += [""] block += [f_eval_basis_copy % {"table_name": table_name, "eval_name": eval_name, "eval_stride": eval_stride, "eval_offset": eval_offset, "space_dim": space_dim, "table_offset": table_offset}] # Generate code code += [f_comment("Evaluate basis functions on cell %d" % cell_number)] code += [f_static_array("double", eval_name, eval_size)] code += f_loop(block, [("ip", 0, "num_quadrature_points")]) code += [""] # Code for evaluate_basis_derivatives_all (derivative of degree n > 0) else: code += [f_comment("--- Evaluation of basis function derivatives of order %d ---" % n)] code += [""] # FIXME: We extract values for all possible # derivatives, even # FIXME: if not all are used. (For components, we # extract only # FIXME: components that are actually used.) This may # be optimized # FIXME: but the extra cost is likely small. # Get derivative tuples __, deriv_tuples = compute_derivative_tuples(n, gdim) # Generate names for derivatives derivs = ["".join(str(_d) for _d in d) for d in deriv_tuples] # Compute variables for code generation eval_stride = value_size * len(derivs) eval_size = space_dim * eval_stride table_size = num_cells * space_dim # Iterate over derivatives and initialize tables seen_derivs = set() for d in derivs: # Skip derivative if seen before (d^2/dxdy = d^2/dydx) if d in seen_derivs: continue seen_derivs.add(d) # Iterate over components for c in components: # Set name of table if c is None: table_name = prefix + "_D%s" % d else: table_name = prefix + "_C%s_D%s" % (c, d) # Generate code for declaration of table code += [f_comment("Create table %s for basis function derivatives on all cells" % table_name)] code += [(f_eval_derivs_decl % {"table_name": table_name})] code += [(f_eval_derivs_init % {"table_name": table_name, "table_size": table_size})] code += [""] # Iterate over cells (in macro element) for cell_number in range(num_cells): # Compute variables for code generation eval_name = "%s_dvalues_%d_%d" % (prefix, n, cell_number) table_offset = cell_number * space_dim vertex_offset = cell_number * num_vertices * gdim # Generate block of code for loop block = [] # Generate code for calling evaluate_basis_derivatives_all block += [f_eval_derivs % {"classname": element_classname, "eval_name": eval_name, "gdim": gdim, "vertex_offset": vertex_offset, "n": n}] # Iterate over derivatives and extract values seen_derivs = set() for i, d in enumerate(derivs): # Skip derivative if seen before (d^2/dxdy = d^2/dydx) if d in seen_derivs: continue seen_derivs.add(d) # Iterate over components for c in components: # Set name of table and component offset if c is None: table_name = prefix + "_D%s" % d eval_offset = i else: table_name = prefix + "_C%s_D%s" % (c, d) eval_offset = len(derivs) * int(c) + i # Generate code for copying values block += [""] block += [(f_eval_derivs_copy % {"table_name": table_name, "eval_name": eval_name, "eval_stride": eval_stride, "eval_offset": eval_offset, "space_dim": space_dim, "table_offset": table_offset})] # Generate code code += [f_comment("Evaluate basis function derivatives on cell %d" % cell_number)] code += [f_static_array("double", eval_name, eval_size)] code += f_loop(block, [("ip", 0, "num_quadrature_points")]) code += [""] # Add newline code += [""] return code
def interpret_ufl_namespace(namespace): "Takes a namespace dict from an executed ufl file and converts it to a FileData object." # Object to hold all returned data ufd = FileData() # Extract object names for Form, Coefficient and FiniteElementBase objects # The use of id(obj) as key in object_names is necessary # because we need to distinguish between instances, # and not just between objects with different values. for name, value in sorted_by_key(namespace): # Store objects by reserved name OR instance id reserved_names = ("unknown",) # Currently only one reserved name if name in reserved_names: # Store objects with reserved names ufd.reserved_objects[name] = value # FIXME: Remove after FFC is updated to use reserved_objects: ufd.object_names[name] = value ufd.object_by_name[name] = value elif isinstance(value, (FiniteElementBase, Coefficient, Argument, Form, Expr)): # Store instance <-> name mappings for important objects # without a reserved name ufd.object_names[id(value)] = name ufd.object_by_name[name] = value # Get list of exported forms forms = namespace.get("forms") if forms is None: # Get forms from object_by_name, which has already mapped # tuple->Form where needed def get_form(name): form = ufd.object_by_name.get(name) return form if isinstance(form, Form) else None a_form = get_form("a") L_form = get_form("L") M_form = get_form("M") forms = [a_form, L_form, M_form] # Add forms F and J if not "a" and "L" are used if a_form is None or L_form is None: F_form = get_form("F") J_form = get_form("J") forms += [F_form, J_form] # Remove Nones forms = [f for f in forms if isinstance(f, Form)] ufd.forms = forms # Validate types if not isinstance(ufd.forms, (list, tuple)): error("Expecting 'forms' to be a list or tuple, not '%s'." % type(ufd.forms)) if not all(isinstance(a, Form) for a in ufd.forms): error("Expecting 'forms' to be a list of Form instances.") # Get list of exported elements elements = namespace.get("elements") if elements is None: elements = [ufd.object_by_name.get(name) for name in ("element",)] elements = [e for e in elements if e is not None] ufd.elements = elements # Validate types if not isinstance(ufd.elements, (list, tuple)): error("Expecting 'elements' to be a list or tuple, not '%s'." % type(ufd.elements)) if not all(isinstance(e, FiniteElementBase) for e in ufd.elements): error("Expecting 'elements' to be a list of FiniteElementBase instances.") # Get list of exported coefficients # TODO: Temporarily letting 'coefficients' override 'functions', # but allow 'functions' for compatibility functions = namespace.get("functions", []) if functions: warning("Deprecation warning: Rename 'functions' to 'coefficients' to export coefficients.") ufd.coefficients = namespace.get("coefficients", functions) # Validate types if not isinstance(ufd.coefficients, (list, tuple)): error("Expecting 'coefficients' to be a list or tuple, not '%s'." % type(ufd.coefficients)) if not all(isinstance(e, Coefficient) for e in ufd.coefficients): error("Expecting 'coefficients' to be a list of Coefficient instances.") # Get list of exported expressions ufd.expressions = namespace.get("expressions", []) # Validate types if not isinstance(ufd.expressions, (list, tuple)): error("Expecting 'expressions' to be a list or tuple, not '%s'." % type(ufd.expressions)) if not all(isinstance(e, Expr) for e in ufd.expressions): error("Expecting 'expressions' to be a list of Expr instances.") # Return file data return ufd
def reduce_ops(self): "Reduce the number of operations needed to evaluate the sum." if self._reduced: return self._reduced # NOTE: Assuming that sum has already been expanded. # TODO: Add test for this and handle case if it is not. # TODO: The entire function looks expensive, can it be optimised? # TODO: It is not necessary to create a new Sum if we do not # have more than one Fraction. # First group all fractions in the sum. new_sum = _group_fractions(self) if new_sum._prec != 3: # sum self._reduced = new_sum.reduce_ops() return self._reduced # Loop all variables of the sum and collect the number of # common variables that can be factored out. common_vars = {} for var in new_sum.vrs: # Get dictonary of occurrences and add the variable and # the number of occurrences to common dictionary. for k, v in sorted_by_key(var.get_var_occurrences()): if k in common_vars: common_vars[k].append((v, var)) else: common_vars[k] = [(v, var)] # Determine the maximum reduction for each variable sorted as: # {(x*x*y, x*y*z, 2*y):[2, [y]]}. terms_reductions = {} for k, v in sorted_by_key(common_vars): # If the number of expressions that can be reduced is only # one there is nothing to be done. if len(v) > 1: # TODO: Is there a better way to compute the reduction # gain and the number of occurrences we should remove? # Get the list of number of occurences of 'k' in # expressions in 'v'. occurrences = [t[0] for t in v] # Determine the favorable number of occurences and an # estimate of the maximum reduction for current # variable. fav_occur = 0 reduc = 0 for i in set(occurrences): # Get number of terms that has a number of # occcurences equal to or higher than the current # number. num_terms = len([o for o in occurrences if o >= i]) # An estimate of the reduction in operations is: # (number_of_terms - 1) * number_occurrences. new_reduc = (num_terms - 1) * i if new_reduc > reduc: reduc = new_reduc fav_occur = i # Extract the terms of v where the number of # occurrences is equal to or higher than the most # favorable number of occurrences. terms = sorted([t[1] for t in v if t[0] >= fav_occur]) # We need to reduce the expression with the favorable # number of occurrences of the current variable. red_vars = [k] * fav_occur # If the list of terms is already present in the # dictionary, add the reduction count and the # variables. if tuple(terms) in terms_reductions: terms_reductions[tuple(terms)][0] += reduc terms_reductions[tuple(terms)][1] += red_vars else: terms_reductions[tuple(terms)] = [reduc, red_vars] if terms_reductions: # Invert dictionary of terms. reductions_terms = dict([((v[0], tuple(v[1])), k) for k, v in terms_reductions.items()]) # Create a sorted list of those variables that give the # highest reduction. sorted_reduc_var = sorted(reductions_terms.keys(), reverse=True) # Create a new dictionary of terms that should be reduced, # if some terms overlap, only pick the one which give the # highest reduction to ensure that a*x*x + b*x*x + x*x*y + # 2*y -> x*x*(a + b + y) + 2*y NOT x*x*(a + b) + y*(2 + # x*x). reduction_vars = {} rejections = {} for var in sorted_reduc_var: terms = reductions_terms[var] if _overlap(terms, reduction_vars) or _overlap(terms, rejections): rejections[var[1]] = terms else: reduction_vars[var[1]] = terms # Reduce each set of terms with appropriate variables. all_reduced_terms = [] reduced_expressions = [] for reduc_var, terms in sorted(reduction_vars.items()): # Add current terms to list of all variables that have # been reduced. all_reduced_terms += list(terms) # Create variable that we will use to reduce the terms. reduction_var = None if len(reduc_var) > 1: reduction_var = create_product(list(reduc_var)) else: reduction_var = reduc_var[0] # Reduce all terms that need to be reduced. reduced_terms = [t.reduce_var(reduction_var) for t in terms] # Create reduced expression. reduced_expr = None if len(reduced_terms) > 1: # Try to reduce the reduced terms further. reduced_expr = create_product([reduction_var, create_sum(reduced_terms).reduce_ops()]) else: reduced_expr = create_product(reduction_var, reduced_terms[0]) # Add reduced expression to list of reduced # expressions. reduced_expressions.append(reduced_expr) # Create list of terms that should not be reduced. dont_reduce_terms = [] for v in new_sum.vrs: if v not in all_reduced_terms: dont_reduce_terms.append(v) # Create expression from terms that was not reduced. not_reduced_expr = None if dont_reduce_terms and len(dont_reduce_terms) > 1: # Try to reduce the remaining terms that were not # reduced at first. not_reduced_expr = create_sum(dont_reduce_terms).reduce_ops() elif dont_reduce_terms: not_reduced_expr = dont_reduce_terms[0] # Create return expression. if not_reduced_expr: self._reduced = create_sum(reduced_expressions + [not_reduced_expr]) elif len(reduced_expressions) > 1: self._reduced = create_sum(reduced_expressions) else: self._reduced = reduced_expressions[0] return self._reduced # Return self if we don't have any variables for which we can # reduce the sum. self._reduced = self return self._reduced
def group_vars(expr, format): """Group variables in an expression, such that: "x + y + z + 2*y + 6*z" = "x + 3*y + 7*z" "x*x + x*x + 2*x + 3*x + 5" = "2.0*x*x + 5.0*x + 5" "x*y + y*x + 2*x*y + 3*x + 0*x + 5" = "5.0*x*y + 3.0*x + 5" "(y + z)*x + 5*(y + z)*x" = "6.0*(y + z)*x" "1/(x*x) + 2*1/(x*x) + std::sqrt(x) + 6*std::sqrt(x)" = "3*1/(x*x) + 7*std::sqrt(x)" """ # Get formats format_float = format["floating point"] add = format["add"](["", ""]) mult = format["multiply"](["", ""]) new_prods = {} # Get list of products prods = split_expression(expr, format, add) # Loop products and collect factors for p in prods: # Get list of variables, and do a basic sort vrs = split_expression(p, format, mult) factor = 1 new_var = [] # Try to multiply factor with variable, else variable must be multiplied by factor later # If we don't have a variable, set factor to zero and break for v in vrs: if v: try: f = float(v) factor *= f except: new_var.append(v) else: factor = 0 break # Create new variable that must be multiplied with factor. Add this # variable to dictionary, if it already exists add factor to other factors new_var.sort() new_var = mult.join(new_var) if new_var in new_prods: new_prods[new_var] += factor else: new_prods[new_var] = factor # Reset products prods = [] for prod, f in sorted_by_key(new_prods): # If we have a product append mult of both if prod: # If factor is 1.0 we don't need it if f == 1.0: prods.append(prod) else: prods.append(mult.join([format_float(f), prod])) # If we just have a factor elif f: prods.append(format_float(f)) prods.sort() return add.join(prods)
def reduce_ops(self): "Reduce the number of operations needed to evaluate the sum." # global ind # ind += " " # print "\n%sreduce_ops, start" % ind if self._reduced: return self._reduced # NOTE: Assuming that sum has already been expanded. # TODO: Add test for this and handle case if it is not. # TODO: The entire function looks expensive, can it be optimised? # TODO: It is not necessary to create a new Sum if we do not have more # than one Fraction. # First group all fractions in the sum. new_sum = _group_fractions(self) if new_sum._prec != 3: # sum self._reduced = new_sum.reduce_ops() return self._reduced # Loop all variables of the sum and collect the number of common # variables that can be factored out. common_vars = {} for var in new_sum.vrs: # Get dictonary of occurrences and add the variable and the number # of occurrences to common dictionary. for k, v in sorted_by_key(var.get_var_occurrences()): # print # print ind + "var: ", var # print ind + "k: ", k # print ind + "v: ", v if k in common_vars: common_vars[k].append((v, var)) else: common_vars[k] = [(v, var)] # print # print "common vars: " # for k,v in common_vars.items(): # print "k: ", k # print "v: ", v # print # Determine the maximum reduction for each variable # sorted as: {(x*x*y, x*y*z, 2*y):[2, [y]]}. terms_reductions = {} for k, v in sorted_by_key(common_vars): # print # print ind + "k: ", k # print ind + "v: ", v # If the number of expressions that can be reduced is only one # there is nothing to be done. if len(v) > 1: # TODO: Is there a better way to compute the reduction gain # and the number of occurrences we should remove? # Get the list of number of occurences of 'k' in expressions # in 'v'. occurrences = [t[0] for t in v] # Determine the favorable number of occurences and an estimate # of the maximum reduction for current variable. fav_occur = 0 reduc = 0 for i in set(occurrences): # Get number of terms that has a number of occcurences equal # to or higher than the current number. num_terms = len([o for o in occurrences if o >= i]) # An estimate of the reduction in operations is: # (number_of_terms - 1) * number_occurrences. new_reduc = (num_terms - 1) * i if new_reduc > reduc: reduc = new_reduc fav_occur = i # Extract the terms of v where the number of occurrences is # equal to or higher than the most favorable number of occurrences. terms = sorted([t[1] for t in v if t[0] >= fav_occur]) # We need to reduce the expression with the favorable number of # occurrences of the current variable. red_vars = [k] * fav_occur # If the list of terms is already present in the dictionary, # add the reduction count and the variables. if tuple(terms) in terms_reductions: terms_reductions[tuple(terms)][0] += reduc terms_reductions[tuple(terms)][1] += red_vars else: terms_reductions[tuple(terms)] = [reduc, red_vars] # print "\nterms_reductions: " # for k,v in terms_reductions.items(): # print "k: ", create_sum(k) # print "v: ", v # print "red: self: ", self if terms_reductions: # Invert dictionary of terms. reductions_terms = dict([ ((v[0], tuple(v[1])), k) for k, v in six.iteritems(terms_reductions) ]) # Create a sorted list of those variables that give the highest # reduction. sorted_reduc_var = sorted(six.iterkeys(reductions_terms), reverse=True) # sorted_reduc_var = [k for k, v in six.iteritems(reductions_terms)] # print # print ind + "raw" # for k in sorted_reduc_var: # print ind, k[0], k[1] # sorted_reduc_var.sort() # sorted_reduc_var.sort(lambda x, y: cmp(x[0], y[0])) # sorted_reduc_var.reverse() # print ind + "sorted" # for k in sorted_reduc_var: # print ind, k[0], k[1] # Create a new dictionary of terms that should be reduced, if some # terms overlap, only pick the one which give the highest reduction to # ensure that a*x*x + b*x*x + x*x*y + 2*y -> x*x*(a + b + y) + 2*y NOT # x*x*(a + b) + y*(2 + x*x). reduction_vars = {} rejections = {} for var in sorted_reduc_var: terms = reductions_terms[var] if _overlap(terms, reduction_vars) or _overlap( terms, rejections): rejections[var[1]] = terms else: reduction_vars[var[1]] = terms # print "\nreduction_vars: " # for k,v in reduction_vars.items(): # print "k: ", k # print "v: ", v # Reduce each set of terms with appropriate variables. all_reduced_terms = [] reduced_expressions = [] for reduc_var, terms in sorted(six.iteritems(reduction_vars)): # Add current terms to list of all variables that have been reduced. all_reduced_terms += list(terms) # Create variable that we will use to reduce the terms. reduction_var = None if len(reduc_var) > 1: reduction_var = create_product(list(reduc_var)) else: reduction_var = reduc_var[0] # Reduce all terms that need to be reduced. reduced_terms = [t.reduce_var(reduction_var) for t in terms] # Create reduced expression. reduced_expr = None if len(reduced_terms) > 1: # Try to reduce the reduced terms further. reduced_expr = create_product([ reduction_var, create_sum(reduced_terms).reduce_ops() ]) else: reduced_expr = create_product(reduction_var, reduced_terms[0]) # Add reduced expression to list of reduced expressions. reduced_expressions.append(reduced_expr) # Create list of terms that should not be reduced. dont_reduce_terms = [] for v in new_sum.vrs: if not v in all_reduced_terms: dont_reduce_terms.append(v) # Create expression from terms that was not reduced. not_reduced_expr = None if dont_reduce_terms and len(dont_reduce_terms) > 1: # Try to reduce the remaining terms that were not reduced at first. not_reduced_expr = create_sum(dont_reduce_terms).reduce_ops() elif dont_reduce_terms: not_reduced_expr = dont_reduce_terms[0] # Create return expression. if not_reduced_expr: self._reduced = create_sum(reduced_expressions + [not_reduced_expr]) elif len(reduced_expressions) > 1: self._reduced = create_sum(reduced_expressions) else: self._reduced = reduced_expressions[0] # # NOTE: Only switch on for debugging. # if not self._reduced.expand() == self.expand(): # print reduced_expressions[0] # print reduced_expressions[0].expand() # print "self: ", self # print "red: ", repr(self._reduced) # print "self.exp: ", self.expand() # print "red.exp: ", self._reduced.expand() # error("Reduced expression is not equal to original expression.") return self._reduced # Return self if we don't have any variables for which we can reduce # the sum. self._reduced = self return self._reduced
def expand(self): "Expand all members of the sum." # If sum is already expanded, simply return the expansion. if self._expanded: return self._expanded # TODO: This function might need some optimisation. # Sort variables into symbols, products and fractions (add floats # directly to new list, will be handled later). Add fractions if # possible else add to list. new_variables = [] syms = [] prods = [] frac_groups = {} # TODO: Rather than using '+', would it be more efficient to collect # the terms first? for var in self.vrs: exp = var.expand() # TODO: Should we also group fractions, or put this in a separate function? if exp._prec in (0, 4): # float or frac new_variables.append(exp) elif exp._prec == 1: # sym syms.append(exp) elif exp._prec == 2: # prod prods.append(exp) elif exp._prec == 3: # sum for v in exp.vrs: if v._prec in (0, 4): # float or frac new_variables.append(v) elif v._prec == 1: # sym syms.append(v) elif v._prec == 2: # prod prods.append(v) # Sort all variables in groups: [2*x, -7*x], [(x + y), (2*x + 4*y)] etc. # First handle product in order to add symbols if possible. prod_groups = {} for v in prods: if v.get_vrs() in prod_groups: prod_groups[v.get_vrs()] += v else: prod_groups[v.get_vrs()] = v sym_groups = {} # Loop symbols and add to appropriate groups. for v in syms: # First try to add to a product group. if (v, ) in prod_groups: prod_groups[(v, )] += v # Then to other symbols. elif v in sym_groups: sym_groups[v] += v # Create a new entry in the symbols group. else: sym_groups[v] = v # Loop groups and add to new variable list. for k, v in sorted_by_key(sym_groups): new_variables.append(v) for k, v in sorted_by_key(prod_groups): new_variables.append(v) # for k,v in frac_groups.iteritems(): # new_variables.append(v) # append(v) if len(new_variables) > 1: # Return new sum (will remove multiple instances of floats during construction). self._expanded = create_sum(sorted(new_variables)) return self._expanded elif new_variables: # If we just have one variable left, return it since it is already expanded. self._expanded = new_variables[0] return self._expanded error("Where did the variables go?")
def _evaluate_basis_at_quadrature_points(psi_tables, gdim, element_data, form_prefix, num_vertices, num_cells): "Generate code for calling evaluate basis (derivatives) at quadrature points" # Prefetch formats to speed up code generation f_comment = format["comment"] f_declaration = format["declaration"] f_static_array = format["static array"] f_loop = format["generate loop"] f_eval_basis_decl = format["eval_basis_decl"] f_eval_basis_init = format["eval_basis_init"] f_eval_basis = format["eval_basis"] f_eval_basis_copy = format["eval_basis_copy"] f_eval_derivs_decl = format["eval_derivs_decl"] f_eval_derivs_init = format["eval_derivs_init"] f_eval_derivs = format["eval_derivs"] f_eval_derivs_copy = format["eval_derivs_copy"] code = [] # Extract prefixes for tables prefixes = sorted(set(table.split("_")[0] for table in psi_tables)) # Use lower case prefix for form name form_prefix = form_prefix.lower() # The psi_tables used by the quadrature code are for scalar # components of specific derivatives, while tabulate_basis_all and # tabulate_basis_derivatives_all return data including all # possible components and derivatives. We therefore need to # iterate over prefixes (= elements), call tabulate_basis_all or # tabulate_basis_derivatives all, and then extract the relevant # data and fill in the psi_tables. We therefore need to extract # for each prefix, which tables need to be filled in. # For each unique prefix, check which derivatives and components # are used used_derivatives_and_components = {} for prefix in prefixes: used_derivatives_and_components[prefix] = {} for table in psi_tables: if prefix not in table: continue # Check for derivative if "_D" in table: d = table.split("_D")[1].split("_")[0] n = sum([int(_d) for _d in d ]) # FIXME: Assume at most 9 derivatives... else: n = 0 # Check for component if "_C" in table: c = table.split("_C")[1].split("_")[0] else: c = None # Note that derivative has been used if n not in used_derivatives_and_components[prefix]: used_derivatives_and_components[prefix][n] = set() used_derivatives_and_components[prefix][n].add(c) # Generate code for setting quadrature weights code += [f_comment("Set quadrature weights")] code += [f_declaration("const double*", "W", "quadrature_weights")] code += [""] # Generate code for calling evaluate_basis_[derivatives_]all for prefix in prefixes: # Get element data for current element counter = int(prefix.split("FE")[1]) space_dim = element_data[counter]["num_element_dofs"] value_size = element_data[counter]["physical_value_size"] element_classname = element_data[counter]["classname"] # Iterate over derivative orders for n, components in sorted_by_key( used_derivatives_and_components[prefix]): # components are a set and need to be sorted components = sorted(components) # Code for evaluate_basis_all (n = 0 which means it's not # a derivative) if n == 0: code += [f_comment("--- Evaluation of basis functions ---")] code += [""] # Compute variables for code generation eval_stride = value_size eval_size = space_dim * eval_stride table_size = num_cells * space_dim # Iterate over components and initialize tables for c in components: # Set name of table if c is None: table_name = prefix else: table_name = prefix + "_C%s" % c # Generate code for declaration of table code += [ f_comment( "Create table %s for basis function values on all cells" % table_name) ] code += [f_eval_basis_decl % {"table_name": table_name}] code += [ f_eval_basis_init % { "table_name": table_name, "table_size": table_size } ] code += [""] # Iterate over cells in macro element and evaluate basis for cell_number in range(num_cells): # Compute variables for code generation eval_name = "%s_values_%d" % (prefix, cell_number) table_offset = cell_number * space_dim vertex_offset = cell_number * num_vertices * gdim # Generate block of code for loop block = [] # Generate code for calling evaluate_basis_all block += [ f_eval_basis % { "classname": element_classname, "eval_name": eval_name, "gdim": gdim, "vertex_offset": vertex_offset } ] # Iterate over components and extract values for c in components: # Set name of table and component offset if c is None: table_name = prefix eval_offset = 0 else: table_name = prefix + "_C%s" % c eval_offset = int(c) # Generate code for copying values block += [""] block += [ f_eval_basis_copy % { "table_name": table_name, "eval_name": eval_name, "eval_stride": eval_stride, "eval_offset": eval_offset, "space_dim": space_dim, "table_offset": table_offset } ] # Generate code code += [ f_comment("Evaluate basis functions on cell %d" % cell_number) ] code += [f_static_array("double", eval_name, eval_size)] code += f_loop(block, [("ip", 0, "num_quadrature_points")]) code += [""] # Code for evaluate_basis_derivatives_all (derivative of degree n > 0) else: code += [ f_comment( "--- Evaluation of basis function derivatives of order %d ---" % n) ] code += [""] # FIXME: We extract values for all possible # derivatives, even # FIXME: if not all are used. (For components, we # extract only # FIXME: components that are actually used.) This may # be optimized # FIXME: but the extra cost is likely small. # Get derivative tuples __, deriv_tuples = compute_derivative_tuples(n, gdim) # Generate names for derivatives derivs = ["".join(str(_d) for _d in d) for d in deriv_tuples] # Compute variables for code generation eval_stride = value_size * len(derivs) eval_size = space_dim * eval_stride table_size = num_cells * space_dim # Iterate over derivatives and initialize tables seen_derivs = set() for d in derivs: # Skip derivative if seen before (d^2/dxdy = d^2/dydx) if d in seen_derivs: continue seen_derivs.add(d) # Iterate over components for c in components: # Set name of table if c is None: table_name = prefix + "_D%s" % d else: table_name = prefix + "_C%s_D%s" % (c, d) # Generate code for declaration of table code += [ f_comment( "Create table %s for basis function derivatives on all cells" % table_name) ] code += [(f_eval_derivs_decl % { "table_name": table_name })] code += [(f_eval_derivs_init % { "table_name": table_name, "table_size": table_size })] code += [""] # Iterate over cells (in macro element) for cell_number in range(num_cells): # Compute variables for code generation eval_name = "%s_dvalues_%d_%d" % (prefix, n, cell_number) table_offset = cell_number * space_dim vertex_offset = cell_number * num_vertices * gdim # Generate block of code for loop block = [] # Generate code for calling evaluate_basis_derivatives_all block += [ f_eval_derivs % { "classname": element_classname, "eval_name": eval_name, "gdim": gdim, "vertex_offset": vertex_offset, "n": n } ] # Iterate over derivatives and extract values seen_derivs = set() for i, d in enumerate(derivs): # Skip derivative if seen before (d^2/dxdy = d^2/dydx) if d in seen_derivs: continue seen_derivs.add(d) # Iterate over components for c in components: # Set name of table and component offset if c is None: table_name = prefix + "_D%s" % d eval_offset = i else: table_name = prefix + "_C%s_D%s" % (c, d) eval_offset = len(derivs) * int(c) + i # Generate code for copying values block += [""] block += [(f_eval_derivs_copy % { "table_name": table_name, "eval_name": eval_name, "eval_stride": eval_stride, "eval_offset": eval_offset, "space_dim": space_dim, "table_offset": table_offset })] # Generate code code += [ f_comment( "Evaluate basis function derivatives on cell %d" % cell_number) ] code += [f_static_array("double", eval_name, eval_size)] code += f_loop(block, [("ip", 0, "num_quadrature_points")]) code += [""] # Add newline code += [""] return code
def reduce_ops(self): "Reduce the number of operations needed to evaluate the sum." if self._reduced: return self._reduced # NOTE: Assuming that sum has already been expanded. # TODO: Add test for this and handle case if it is not. # TODO: The entire function looks expensive, can it be optimised? # TODO: It is not necessary to create a new Sum if we do not # have more than one Fraction. # First group all fractions in the sum. new_sum = _group_fractions(self) if new_sum._prec != 3: # sum self._reduced = new_sum.reduce_ops() return self._reduced # Loop all variables of the sum and collect the number of # common variables that can be factored out. common_vars = {} for var in new_sum.vrs: # Get dictonary of occurrences and add the variable and # the number of occurrences to common dictionary. for k, v in sorted_by_key(var.get_var_occurrences()): if k in common_vars: common_vars[k].append((v, var)) else: common_vars[k] = [(v, var)] # Determine the maximum reduction for each variable sorted as: # {(x*x*y, x*y*z, 2*y):[2, [y]]}. terms_reductions = {} for k, v in sorted_by_key(common_vars): # If the number of expressions that can be reduced is only # one there is nothing to be done. if len(v) > 1: # TODO: Is there a better way to compute the reduction # gain and the number of occurrences we should remove? # Get the list of number of occurences of 'k' in # expressions in 'v'. occurrences = [t[0] for t in v] # Determine the favorable number of occurences and an # estimate of the maximum reduction for current # variable. fav_occur = 0 reduc = 0 for i in set(occurrences): # Get number of terms that has a number of # occcurences equal to or higher than the current # number. num_terms = len([o for o in occurrences if o >= i]) # An estimate of the reduction in operations is: # (number_of_terms - 1) * number_occurrences. new_reduc = (num_terms - 1) * i if new_reduc > reduc: reduc = new_reduc fav_occur = i # Extract the terms of v where the number of # occurrences is equal to or higher than the most # favorable number of occurrences. terms = sorted([t[1] for t in v if t[0] >= fav_occur]) # We need to reduce the expression with the favorable # number of occurrences of the current variable. red_vars = [k] * fav_occur # If the list of terms is already present in the # dictionary, add the reduction count and the # variables. if tuple(terms) in terms_reductions: terms_reductions[tuple(terms)][0] += reduc terms_reductions[tuple(terms)][1] += red_vars else: terms_reductions[tuple(terms)] = [reduc, red_vars] if terms_reductions: # Invert dictionary of terms. reductions_terms = dict([((v[0], tuple(v[1])), k) for k, v in terms_reductions.items()]) # Create a sorted list of those variables that give the # highest reduction. sorted_reduc_var = sorted(reductions_terms.keys(), reverse=True) # Create a new dictionary of terms that should be reduced, # if some terms overlap, only pick the one which give the # highest reduction to ensure that a*x*x + b*x*x + x*x*y + # 2*y -> x*x*(a + b + y) + 2*y NOT x*x*(a + b) + y*(2 + # x*x). reduction_vars = {} rejections = {} for var in sorted_reduc_var: terms = reductions_terms[var] if _overlap(terms, reduction_vars) or _overlap( terms, rejections): rejections[var[1]] = terms else: reduction_vars[var[1]] = terms # Reduce each set of terms with appropriate variables. all_reduced_terms = [] reduced_expressions = [] for reduc_var, terms in sorted(reduction_vars.items()): # Add current terms to list of all variables that have # been reduced. all_reduced_terms += list(terms) # Create variable that we will use to reduce the terms. reduction_var = None if len(reduc_var) > 1: reduction_var = create_product(list(reduc_var)) else: reduction_var = reduc_var[0] # Reduce all terms that need to be reduced. reduced_terms = [t.reduce_var(reduction_var) for t in terms] # Create reduced expression. reduced_expr = None if len(reduced_terms) > 1: # Try to reduce the reduced terms further. reduced_expr = create_product([ reduction_var, create_sum(reduced_terms).reduce_ops() ]) else: reduced_expr = create_product(reduction_var, reduced_terms[0]) # Add reduced expression to list of reduced # expressions. reduced_expressions.append(reduced_expr) # Create list of terms that should not be reduced. dont_reduce_terms = [] for v in new_sum.vrs: if v not in all_reduced_terms: dont_reduce_terms.append(v) # Create expression from terms that was not reduced. not_reduced_expr = None if dont_reduce_terms and len(dont_reduce_terms) > 1: # Try to reduce the remaining terms that were not # reduced at first. not_reduced_expr = create_sum(dont_reduce_terms).reduce_ops() elif dont_reduce_terms: not_reduced_expr = dont_reduce_terms[0] # Create return expression. if not_reduced_expr: self._reduced = create_sum(reduced_expressions + [not_reduced_expr]) elif len(reduced_expressions) > 1: self._reduced = create_sum(reduced_expressions) else: self._reduced = reduced_expressions[0] return self._reduced # Return self if we don't have any variables for which we can # reduce the sum. self._reduced = self return self._reduced
def sum(self, o, *operands): #print("Visiting Sum: " + "\noperands: \n" + "\n".join(map(repr, operands))) # Prefetch formats to speed up code generation. f_group = format["grouping"] f_add = format["add"] f_mult = format["multiply"] f_float = format["floating point"] code = {} # Loop operands that has to be summed and sort according to map (j,k). for op in operands: # If entries does already exist we can add the code, otherwise just # dump them in the element tensor. for key, val in sorted_by_key(op): if key in code: code[key].append(val) else: code[key] = [val] # Add sums and group if necessary. for key, val in sorted_by_key(code): # Exclude all zero valued terms from sum value = [v for v in val if not v is None] if len(value) > 1: # NOTE: Since we no longer call expand_indices, the following # is needed to prevent the code from exploding for forms like # HyperElasticity duplications = {} for val in value: if val in duplications: duplications[val] += 1 continue duplications[val] = 1 # Add a product for each term that has duplicate code expressions = [] for expr, num_occur in sorted_by_key(duplications): if num_occur > 1: # Pre-multiply expression with number of occurrences expressions.append(f_mult([f_float(num_occur), expr])) continue # Just add expression if there is only one expressions.append(expr) ffc_assert(expressions, "Where did the expressions go?") if len(expressions) > 1: code[key] = f_group(f_add(expressions)) continue code[key] = expressions[0] else: # Check for zero valued sum and delete from code # This might result in returning an empty dict, but that should # be interpreted as zero by other handlers. if not value: del code[key] continue code[key] = value[0] return code
def group_form_integrals(form, domains, do_append_everywhere_integrals=True): """Group integrals by domain and type, performing canonical simplification. :arg form: the :class:`~.Form` to group the integrals of. :arg domains: an iterable of :class:`~.Domain`s. :returns: A new :class:`~.Form` with gathered integrands. """ # Group integrals by domain and type integrals_by_domain_and_type = \ group_integrals_by_domain_and_type(form.integrals(), domains) integrals = [] for domain in domains: for integral_type in ufl.measure.integral_types(): # Get integrals with this domain and type ddt_integrals = integrals_by_domain_and_type.get((domain, integral_type)) if ddt_integrals is None: continue # Group integrals by subdomain id, after splitting e.g. # f*dx((1,2)) + g*dx((2,3)) -> f*dx(1) + (f+g)*dx(2) + g*dx(3) # (note: before this call, 'everywhere' is a valid subdomain_id, # and after this call, 'otherwise' is a valid subdomain_id) single_subdomain_integrals = \ rearrange_integrals_by_single_subdomains(ddt_integrals, do_append_everywhere_integrals) for subdomain_id, ss_integrals in sorted_by_key(single_subdomain_integrals): # strip the coordinate derivatives from all integrals # this yields a list of the form [(coordinate derivative, integral), ...] stripped_integrals_and_coordderivs = strip_coordinate_derivatives(ss_integrals) # now group the integrals by the coordinate derivative def calc_hash(cd): return sum(sum(tuple_elem._ufl_compute_hash_() for tuple_elem in tuple_) for tuple_ in cd) coordderiv_integrals_dict = {} for integral, coordderiv in stripped_integrals_and_coordderivs: coordderivhash = calc_hash(coordderiv) if coordderivhash in coordderiv_integrals_dict: coordderiv_integrals_dict[coordderivhash][1].append(integral) else: coordderiv_integrals_dict[coordderivhash] = (coordderiv, [integral]) # cd_integrals_dict is now a dict of the form # { hash: (CoordinateDerivative, [integral, integral, ...]), ... } # we can now put the integrals back together and then afterwards # apply the CoordinateDerivative again for cdhash, samecd_integrals in sorted_by_key(coordderiv_integrals_dict): # Accumulate integrands of integrals that share the # same compiler data integrands_and_cds = \ accumulate_integrands_with_same_metadata(samecd_integrals[1]) for integrand, metadata in integrands_and_cds: integral = Integral(integrand, integral_type, domain, subdomain_id, metadata, None) integral = attach_coordinate_derivatives(integral, samecd_integrals[0]) integrals.append(integral) return Form(integrals)
def expand(self): "Expand all members of the sum." # If sum is already expanded, simply return the expansion. if self._expanded: return self._expanded # TODO: This function might need some optimisation. # Sort variables into symbols, products and fractions (add floats # directly to new list, will be handled later). Add fractions if # possible else add to list. new_variables = [] syms = [] prods = [] frac_groups = {} # TODO: Rather than using '+', would it be more efficient to collect # the terms first? for var in self.vrs: exp = var.expand() # TODO: Should we also group fractions, or put this in a separate function? if exp._prec in (0, 4): # float or frac new_variables.append(exp) elif exp._prec == 1: # sym syms.append(exp) elif exp._prec == 2: # prod prods.append(exp) elif exp._prec == 3: # sum for v in exp.vrs: if v._prec in (0, 4): # float or frac new_variables.append(v) elif v._prec == 1: # sym syms.append(v) elif v._prec == 2: # prod prods.append(v) # Sort all variables in groups: [2*x, -7*x], [(x + y), (2*x + 4*y)] etc. # First handle product in order to add symbols if possible. prod_groups = {} for v in prods: if v.get_vrs() in prod_groups: prod_groups[v.get_vrs()] += v else: prod_groups[v.get_vrs()] = v sym_groups = {} # Loop symbols and add to appropriate groups. for v in syms: # First try to add to a product group. if (v,) in prod_groups: prod_groups[(v,)] += v # Then to other symbols. elif v in sym_groups: sym_groups[v] += v # Create a new entry in the symbols group. else: sym_groups[v] = v # Loop groups and add to new variable list. for k,v in sorted_by_key(sym_groups): new_variables.append(v) for k,v in sorted_by_key(prod_groups): new_variables.append(v) # for k,v in frac_groups.iteritems(): # new_variables.append(v) # append(v) if len(new_variables) > 1: # Return new sum (will remove multiple instances of floats during construction). self._expanded = create_sum(sorted(new_variables)) return self._expanded elif new_variables: # If we just have one variable left, return it since it is already expanded. self._expanded = new_variables[0] return self._expanded error("Where did the variables go?")
def group_form_integrals(form, domains, do_append_everywhere_integrals=True): """Group integrals by domain and type, performing canonical simplification. :arg form: the :class:`~.Form` to group the integrals of. :arg domains: an iterable of :class:`~.Domain`s. :returns: A new :class:`~.Form` with gathered integrands. """ # Group integrals by domain and type integrals_by_domain_and_type = \ group_integrals_by_domain_and_type(form.integrals(), domains) integrals = [] for domain in domains: for integral_type in ufl.measure.integral_types(): # Get integrals with this domain and type ddt_integrals = integrals_by_domain_and_type.get( (domain, integral_type)) if ddt_integrals is None: continue # Group integrals by subdomain id, after splitting e.g. # f*dx((1,2)) + g*dx((2,3)) -> f*dx(1) + (f+g)*dx(2) + g*dx(3) # (note: before this call, 'everywhere' is a valid subdomain_id, # and after this call, 'otherwise' is a valid subdomain_id) single_subdomain_integrals = \ rearrange_integrals_by_single_subdomains(ddt_integrals, do_append_everywhere_integrals) for subdomain_id, ss_integrals in sorted_by_key( single_subdomain_integrals): # strip the coordinate derivatives from all integrals # this yields a list of the form [(coordinate derivative, integral), ...] stripped_integrals_and_coordderivs = strip_coordinate_derivatives( ss_integrals) # now group the integrals by the coordinate derivative def calc_hash(cd): return sum( sum(tuple_elem._ufl_compute_hash_() for tuple_elem in tuple_) for tuple_ in cd) coordderiv_integrals_dict = {} for integral, coordderiv in stripped_integrals_and_coordderivs: coordderivhash = calc_hash(coordderiv) if coordderivhash in coordderiv_integrals_dict: coordderiv_integrals_dict[coordderivhash][1].append( integral) else: coordderiv_integrals_dict[coordderivhash] = ( coordderiv, [integral]) # cd_integrals_dict is now a dict of the form # { hash: (CoordinateDerivative, [integral, integral, ...]), ... } # we can now put the integrals back together and then afterwards # apply the CoordinateDerivative again for cdhash, samecd_integrals in sorted_by_key( coordderiv_integrals_dict): # Accumulate integrands of integrals that share the # same compiler data integrands_and_cds = \ accumulate_integrands_with_same_metadata(samecd_integrals[1]) for integrand, metadata in integrands_and_cds: integral = Integral(integrand, integral_type, domain, subdomain_id, metadata, None) integral = attach_coordinate_derivatives( integral, samecd_integrals[0]) integrals.append(integral) return Form(integrals)
def interpret_ufl_namespace(namespace): "Takes a namespace dict from an executed ufl file and converts it to a FileData object." # Object to hold all returned data ufd = FileData() # Extract object names for Form, Coefficient and FiniteElementBase objects # The use of id(obj) as key in object_names is necessary # because we need to distinguish between instances, # and not just between objects with different values. for name, value in sorted_by_key(namespace): # Store objects by reserved name OR instance id reserved_names = ("unknown", ) # Currently only one reserved name if name in reserved_names: # Store objects with reserved names ufd.reserved_objects[name] = value # FIXME: Remove after FFC is updated to use reserved_objects: ufd.object_names[name] = value ufd.object_by_name[name] = value elif isinstance( value, (FiniteElementBase, Coefficient, Argument, Form, Expr)): # Store instance <-> name mappings for important objects # without a reserved name ufd.object_names[id(value)] = name ufd.object_by_name[name] = value # Get list of exported forms forms = namespace.get("forms") if forms is None: # Get forms from object_by_name, which has already mapped # tuple->Form where needed def get_form(name): form = ufd.object_by_name.get(name) return form if isinstance(form, Form) else None a_form = get_form("a") L_form = get_form("L") M_form = get_form("M") forms = [a_form, L_form, M_form] # Add forms F and J if not "a" and "L" are used if a_form is None or L_form is None: F_form = get_form("F") J_form = get_form("J") forms += [F_form, J_form] # Remove Nones forms = [f for f in forms if isinstance(f, Form)] ufd.forms = forms # Validate types if not isinstance(ufd.forms, (list, tuple)): error("Expecting 'forms' to be a list or tuple, not '%s'." % type(ufd.forms)) if not all(isinstance(a, Form) for a in ufd.forms): error("Expecting 'forms' to be a list of Form instances.") # Get list of exported elements elements = namespace.get("elements") if elements is None: elements = [ufd.object_by_name.get(name) for name in ("element", )] elements = [e for e in elements if e is not None] ufd.elements = elements # Validate types if not isinstance(ufd.elements, (list, tuple)): error("Expecting 'elements' to be a list or tuple, not '%s'." % type(ufd.elements)) if not all(isinstance(e, FiniteElementBase) for e in ufd.elements): error( "Expecting 'elements' to be a list of FiniteElementBase instances." ) # Get list of exported coefficients # TODO: Temporarily letting 'coefficients' override 'functions', # but allow 'functions' for compatibility functions = namespace.get("functions", []) if functions: warning( "Deprecation warning: Rename 'functions' to 'coefficients' to export coefficients." ) ufd.coefficients = namespace.get("coefficients", functions) # Validate types if not isinstance(ufd.coefficients, (list, tuple)): error("Expecting 'coefficients' to be a list or tuple, not '%s'." % type(ufd.coefficients)) if not all(isinstance(e, Coefficient) for e in ufd.coefficients): error( "Expecting 'coefficients' to be a list of Coefficient instances.") # Get list of exported expressions ufd.expressions = namespace.get("expressions", []) # Validate types if not isinstance(ufd.expressions, (list, tuple)): error("Expecting 'expressions' to be a list or tuple, not '%s'." % type(ufd.expressions)) if not all(isinstance(e, Expr) for e in ufd.expressions): error("Expecting 'expressions' to be a list of Expr instances.") # Return file data return ufd