def _evaluate_basis(data): """Generate run time code to evaluate an element basisfunction at an arbitrary point. The value(s) of the basisfunction is/are computed as in FIAT as the dot product of the coefficients (computed at compile time) and basisvalues which are dependent on the coordinate and thus have to be computed at run time. The function should work for all elements supported by FIAT, but it remains untested for tensor valued elements.""" if isinstance(data, str): return format["exception"]("evaluate_basis: %s" % data) # Prefetch formats. f_assign = format["assign"] f_comment = format["comment"] f_values = format["argument values"] f_float = format["floating point"] f_component = format["component"] # Initialise return code. code = [] # Get the element cell name and geometric dimension. cell = data["cell"] element_cellname = cell.cellname() gdim = cell.geometric_dimension() tdim = cell.topological_dimension() # Get code snippets for Jacobian, Inverse of Jacobian and mapping of # coordinates from physical element to the FIAT reference element. code += [format["compute_jacobian"](cell)] code += [format["compute_jacobian_inverse"](cell)] if data["needs_oriented"]: code += [format["orientation"]["ufc"](tdim, gdim)] code += ["", format["fiat coordinate map"](element_cellname, gdim)] # Get value shape and reset values. This should also work for TensorElement, # scalar are empty tuples, therefore (1,) in which case value_shape = 1. reference_value_size = data["reference_value_size"] code += ["", f_comment("Reset values")] if reference_value_size == 1: # Reset values as a pointer. code += [f_assign(format["dereference pointer"](f_values), f_float(0.0))] else: # Reset all values. code += [f_assign(f_component(f_values, i), f_float(0.0)) for i in range(reference_value_size)] # Create code for all basis values (dofs). dof_cases = [] for dof_data in data["dofs_data"]: dof_cases.append(_generate_dof_code(data, dof_data)) code += [format["switch"](format["argument basis num"], dof_cases)] # Remove unused variables (from transformations and mappings) in code. code = remove_unused("\n".join(code)) #code = "\n".join(code) return code
def _generate_dof_code(data, dof_data): """Generate code for a single basis element as the dot product of coefficients and basisvalues. Then apply transformation if applicable.""" # Generate basisvalues. code = _compute_basisvalues(data, dof_data) # Tabulate coefficients. code += _tabulate_coefficients(dof_data) # Compute the value of the basisfunction as the dot product of the # coefficients and basisvalues and apply transformation. code += _compute_values(data, dof_data) return remove_unused("\n".join(code))
def interpolate_vertex_values(ir): "Generate code for interpolate_vertex_values." # Handle unsupported elements. if isinstance(ir, str): return format["exception"]("interpolate_vertex_values: %s" % ir) # Add code for Jacobian if necessary code = [] cell = ir["cell"] gdim = cell.geometric_dimension() tdim = cell.topological_dimension() if ir["needs_jacobian"]: # Generate code for basic geometric quantities code.append(format["compute_jacobian"](cell)) code.append("") code.append(format["compute_jacobian_inverse"](cell)) if ir["needs_oriented"]: code.append("") code.append(format["orientation"]["ufc"](tdim, gdim)) # Compute total value dimension for (mixed) element total_dim = ir["physical_value_size"] # Generate code for each element value_offset = 0 space_offset = 0 for data in ir["element_data"]: # Add vertex interpolation for this element code.append(format["comment"]("Evaluate function and change variables")) code.append(_interpolate_vertex_values_element(data, gdim, tdim, total_dim, value_offset, space_offset)) # Update offsets for value- and space dimension value_offset += data["physical_value_size"] space_offset += data["space_dim"] # Remove unused variables. (Not tracking set of used variables in # order to keep this code clean. Since generated code is of # limited size, this should be ok.) clean_code = remove_unused("\n".join(code)) return clean_code
def interpolate_vertex_values(ir): "Generate code for interpolate_vertex_values." # Handle unsupported elements. if isinstance(ir, str): return format["exception"]("interpolate_vertex_values: %s" % ir) # Add code for Jacobian if necessary code = [] gdim = ir["geometric_dimension"] tdim = ir["topological_dimension"] if ir["needs_jacobian"]: # Generate code for basic geometric quantities code.append(format["compute_jacobian"](tdim, gdim)) code.append("") code.append(format["compute_jacobian_inverse"](tdim, gdim)) if ir["needs_oriented"]: code.append("") code.append(format["orientation"](tdim, gdim)) # Compute total value dimension for (mixed) element total_dim = ir["physical_value_size"] # Generate code for each element value_offset = 0 space_offset = 0 for data in ir["element_data"]: # Add vertex interpolation for this element code.append(format["comment"]("Evaluate function and change variables")) code.append(_interpolate_vertex_values_element(data, gdim, tdim, total_dim, value_offset, space_offset)) # Update offsets for value- and space dimension value_offset += data["physical_value_size"] space_offset += data["space_dim"] # Remove unused variables. (Not tracking set of used variables in # order to keep this code clean. Since generated code is of # limited size, this should be ok.) clean_code = remove_unused("\n".join(code)) return clean_code
def _generate_dof_code(data, dof_data): "Generate code for a basis." code = [] # Compute basisvalues, from evaluatebasis.py. code += _compute_basisvalues(data, dof_data) # Tabulate coefficients. code += _tabulate_coefficients(dof_data) # Tabulate coefficients for derivatives. code += _tabulate_dmats(dof_data) # Compute the derivatives of the basisfunctions on the reference (FIAT) element, # as the dot product of the new coefficients and basisvalues. code += _compute_reference_derivatives(data, dof_data) # Transform derivatives to physical element by multiplication with the transformation matrix. code += _transform_derivatives(data, dof_data) code = remove_unused("\n".join(code)) return code
def _generate_dof_code(data, dof_data): "Generate code for a basis." code = [] # Compute basisvalues, from evaluatebasis.py. code += _compute_basisvalues(data, dof_data) # Tabulate coefficients. code += _tabulate_coefficients(dof_data) # Tabulate coefficients for derivatives. code += _tabulate_dmats(dof_data) # Compute the derivatives of the basisfunctions on the reference (FIAT) element, # as the dot product of the new coefficients and basisvalues. code += _compute_reference_derivatives(data, dof_data) # Transform derivatives to physical element by multiplication with the transformation matrix. code += _transform_derivatives(data, dof_data) code = remove_unused("\n".join(code)) return code
def _tabulate_tensor(ir, parameters): "Generate code for a single integral (tabulate_tensor())." p_format = parameters["format"] precision = parameters["precision"] f_comment = format["comment"] f_G = format["geometry constant"] f_const_double = format["assign"] f_float = format["float"] f_assign = format["assign"] f_A = format["element tensor"][p_format] f_r = format["free indices"][0] f_j = format["first free index"] f_k = format["second free index"] f_loop = format["generate loop"] f_int = format["int"] f_weight = format["weight"] # Get data. opt_par = ir["optimise_parameters"] integral_type = ir["integral_type"] cell = ir["cell"] gdim = cell.geometric_dimension() tdim = cell.topological_dimension() num_facets = ir["num_facets"] num_vertices= ir["num_vertices"] integrals = ir["trans_integrals"] geo_consts = ir["geo_consts"] oriented = ir["needs_oriented"] # Create sets of used variables. used_weights = set() used_psi_tables = set() used_nzcs = set() trans_set = set() sets = [used_weights, used_psi_tables, used_nzcs, trans_set] affine_tables = {} # TODO: This is not populated anywhere, remove? quadrature_weights = ir["quadrature_weights"] #The pyop2 format requires dereferencing constant coefficients since # these are passed in as double * common = [] if p_format == "pyop2": for n, c in zip(ir["coefficient_names"], ir["coefficient_elements"]): if c.family() == 'Real': # Second index is always? 0, so we cast to (double (*)[1]). common += ['double (*w%(n)s)[1] = (double (*)[1])c%(n)s;\n' % {'n': n[1:]}] operations = [] if integral_type == "cell": # Update transformer with facets and generate code + set of used geometry terms. nest_ir, num_ops = _generate_element_tensor(integrals, sets, \ opt_par, parameters) # Set operations equal to num_ops (for printing info on operations). operations.append([num_ops]) # Generate code for basic geometric quantities # @@@: Jacobian snippet jacobi_code = "" jacobi_code += format["compute_jacobian"](cell) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](cell) if oriented and tdim != gdim: # NEED TO THINK ABOUT THIS FOR EXTRUSION jacobi_code += format["orientation"][p_format](tdim, gdim) jacobi_code += "\n" jacobi_code += format["scale factor snippet"][p_format] # Generate code for cell volume and circumradius -- note that the # former will be incorrect on extruded meshes by a constant factor. jacobi_code += "\n\n" + format["generate cell volume"][p_format](tdim, gdim, integral_type) jacobi_code += "\n\n" + format["generate circumradius"][p_format](tdim, gdim, integral_type) elif integral_type in ("exterior_facet", "exterior_facet_vert"): if p_format == 'pyop2': common += ["unsigned int facet = *facet_p;\n"] # Generate tensor code for facets + set of used geometry terms. nest_ir, ops = _generate_element_tensor(integrals, sets, opt_par, parameters) # Save number of operations (for printing info on operations). operations.append([ops]) # Generate code for basic geometric quantities # @@@: Jacobian snippet jacobi_code = "" jacobi_code += format["compute_jacobian"](cell) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](cell) if oriented and tdim != gdim: # NEED TO THINK ABOUT THIS FOR EXTRUSION jacobi_code += format["orientation"][p_format](tdim, gdim) jacobi_code += "\n" if integral_type == "exterior_facet": jacobi_code += "\n\n" + format["facet determinant"](cell, p_format, integral_type) jacobi_code += "\n\n" + format["generate normal"](cell, p_format, integral_type) jacobi_code += "\n\n" + format["generate facet area"](tdim, gdim) if tdim == 3: jacobi_code += "\n\n" + format["generate min facet edge length"](tdim, gdim) jacobi_code += "\n\n" + format["generate max facet edge length"](tdim, gdim) # Generate code for cell volume and circumradius jacobi_code += "\n\n" + format["generate cell volume"][p_format](tdim, gdim, integral_type) jacobi_code += "\n\n" + format["generate circumradius"][p_format](tdim, gdim, integral_type) elif integral_type == "exterior_facet_vert": jacobi_code += "\n\n" + format["facet determinant"](cell, p_format, integral_type) jacobi_code += "\n\n" + format["generate normal"](cell, p_format, integral_type) # OTHER THINGS NOT IMPLEMENTED YET else: raise RuntimeError("Invalid integral_type") elif integral_type in ("exterior_facet_top", "exterior_facet_bottom"): nest_ir, ops = _generate_element_tensor(integrals, sets, opt_par, parameters) operations.append([ops]) # Generate code for basic geometric quantities # @@@: Jacobian snippet jacobi_code = "" jacobi_code += format["compute_jacobian"](cell) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](cell) if oriented: # NEED TO THINK ABOUT THIS FOR EXTRUSION jacobi_code += format["orientation"][p_format](tdim, gdim) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"](cell, p_format, integral_type) jacobi_code += "\n\n" + format["generate normal"](cell, p_format, integral_type) # THE REST IS NOT IMPLEMENTED YET elif integral_type in ("interior_facet", "interior_facet_vert"): if p_format == 'pyop2': common += ["unsigned int facet_0 = facet_p[0];"] common += ["unsigned int facet_1 = facet_p[1];"] common += ["double **coordinate_dofs_0 = coordinate_dofs;"] # Note that the following line is unsafe for isoparametric elements. common += ["double **coordinate_dofs_1 = coordinate_dofs + %d;" % num_vertices] # Generate tensor code for facets + set of used geometry terms. nest_ir, ops = _generate_element_tensor(integrals, sets, opt_par, parameters) # Save number of operations (for printing info on operations). operations.append([ops]) # Generate code for basic geometric quantities # @@@: Jacobian snippet jacobi_code = "" for _r in ["+", "-"]: if p_format == "pyop2": jacobi_code += format["compute_jacobian_interior"](cell, r=_r) else: jacobi_code += format["compute_jacobian"](cell, r=_r) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](cell, r=_r) if oriented and tdim != gdim: # NEED TO THINK ABOUT THIS FOR EXTRUSION jacobi_code += format["orientation"][p_format](tdim, gdim, r=_r) jacobi_code += "\n" if integral_type == "interior_facet": jacobi_code += "\n\n" + format["facet determinant"](cell, p_format, integral_type, r="+") jacobi_code += "\n\n" + format["generate normal"](cell, p_format, integral_type) jacobi_code += "\n\n" + format["generate facet area"](tdim, gdim) if tdim == 3: jacobi_code += "\n\n" + format["generate min facet edge length"](tdim, gdim, r="+") jacobi_code += "\n\n" + format["generate max facet edge length"](tdim, gdim, r="+") # Generate code for cell volume and circumradius jacobi_code += "\n\n" + format["generate cell volume"][p_format](tdim, gdim, integral_type) jacobi_code += "\n\n" + format["generate circumradius interior"](tdim, gdim, integral_type) elif integral_type == "interior_facet_vert": # THE REST IS NOT IMPLEMENTED YET jacobi_code += "\n\n" + format["facet determinant"](cell, p_format, integral_type, r="+") jacobi_code += "\n\n" + format["generate normal"](cell, p_format, integral_type) else: raise RuntimeError("Invalid integral_type") elif integral_type == "interior_facet_horiz": common += ["double **coordinate_dofs_0 = coordinate_dofs;"] # Note that the following line is unsafe for isoparametric elements. common += ["double **coordinate_dofs_1 = coordinate_dofs + %d;" % num_vertices] nest_ir, ops = _generate_element_tensor(integrals, sets, opt_par, parameters) # Save number of operations (for printing info on operations). operations.append([ops]) # Generate code for basic geometric quantities # @@@: Jacobian snippet jacobi_code = "" for _r in ["+", "-"]: jacobi_code += format["compute_jacobian_interior"](cell, r=_r) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](cell, r=_r) if oriented: # NEED TO THINK ABOUT THIS FOR EXTRUSION jacobi_code += format["orientation"][p_format](tdim, gdim, r=_r) jacobi_code += "\n" # TODO: verify that this is correct (we think it is) jacobi_code += "\n\n" + format["facet determinant"](cell, p_format, integral_type, r="+") jacobi_code += "\n\n" + format["generate normal"](cell, p_format, integral_type) # THE REST IS NOT IMPLEMENTED YET elif integral_type == "point": # Update transformer with vertices and generate code + set of used geometry terms. nest_ir, ops = _generate_element_tensor(integrals, sets, opt_par, parameters) # Save number of operations (for printing info on operations). operations.append([ops]) # Generate code for basic geometric quantities # @@@: Jacobian snippet jacobi_code = "" jacobi_code += format["compute_jacobian"](cell) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](cell) if oriented and tdim != gdim: jacobi_code += format["orientation"][p_format](tdim, gdim) jacobi_code += "\n" else: error("Unhandled integral type: " + str(integral_type)) # Embedded manifold, need to pass in cell orientations if oriented and tdim != gdim and p_format == 'pyop2': if integral_type in ("interior_facet", "interior_facet_vert", "interior_facet_horiz"): common += ["const int cell_orientation%s = cell_orientation_[0][0];" % _choose_map('+'), "const int cell_orientation%s = cell_orientation_[1][0];" % _choose_map('-')] else: common += ["const int cell_orientation = cell_orientation_[0][0];"] # After we have generated the element code for all facets we can remove # the unused transformations and tabulate the used psi tables and weights. common += [remove_unused(jacobi_code, trans_set)] jacobi_ir = pyop2.FlatBlock("\n".join(common)) # @@@: const double W3[3] = {{...}} pyop2_weights = [] for weights, points in [quadrature_weights[p] for p in used_weights]: n_points = len(points) w_sym = pyop2.Symbol(f_weight(n_points), () if n_points == 1 else (n_points,)) pyop2_weights.append(pyop2.Decl("double", w_sym, pyop2.ArrayInit(weights, precision), qualifiers=["static", "const"])) name_map = ir["name_map"] tables = ir["unique_tables"] tables.update(affine_tables) # TODO: This is not populated anywhere, remove? # @@@: const double FE0[] = {{...}} code, decl = _tabulate_psis(tables, used_psi_tables, name_map, used_nzcs, opt_par, parameters) pyop2_basis = [] for name, data in decl.items(): rank, _, values = data zeroflags = values.get_zeros() feo_sym = pyop2.Symbol(name, rank) init = pyop2.ArrayInit(values, precision) if zeroflags is not None and not zeroflags.all(): nz_indices = numpy.logical_not(zeroflags).nonzero() # Note: in the following, we take the last entry of /nz_indices/ since we /know/ # we have been tracking only zero-valued columns nz_indices = nz_indices[-1] nz_bounds = tuple([(i, 0)] for i in rank[:-1]) nz_bounds += ([(max(nz_indices) - min(nz_indices) + 1, min(nz_indices))],) init = pyop2.SparseArrayInit(values, precision, nz_bounds) pyop2_basis.append(pyop2.Decl("double", feo_sym, init, ["static", "const"])) # Build the root of the PyOP2' ast pyop2_tables = pyop2_weights + [tab for tab in pyop2_basis] root = pyop2.Root([jacobi_ir] + pyop2_tables + nest_ir) return root
def _tabulate_tensor(ir, parameters): "Generate code for tabulate_tensor." # Prefetch formats to speed up code generation comment = format["comment"] switch = format["switch"] # Set of used variables for Jacobian and geometry tensor j_set = set() g_set = set() # Extract data from intermediate representation AK = ir["AK"] integral_type = ir["integral_type"] tdim = ir["cell"].topological_dimension() gdim = ir["cell"].geometric_dimension() cell = ir["cell"] oriented = ir["needs_oriented"] num_facets = ir["num_facets"] # Check integral type and generate code if integral_type == "cell": # Generate code for one single tensor contraction t_code = _generate_tensor_contraction(AK, parameters, g_set) # Generate code for geometry tensors g_code = _generate_geometry_tensors(AK, j_set, g_set, tdim, gdim) # Generate code for basic geometric quantities j_code = "" j_code += format["compute_jacobian"](cell) j_code += "\n" j_code += format["compute_jacobian_inverse"](cell) if oriented: j_code += format["orientation"]["ufc"](tdim, gdim) j_code += "\n" j_code += format["scale factor snippet"]["ufc"] elif integral_type == "exterior_facet": # Generate code for num_facets tensor contractions cases = [None for i in range(num_facets)] for i in range(num_facets): cases[i] = _generate_tensor_contraction(AK[i], parameters, g_set) t_code = switch(format["facet"](None), cases) # Generate code for geometry tensors g_code = _generate_geometry_tensors(AK[0], j_set, g_set, tdim, gdim) # Generate code for Jacobian j_code = "" j_code += format["compute_jacobian"](cell) j_code += "\n" j_code += format["compute_jacobian_inverse"](cell) if oriented: j_code += format["orientation"]["ufc"](tdim, gdim) j_code += "\n" j_code += format["facet determinant"]["ufc"](tdim, gdim) elif integral_type == "interior_facet": # Generate code for num_facets x num_facets tensor contractions cases = [[None for j in range(num_facets)] for i in range(num_facets)] for i in range(num_facets): for j in range(num_facets): cases[i][j] = _generate_tensor_contraction( AK[i][j], parameters, g_set) t_code = switch(format["facet"]("+"), [ switch(format["facet"]("-"), cases[i]) for i in range(len(cases)) ]) # Generate code for geometry tensors g_code = _generate_geometry_tensors(AK[0][0], j_set, g_set, tdim, gdim) # Generate code for Jacobian j_code = "" for _r in ["+", "-"]: j_code += format["compute_jacobian"](cell, r=_r) j_code += "\n" j_code += format["compute_jacobian_inverse"](cell, r=_r) j_code += "\n" if oriented: j_code += format["orientation"]["ufc"](tdim, gdim, r=_r) j_code += format["facet determinant"]["ufc"](tdim, gdim, r="+") j_code += "\n" else: error("Unhandled integral type: " + str(integral_type)) # Remove unused declarations from Jacobian code j_code = remove_unused(j_code, j_set) # Compute total number of operations j_ops, g_ops, t_ops = [count_ops(c) for c in (j_code, g_code, t_code)] total_ops = j_ops + g_ops + t_ops # Add generated code lines = [] lines.append( comment( "Number of operations (multiply-add pairs) for Jacobian data: %d" % j_ops)) lines.append( comment( "Number of operations (multiply-add pairs) for geometry tensor: %d" % g_ops)) lines.append( comment( "Number of operations (multiply-add pairs) for tensor contraction: %d" % t_ops)) lines.append( comment( "Total number of operations (multiply-add pairs): %d" % total_ops)) lines.append("") lines.append(j_code) lines.append("") lines.append(comment("Compute geometry tensor")) lines.append(g_code) lines.append("") lines.append(comment("Compute element tensor")) lines.append(t_code) return "\n".join(lines)
def _tabulate_tensor(ir, parameters): "Generate code for tabulate_tensor." # Prefetch formats to speed up code generation comment = format["comment"] switch = format["switch"] # Set of used variables for Jacobian and geometry tensor j_set = set() g_set = set() # Extract data from intermediate representation AK = ir["AK"] domain_type = ir["domain_type"] tdim = ir["topological_dimension"] gdim = ir["geometric_dimension"] oriented = ir["needs_oriented"] num_facets = ir["num_facets"] # Check integral type and generate code if domain_type == "cell": # Generate code for one single tensor contraction t_code = _generate_tensor_contraction(AK, parameters, g_set) # Generate code for geometry tensors g_code = _generate_geometry_tensors(AK, j_set, g_set, tdim, gdim) # Generate code for basic geometric quantities j_code = "" j_code += format["compute_jacobian"](tdim, gdim) j_code += "\n" j_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: j_code += format["orientation"](tdim, gdim) j_code += "\n" j_code += format["scale factor snippet"] elif domain_type == "exterior_facet": # Generate code for num_facets tensor contractions cases = [None for i in range(num_facets)] for i in range(num_facets): cases[i] = _generate_tensor_contraction(AK[i], parameters, g_set) t_code = switch(format["facet"](None), cases) # Generate code for geometry tensors g_code = _generate_geometry_tensors(AK[0], j_set, g_set, tdim, gdim) # Generate code for Jacobian j_code = "" j_code += format["compute_jacobian"](tdim, gdim) j_code += "\n" j_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: j_code += format["orientation"](tdim, gdim) j_code += "\n" j_code += format["facet determinant"](tdim, gdim) elif domain_type == "interior_facet": # Generate code for num_facets x num_facets tensor contractions cases = [[None for j in range(num_facets)] for i in range(num_facets)] for i in range(num_facets): for j in range(num_facets): cases[i][j] = _generate_tensor_contraction(AK[i][j], parameters, g_set) t_code = switch(format["facet"]("+"), [switch(format["facet"]("-"), cases[i]) for i in range(len(cases))]) # Generate code for geometry tensors g_code = _generate_geometry_tensors(AK[0][0], j_set, g_set, tdim, gdim) # Generate code for Jacobian j_code = "" for _r in ["+", "-"]: j_code += format["compute_jacobian"](tdim, gdim, r=_r) j_code += "\n" j_code += format["compute_jacobian_inverse"](tdim, gdim, r=_r) j_code += "\n" j_code += format["facet determinant"](tdim, gdim, r="+") j_code += "\n" else: error("Unhandled integral type: " + str(domain_type)) # Remove unused declarations from Jacobian code j_code = remove_unused(j_code, j_set) # Compute total number of operations j_ops, g_ops, t_ops = [count_ops(c) for c in (j_code, g_code, t_code)] total_ops = j_ops + g_ops + t_ops # Add generated code lines = [] lines.append(comment("Number of operations (multiply-add pairs) for Jacobian data: %d" % j_ops)) lines.append(comment("Number of operations (multiply-add pairs) for geometry tensor: %d" % g_ops)) lines.append(comment("Number of operations (multiply-add pairs) for tensor contraction: %d" % t_ops)) lines.append(comment("Total number of operations (multiply-add pairs): %d" % total_ops)) lines.append("") lines.append(j_code) lines.append("") lines.append(comment("Compute geometry tensor")) lines.append(g_code) lines.append("") lines.append(comment("Compute element tensor")) lines.append(t_code) return "\n".join(lines)
def _evaluate_basis_derivatives(data): """Evaluate the derivatives of an element basisfunction at a point. The values are computed as in FIAT as the matrix product of the coefficients (computed at compile time), basisvalues which are dependent on the coordinate and thus have to be computed at run time and combinations (depending on the order of derivative) of dmats tables which hold the derivatives of the expansion coefficients.""" if isinstance(data, str): return format["exception"]("evaluate_basis_derivatives: %s" % data) # Initialise return code. code = [] # Get the element cell domain, geometric and topological dimension. element_cellname = data["cellname"] gdim = data["geometric_dimension"] tdim = data["topological_dimension"] max_degree = data["max_degree"] # Compute number of derivatives that has to be computed, and # declare an array to hold the values of the derivatives on the # reference element. code += [""] if tdim == gdim: _t = "" _g = "" code += _compute_num_derivatives(tdim, "") # Reset all values. code += _reset_values(data, _g) # Handle values of argument 'n'. code += _handle_degree(max_degree) # If max_degree is zero, return code (to avoid declarations such as # combinations[1][0]) and because there's nothing to compute.) if max_degree == 0: return remove_unused("\n".join(code)) # Generate geo code. code += _geometry_related_code(data, tdim, gdim, element_cellname) # Generate all possible combinations of derivatives. code += _generate_combinations(tdim, "", max_degree) else: _t = "_t" _g = "_g" code += _compute_num_derivatives(tdim, _t) code += [""] code += _compute_num_derivatives(gdim, _g) # Reset all values. code += _reset_values(data, _g) # Handle values of argument 'n'. code += _handle_degree(max_degree) # If max_degree is zero, return code (to avoid declarations such as # combinations[1][0]) and because there's nothing to compute.) if max_degree == 0: return remove_unused("\n".join(code)) # Generate geo code. code += _geometry_related_code(data, tdim, gdim, element_cellname) # Generate all possible combinations of derivatives. code += _generate_combinations(tdim, _t, max_degree) code += _generate_combinations(gdim, _g, max_degree) # Generate the transformation matrix. code += _generate_transform(element_cellname, gdim, tdim, max_degree) # Create code for all basis values (dofs). dof_cases = [] for dof in data["dof_data"]: dof_cases.append(_generate_dof_code(data, dof)) code += [format["switch"](format["argument basis num"], dof_cases)] code = remove_unused("\n".join(code)) #code = "\n".join(code) return code
def _evaluate_basis(data): """Generate run time code to evaluate an element basisfunction at an arbitrary point. The value(s) of the basisfunction is/are computed as in FIAT as the dot product of the coefficients (computed at compile time) and basisvalues which are dependent on the coordinate and thus have to be computed at run time. The function should work for all elements supported by FIAT, but it remains untested for tensor valued elements.""" if isinstance(data, str): return format["exception"]("evaluate_basis: %s" % data) # Prefetch formats. f_assign = format["assign"] f_comment = format["comment"] f_values = format["argument values"] f_float = format["floating point"] f_component = format["component"] # Initialise return code. code = [] # Get the element cell name and geometric dimension. cell = data["cell"] element_cellname = cell.cellname() gdim = cell.geometric_dimension() tdim = cell.topological_dimension() # Get code snippets for Jacobian, Inverse of Jacobian and mapping of # coordinates from physical element to the FIAT reference element. code += [format["compute_jacobian"](cell)] code += [format["compute_jacobian_inverse"](cell)] if data["needs_oriented"]: code += [format["orientation"]["ufc"](tdim, gdim)] code += ["", format["fiat coordinate map"](element_cellname, gdim)] # Get value shape and reset values. This should also work for TensorElement, # scalar are empty tuples, therefore (1,) in which case value_shape = 1. reference_value_size = data["reference_value_size"] code += ["", f_comment("Reset values")] if reference_value_size == 1: # Reset values as a pointer. code += [ f_assign(format["dereference pointer"](f_values), f_float(0.0)) ] else: # Reset all values. code += [ f_assign(f_component(f_values, i), f_float(0.0)) for i in range(reference_value_size) ] # Create code for all basis values (dofs). dof_cases = [] for dof_data in data["dofs_data"]: dof_cases.append(_generate_dof_code(data, dof_data)) code += [format["switch"](format["argument basis num"], dof_cases)] # Remove unused variables (from transformations and mappings) in code. code = remove_unused("\n".join(code)) #code = "\n".join(code) return code
def _evaluate_basis_derivatives(data): """Evaluate the derivatives of an element basisfunction at a point. The values are computed as in FIAT as the matrix product of the coefficients (computed at compile time), basisvalues which are dependent on the coordinate and thus have to be computed at run time and combinations (depending on the order of derivative) of dmats tables which hold the derivatives of the expansion coefficients.""" if isinstance(data, str): return format["exception"]("evaluate_basis_derivatives: %s" % data) # Initialise return code. code = [] # Get the element cell domain, geometric and topological dimension. element_cellname = data["cellname"] gdim = data["geometric_dimension"] tdim = data["topological_dimension"] max_degree = data["max_degree"] # Compute number of derivatives that has to be computed, and # declare an array to hold the values of the derivatives on the # reference element. code += [""] if tdim == gdim: _t = "" _g = "" code += _compute_num_derivatives(tdim, "") # Reset all values. code += _reset_values(data, _g) # Handle values of argument 'n'. code += _handle_degree(max_degree) # If max_degree is zero, return code (to avoid declarations such as # combinations[1][0]) and because there's nothing to compute.) if max_degree == 0: return remove_unused("\n".join(code)) # Generate geo code. code += _geometry_related_code(data, tdim, gdim, element_cellname) # Generate all possible combinations of derivatives. code += _generate_combinations(tdim, "", max_degree) else: _t = "_t" _g = "_g" code += _compute_num_derivatives(tdim, _t) code += [""] code += _compute_num_derivatives(gdim, _g) # Reset all values. code += _reset_values(data, _g) # Handle values of argument 'n'. code += _handle_degree(max_degree) # If max_degree is zero, return code (to avoid declarations such as # combinations[1][0]) and because there's nothing to compute.) if max_degree == 0: return remove_unused("\n".join(code)) # Generate geo code. code += _geometry_related_code(data, tdim, gdim, element_cellname) # Generate all possible combinations of derivatives. code += _generate_combinations(tdim, _t, max_degree) code += _generate_combinations(gdim, _g, max_degree) # Generate the transformation matrix. code += _generate_transform(element_cellname, gdim, tdim, max_degree) # Create code for all basis values (dofs). dof_cases = [] for dof in data["dof_data"]: dof_cases.append(_generate_dof_code(data, dof)) code += [format["switch"](format["argument basis num"], dof_cases)] code = remove_unused("\n".join(code)) #code = "\n".join(code) return code
def _tabulate_tensor(ir, parameters): "Generate code for a single integral (tabulate_tensor())." f_comment = format["comment"] f_G = format["geometry constant"] f_const_double = format["assign"] f_switch = format["switch"] f_float = format["float"] f_assign = format["assign"] f_A = format["element tensor"] f_r = format["free indices"][0] f_loop = format["generate loop"] f_int = format["int"] f_facet = format["facet"] # Get data. opt_par = ir["optimise_parameters"] domain_type = ir["domain_type"] gdim = ir["geometric_dimension"] tdim = ir["topological_dimension"] num_facets = ir["num_facets"] num_vertices= ir["num_vertices"] prim_idims = ir["prim_idims"] integrals = ir["trans_integrals"] geo_consts = ir["geo_consts"] oriented = ir["needs_oriented"] # Create sets of used variables. used_weights = set() used_psi_tables = set() used_nzcs = set() trans_set = set() sets = [used_weights, used_psi_tables, used_nzcs, trans_set] affine_tables = {} # TODO: This is not populated anywhere, remove? quadrature_weights = ir["quadrature_weights"] operations = [] if domain_type == "cell": # Update treansformer with facets and generate code + set of used geometry terms. tensor_code, mem_code, num_ops = _generate_element_tensor(integrals, sets, \ opt_par) tensor_code = "\n".join(tensor_code) # Set operations equal to num_ops (for printing info on operations). operations.append([num_ops]) # Generate code for basic geometric quantities jacobi_code = "" jacobi_code += format["compute_jacobian"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["scale factor snippet"] elif domain_type == "exterior_facet": cases = [None for i in range(num_facets)] for i in range(num_facets): # Update treansformer with facets and generate case code + set of used geometry terms. c, mem_code, ops = _generate_element_tensor(integrals[i], sets, opt_par) case = [f_comment("Total number of operations to compute element tensor (from this point): %d" % ops)] case += c cases[i] = "\n".join(case) # Save number of operations (for printing info on operations). operations.append([i, ops]) # Generate tensor code for all cases using a switch. tensor_code = f_switch(f_facet(None), cases) # Generate code for basic geometric quantities jacobi_code = "" jacobi_code += format["compute_jacobian"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"](tdim, gdim) jacobi_code += "\n\n" + format["generate normal"](tdim, gdim, domain_type) jacobi_code += "\n\n" + format["generate facet area"](tdim, gdim) if tdim == 3: jacobi_code += "\n\n" + format["generate min facet edge length"](tdim, gdim) jacobi_code += "\n\n" + format["generate max facet edge length"](tdim, gdim) elif domain_type == "interior_facet": # Modify the dimensions of the primary indices because we have a macro element prim_idims = [d*2 for d in prim_idims] cases = [[None for j in range(num_facets)] for i in range(num_facets)] for i in range(num_facets): for j in range(num_facets): # Update transformer with facets and generate case code + set of used geometry terms. c, mem_code, ops = _generate_element_tensor(integrals[i][j], sets, \ opt_par) case = [f_comment("Total number of operations to compute element tensor (from this point): %d" % ops)] case += c cases[i][j] = "\n".join(case) # Save number of operations (for printing info on operations). operations.append([i, j, ops]) # Generate tensor code for all cases using a switch. tensor_code = f_switch(f_facet("+"), [f_switch(f_facet("-"), cases[i]) for i in range(len(cases))]) # Generate code for basic geometric quantities jacobi_code = "" for _r in ["+", "-"]: jacobi_code += format["compute_jacobian"](tdim, gdim, r=_r) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim, r=_r) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"](tdim, gdim, r="+") jacobi_code += "\n\n" + format["generate normal"](tdim, gdim, domain_type) jacobi_code += "\n\n" + format["generate facet area"](tdim, gdim) if tdim == 3: jacobi_code += "\n\n" + format["generate min facet edge length"](tdim, gdim, r="+") jacobi_code += "\n\n" + format["generate max facet edge length"](tdim, gdim, r="+") elif domain_type == "point": cases = [None for i in range(num_vertices)] for i in range(num_vertices): # Update treansformer with vertices and generate case code + # set of used geometry terms. c, mem_code, ops = _generate_element_tensor(integrals[i], sets, opt_par) case = [f_comment("Total number of operations to compute element tensor (from this point): %d" % ops)] case += c cases[i] = "\n".join(case) # Save number of operations (for printing info on operations). operations.append([i, ops]) # Generate tensor code for all cases using a switch. tensor_code = f_switch(format["vertex"], cases) # Generate code for basic geometric quantities jacobi_code = "" jacobi_code += format["compute_jacobian"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"](tdim, gdim) # FIXME: This is not defined in a point??? else: error("Unhandled integral type: " + str(integral_type)) # Add common (for cell, exterior and interior) geo code. if domain_type != "point": jacobi_code += "\n\n" + format["generate cell volume"](tdim, gdim, domain_type) jacobi_code += "\n\n" + format["generate circumradius"](tdim, gdim, domain_type) # After we have generated the element code for all facets we can remove # the unused transformations and tabulate the used psi tables and weights. common = [remove_unused(jacobi_code, trans_set)] common += _tabulate_weights([quadrature_weights[p] for p in used_weights]) name_map = ir["name_map"] tables = ir["unique_tables"] tables.update(affine_tables) # TODO: This is not populated anywhere, remove? common += _tabulate_psis(tables, used_psi_tables, name_map, used_nzcs, opt_par) # Reset the element tensor (array 'A' given as argument to tabulate_tensor() by assembler) # Handle functionals. common += [f_comment("Reset values in the element tensor.")] value = f_float(0) if prim_idims == []: common += [f_assign(f_A(f_int(0)), f_float(0))] else: dim = functools.reduce(lambda v,u: v*u, prim_idims) common += f_loop([f_assign(f_A(f_r), f_float(0))], [(f_r, 0, dim)]) # Create the constant geometry declarations (only generated if simplify expressions are enabled). geo_ops, geo_code = generate_aux_constants(geo_consts, f_G, f_const_double) if geo_code: common += [f_comment("Number of operations to compute geometry constants: %d." % geo_ops)] common += [format["declaration"](format["float declaration"], f_G(len(geo_consts)))] common += geo_code # Add comments. common += ["", f_comment("Compute element tensor using UFL quadrature representation")] common += [f_comment("Optimisations: %s" % ", ".join([str((k, opt_par[k]))\ for k in sorted(opt_par.keys())]))] # Print info on operation count. message = {"cell": "Cell, number of operations to compute tensor: %d", "exterior_facet": "Exterior facet %d, number of operations to compute tensor: %d", "interior_facet": "Interior facets (%d, %d), number of operations to compute tensor: %d", "point": "Point %d, number of operations to compute tensor: %d"} for ops in operations: # Add geo ops count to integral ops count for writing info. ops[-1] += geo_ops info(message[domain_type] % tuple(ops)) return "\n".join(common) + "\n" + tensor_code
def _tabulate_tensor(ir, parameters): "Generate code for a single integral (tabulate_tensor())." f_comment = format["comment"] f_G = format["geometry constant"] f_const_double = format["assign"] f_switch = format["switch"] f_float = format["float"] f_assign = format["assign"] f_A = format["element tensor"] f_r = format["free indices"][0] f_loop = format["generate loop"] f_int = format["int"] f_facet = format["facet"] # Get data. opt_par = ir["optimise_parameters"] domain_type = ir["domain_type"] gdim = ir["geometric_dimension"] tdim = ir["topological_dimension"] num_facets = ir["num_facets"] num_vertices = ir["num_vertices"] prim_idims = ir["prim_idims"] integrals = ir["trans_integrals"] geo_consts = ir["geo_consts"] oriented = ir["needs_oriented"] # Create sets of used variables. used_weights = set() used_psi_tables = set() used_nzcs = set() trans_set = set() sets = [used_weights, used_psi_tables, used_nzcs, trans_set] affine_tables = {} # TODO: This is not populated anywhere, remove? quadrature_weights = ir["quadrature_weights"] operations = [] if domain_type == "cell": # Update treansformer with facets and generate code + set of used geometry terms. tensor_code, mem_code, num_ops = _generate_element_tensor(integrals, sets, \ opt_par) tensor_code = "\n".join(tensor_code) # Set operations equal to num_ops (for printing info on operations). operations.append([num_ops]) # Generate code for basic geometric quantities jacobi_code = "" jacobi_code += format["compute_jacobian"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["scale factor snippet"] elif domain_type == "exterior_facet": cases = [None for i in range(num_facets)] for i in range(num_facets): # Update treansformer with facets and generate case code + set of used geometry terms. c, mem_code, ops = _generate_element_tensor( integrals[i], sets, opt_par) case = [ f_comment( "Total number of operations to compute element tensor (from this point): %d" % ops) ] case += c cases[i] = "\n".join(case) # Save number of operations (for printing info on operations). operations.append([i, ops]) # Generate tensor code for all cases using a switch. tensor_code = f_switch(f_facet(None), cases) # Generate code for basic geometric quantities jacobi_code = "" jacobi_code += format["compute_jacobian"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"](tdim, gdim) jacobi_code += "\n\n" + format["generate normal"](tdim, gdim, domain_type) jacobi_code += "\n\n" + format["generate facet area"](tdim, gdim) if tdim == 3: jacobi_code += "\n\n" + format["generate min facet edge length"]( tdim, gdim) jacobi_code += "\n\n" + format["generate max facet edge length"]( tdim, gdim) elif domain_type == "interior_facet": # Modify the dimensions of the primary indices because we have a macro element prim_idims = [d * 2 for d in prim_idims] cases = [[None for j in range(num_facets)] for i in range(num_facets)] for i in range(num_facets): for j in range(num_facets): # Update transformer with facets and generate case code + set of used geometry terms. c, mem_code, ops = _generate_element_tensor(integrals[i][j], sets, \ opt_par) case = [ f_comment( "Total number of operations to compute element tensor (from this point): %d" % ops) ] case += c cases[i][j] = "\n".join(case) # Save number of operations (for printing info on operations). operations.append([i, j, ops]) # Generate tensor code for all cases using a switch. tensor_code = f_switch( f_facet("+"), [f_switch(f_facet("-"), cases[i]) for i in range(len(cases))]) # Generate code for basic geometric quantities jacobi_code = "" for _r in ["+", "-"]: jacobi_code += format["compute_jacobian"](tdim, gdim, r=_r) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim, r=_r) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"](tdim, gdim, r="+") jacobi_code += "\n\n" + format["generate normal"](tdim, gdim, domain_type) jacobi_code += "\n\n" + format["generate facet area"](tdim, gdim) if tdim == 3: jacobi_code += "\n\n" + format["generate min facet edge length"]( tdim, gdim, r="+") jacobi_code += "\n\n" + format["generate max facet edge length"]( tdim, gdim, r="+") elif domain_type == "point": cases = [None for i in range(num_vertices)] for i in range(num_vertices): # Update treansformer with vertices and generate case code + # set of used geometry terms. c, mem_code, ops = _generate_element_tensor( integrals[i], sets, opt_par) case = [ f_comment( "Total number of operations to compute element tensor (from this point): %d" % ops) ] case += c cases[i] = "\n".join(case) # Save number of operations (for printing info on operations). operations.append([i, ops]) # Generate tensor code for all cases using a switch. tensor_code = f_switch(format["vertex"], cases) # Generate code for basic geometric quantities jacobi_code = "" jacobi_code += format["compute_jacobian"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"]( tdim, gdim) # FIXME: This is not defined in a point??? else: error("Unhandled integral type: " + str(integral_type)) # Add common (for cell, exterior and interior) geo code. if domain_type != "point": jacobi_code += "\n\n" + format["generate cell volume"](tdim, gdim, domain_type) jacobi_code += "\n\n" + format["generate circumradius"](tdim, gdim, domain_type) # After we have generated the element code for all facets we can remove # the unused transformations and tabulate the used psi tables and weights. common = [remove_unused(jacobi_code, trans_set)] common += _tabulate_weights([quadrature_weights[p] for p in used_weights]) name_map = ir["name_map"] tables = ir["unique_tables"] tables.update( affine_tables) # TODO: This is not populated anywhere, remove? common += _tabulate_psis(tables, used_psi_tables, name_map, used_nzcs, opt_par) # Reset the element tensor (array 'A' given as argument to tabulate_tensor() by assembler) # Handle functionals. common += [f_comment("Reset values in the element tensor.")] value = f_float(0) if prim_idims == []: common += [f_assign(f_A(f_int(0)), f_float(0))] else: dim = functools.reduce(lambda v, u: v * u, prim_idims) common += f_loop([f_assign(f_A(f_r), f_float(0))], [(f_r, 0, dim)]) # Create the constant geometry declarations (only generated if simplify expressions are enabled). geo_ops, geo_code = generate_aux_constants(geo_consts, f_G, f_const_double) if geo_code: common += [ f_comment( "Number of operations to compute geometry constants: %d." % geo_ops) ] common += [ format["declaration"](format["float declaration"], f_G(len(geo_consts))) ] common += geo_code # Add comments. common += [ "", f_comment("Compute element tensor using UFL quadrature representation") ] common += [f_comment("Optimisations: %s" % ", ".join([str((k, opt_par[k]))\ for k in sorted(opt_par.keys())]))] # Print info on operation count. message = { "cell": "Cell, number of operations to compute tensor: %d", "exterior_facet": "Exterior facet %d, number of operations to compute tensor: %d", "interior_facet": "Interior facets (%d, %d), number of operations to compute tensor: %d", "point": "Point %d, number of operations to compute tensor: %d" } for ops in operations: # Add geo ops count to integral ops count for writing info. ops[-1] += geo_ops info(message[domain_type] % tuple(ops)) return "\n".join(common) + "\n" + tensor_code
def _tabulate_tensor(ir, prefix, parameters): "Generate code for a single integral (tabulate_tensor())." # Prefetch formatting to speedup code generation f_comment = format["comment"] f_G = format["geometry constant"] f_const_double = format["assign"] f_switch = format["switch"] f_float = format["float"] f_assign = format["assign"] f_A = format["element tensor"] f_r = format["free indices"][0] f_loop = format["generate loop"] f_int = format["int"] f_facet = format["facet"] # Get data opt_par = ir["optimise_parameters"] integral_type = ir["integral_type"] gdim = ir["geometric_dimension"] tdim = ir["topological_dimension"] num_facets = ir["num_facets"] num_vertices = ir["num_vertices"] prim_idims = ir["prim_idims"] integrals = ir["trans_integrals"] geo_consts = ir["geo_consts"] oriented = ir["needs_oriented"] element_data = ir["element_data"] num_cells = ir["num_cells"] special = ir["special"] # Create sets of used variables used_weights = set() used_psi_tables = set() used_nzcs = set() trans_set = set() sets = [used_weights, used_psi_tables, used_nzcs, trans_set] affine_tables = {} # TODO: This is not populated anywhere, remove? quadrature_weights = ir["quadrature_weights"] operations = [] if integral_type == "cell": # Generate code for computing element tensor tensor_code, mem_code, num_ops = _generate_element_tensor(integrals, sets, opt_par, gdim) tensor_code = "\n".join(tensor_code) # Set operations equal to num_ops (for printing info on operations). operations.append([num_ops]) # Generate code for basic geometric quantities jacobi_code = "" jacobi_code += format["compute_jacobian"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["scale factor snippet"] # Generate code for cell volume and circumradius jacobi_code += "\n\n" + format["generate cell volume"](tdim, gdim, integral_type) jacobi_code += "\n\n" + format["generate circumradius"](tdim, gdim, integral_type) elif integral_type == "exterior_facet": # Iterate over facets cases = [None for i in range(num_facets)] for i in range(num_facets): # Update transformer with facets and generate case code + set of used geometry terms. c, mem_code, ops = _generate_element_tensor(integrals[i], sets, opt_par, gdim) case = [f_comment("Total number of operations to compute element tensor (from this point): %d" % ops)] case += c cases[i] = "\n".join(case) # Save number of operations (for printing info on operations). operations.append([i, ops]) # Generate tensor code for all cases using a switch. tensor_code = f_switch(f_facet(None), cases) # Generate code for basic geometric quantities jacobi_code = "" jacobi_code += format["compute_jacobian"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"](tdim, gdim) jacobi_code += "\n\n" + format["generate normal"](tdim, gdim, integral_type) jacobi_code += "\n\n" + format["generate facet area"](tdim, gdim) if tdim == 3: jacobi_code += "\n\n" + format["generate min facet edge length"](tdim, gdim) jacobi_code += "\n\n" + format["generate max facet edge length"](tdim, gdim) # Generate code for cell volume and circumradius jacobi_code += "\n\n" + format["generate cell volume"](tdim, gdim, integral_type) jacobi_code += "\n\n" + format["generate circumradius"](tdim, gdim, integral_type) elif integral_type == "interior_facet": # Modify the dimensions of the primary indices because we have a macro element prim_idims = [d*2 for d in prim_idims] # Iterate over combinations of facets cases = [[None for j in range(num_facets)] for i in range(num_facets)] for i in range(num_facets): for j in range(num_facets): # Update transformer with facets and generate case code + set of used geometry terms. c, mem_code, ops = _generate_element_tensor(integrals[i][j], sets, opt_par, gdim) case = [f_comment("Total number of operations to compute element tensor (from this point): %d" % ops)] case += c cases[i][j] = "\n".join(case) # Save number of operations (for printing info on operations). operations.append([i, j, ops]) # Generate tensor code for all cases using a switch. tensor_code = f_switch(f_facet("+"), [f_switch(f_facet("-"), cases[i]) for i in range(len(cases))]) # Generate code for basic geometric quantities jacobi_code = "" for _r in ["+", "-"]: jacobi_code += format["compute_jacobian"](tdim, gdim, r=_r) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim, r=_r) if oriented: jacobi_code += format["orientation"](tdim, gdim, r=_r) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"](tdim, gdim, r="+") jacobi_code += "\n\n" + format["generate normal"](tdim, gdim, integral_type) jacobi_code += "\n\n" + format["generate facet area"](tdim, gdim) if tdim == 3: jacobi_code += "\n\n" + format["generate min facet edge length"](tdim, gdim, r="+") jacobi_code += "\n\n" + format["generate max facet edge length"](tdim, gdim, r="+") # Generate code for cell volume and circumradius jacobi_code += "\n\n" + format["generate cell volume"](tdim, gdim, integral_type) jacobi_code += "\n\n" + format["generate circumradius"](tdim, gdim, integral_type) elif integral_type == "point": # Iterate over vertices cases = [None for i in range(num_vertices)] for i in range(num_vertices): # Update transformer with vertices and generate case code + # set of used geometry terms. c, mem_code, ops = _generate_element_tensor(integrals[i], sets, opt_par, gdim) case = [f_comment("Total number of operations to compute element tensor (from this point): %d" % ops)] case += c cases[i] = "\n".join(case) # Save number of operations (for printing info on operations). operations.append([i, ops]) # Generate tensor code for all cases using a switch. tensor_code = f_switch(format["vertex"], cases) # Generate code for basic geometric quantities jacobi_code = "" jacobi_code += format["compute_jacobian"](tdim, gdim) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim) if oriented: jacobi_code += format["orientation"](tdim, gdim) jacobi_code += "\n" jacobi_code += "\n\n" + format["facet determinant"](tdim, gdim) # FIXME: This is not defined in a point??? elif integral_type == "custom": # Warning that more than two cells in only partly supported. # The missing piece is to couple multiple cells to # restrictions other than '+' and '-'. if num_cells > 2: warning("Custom integrals with more than two cells only partly supported.") # Modify the dimensions of the primary indices because we have a macro element if num_cells == 2: prim_idims = [d*2 for d in prim_idims] # Check whether we need to generate facet normals generate_custom_facet_normal = num_cells == 2 # from IPython import embed # embed() # DEBUG STATEMENT # Generate code for computing element tensor tensor_code, mem_code, num_ops = _generate_element_tensor(integrals, sets, opt_par, gdim, generate_custom_facet_normal) tensor_code = "\n".join(tensor_code) # Set operations equal to num_ops (for printing info on operations). operations.append([num_ops]) # FIXME: Jacobi code is only needed when we use cell volume or circumradius. # FIXME: Does not seem to be removed by removed_unused. # Generate code for basic geometric quantities jacobi_code = "" for i in range(num_cells): r = i if num_cells > 1 else None jacobi_code += "\n" jacobi_code += f_comment("--- Compute geometric quantities on cell %d ---" % i) jacobi_code += "\n\n" if num_cells > 1: jacobi_code += f_comment("Extract vertex coordinates\n") jacobi_code += format["extract_cell_coordinates"]((tdim + 1)*gdim*i, r=i) jacobi_code += "\n\n" jacobi_code += format["compute_jacobian"](tdim, gdim, r=r) jacobi_code += "\n" jacobi_code += format["compute_jacobian_inverse"](tdim, gdim, r=r) jacobi_code += "\n" jacobi_code += format["generate cell volume"](tdim, gdim, integral_type, r=r if num_cells > 1 else None) jacobi_code += "\n" jacobi_code += format["generate circumradius"](tdim, gdim, integral_type, r=r if num_cells > 1 else None) jacobi_code += "\n" else: error("Unhandled integral type: " + str(integral_type)) # After we have generated the element code for all facets we can remove # the unused transformations. common = [remove_unused(jacobi_code, trans_set)] # FIXME: After introduction of custom integrals, the common code # here is not really common anymore. Think about how to # restructure this function. # Add common code except for custom integrals if integral_type != "custom": common += _tabulate_weights([quadrature_weights[p] for p in sorted(used_weights)]) # Add common code for updating tables name_map = ir["name_map"] tables = ir["unique_tables"] tables.update(affine_tables) # TODO: This is not populated anywhere, remove? common += _tabulate_psis(tables, used_psi_tables, name_map, used_nzcs, opt_par, integral_type, gdim) # Add special tabulation code for custom integral else: common += _evaluate_basis_at_quadrature_points(used_psi_tables, gdim, element_data, prefix, num_vertices, num_cells, special=="contact") # Reset the element tensor (array 'A' given as argument to tabulate_tensor() by assembler) # Handle functionals. common += [f_comment("Reset values in the element tensor.")] value = f_float(0) if prim_idims == []: common += [f_assign(f_A(f_int(0)), f_float(0))] else: dim = functools.reduce(lambda v, u: v*u, prim_idims) common += f_loop([f_assign(f_A(f_r), f_float(0))], [(f_r, 0, dim)]) # Create the constant geometry declarations (only generated if simplify expressions are enabled). geo_ops, geo_code = generate_aux_constants(geo_consts, f_G, f_const_double) if geo_code: common += [f_comment("Number of operations to compute geometry constants: %d." % geo_ops)] common += [format["declaration"](format["float declaration"], f_G(len(geo_consts)))] common += geo_code # Add comments. common += ["", f_comment("Compute element tensor using UFL quadrature representation")] common += [f_comment("Optimisations: %s" % ", ".join([str((k, opt_par[k]))\ for k in sorted(opt_par.keys())]))] # Print info on operation count. message = {"cell": "Cell, number of operations to compute tensor: %s", "exterior_facet": "Exterior facet %d, number of operations to compute tensor: %s", "interior_facet": "Interior facets (%d, %d), number of operations to compute tensor: %s", "point": "Point %s, number of operations to compute tensor: %s", "custom": "Custom domain, number of operations to compute tensor: %s"} for ops in operations: # Add geo ops count to integral ops count for writing info. if isinstance(ops[-1], int): ops[-1] += geo_ops return "\n".join(common) + "\n" + tensor_code