def tabulate_reference_dof_coordinates(self, L, ir, parameters): # TODO: Change signature to avoid copy? E.g. # virtual const std::vector<double> & tabulate_reference_dof_coordinates() const = 0; # See integral::enabled_coefficients for example # TODO: ensure points is a numpy array, # get tdim from points.shape[1], # place points in ir directly instead of the subdict ir = ir["tabulate_dof_coordinates"] # Raise error if tabulate_reference_dof_coordinates is ill-defined if not ir: msg = "tabulate_reference_dof_coordinates is not defined for this element" return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Extract coordinates and cell dimension tdim = ir["tdim"] points = ir["points"] # Output argument reference_dof_coordinates = L.Symbol("reference_dof_coordinates") # Reference coordinates dof_X = L.Symbol("dof_X") dof_X_values = [X[jj] for X in points for jj in range(tdim)] decl = L.ArrayDecl("static const double", dof_X, (len(points) * tdim,), values=dof_X_values) copy = L.MemCopy(dof_X, reference_dof_coordinates, tdim*len(points)) code = [decl, copy] return code
def evaluate_reference_basis_derivatives(self, L, ir, parameters): data = ir["evaluate_basis"] if isinstance(data, str): msg = "evaluate_reference_basis_derivatives: %s" % data return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) return generate_evaluate_reference_basis_derivatives(L, data, parameters)
def tabulate_reference_dof_coordinates(self, L, ir, parameters): # TODO: Change signature to avoid copy? E.g. # virtual const std::vector<double> & tabulate_reference_dof_coordinates() const = 0; # See integral::enabled_coefficients for example # TODO: ensure points is a numpy array, # get tdim from points.shape[1], # place points in ir directly instead of the subdict ir = ir["tabulate_dof_coordinates"] # Raise error if tabulate_reference_dof_coordinates is ill-defined if not ir: msg = "tabulate_reference_dof_coordinates is not defined for this element" return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Extract coordinates and cell dimension tdim = ir["tdim"] points = ir["points"] # Output argument reference_dof_coordinates = L.Symbol("reference_dof_coordinates") # Reference coordinates dof_X = L.Symbol("dof_X") dof_X_values = [X[jj] for X in points for jj in range(tdim)] decl = L.ArrayDecl("static const double", dof_X, (len(points) * tdim, ), values=dof_X_values) copy = L.MemCopy(dof_X, reference_dof_coordinates, tdim * len(points)) code = [decl, copy] return code
def evaluate_reference_basis(self, L, ir, parameters): data = ir["evaluate_basis"] if isinstance(data, str): msg = "evaluate_reference_basis: %s" % data return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) return generate_evaluate_reference_basis(L, data, parameters)
def evaluate_basis_all(self, L, ir, parameters): data=ir["evaluate_basis"] # Handle unsupported elements. if isinstance(data, str): msg = "evaluate_basis_all: %s" % data return [generate_error(L, msg, parameters["convert_exceptions_to_warnings"])] physical_value_size = data["physical_value_size"] space_dimension = data["space_dimension"] x = L.Symbol("x") coordinate_dofs = L.Symbol("coordinate_dofs") cell_orientation = L.Symbol("cell_orientation") values = L.Symbol("values") # Special case where space dimension is one (constant elements). if space_dimension == 1: code = [L.Comment("Element is constant, calling evaluate_basis."), L.Call("evaluate_basis", (0, values, x, coordinate_dofs, cell_orientation))] return code r = L.Symbol("r") dof_values = L.Symbol("dof_values") if physical_value_size == 1: code = [ L.Comment("Helper variable to hold value of a single dof."), L.VariableDecl("double", dof_values, 0.0), L.Comment("Loop dofs and call evaluate_basis"), L.ForRange(r, 0, space_dimension, index_type=index_type, body=[L.Call("evaluate_basis", (r, L.AddressOf(dof_values), x, coordinate_dofs, cell_orientation)), L.Assign(values[r], dof_values)] ) ] else: s = L.Symbol("s") code = [L.Comment("Helper variable to hold values of a single dof."), L.ArrayDecl("double", dof_values, physical_value_size, 0.0), L.Comment("Loop dofs and call evaluate_basis"), L.ForRange(r, 0, space_dimension, index_type=index_type, body=[L.Call("evaluate_basis", (r, dof_values, x, coordinate_dofs, cell_orientation)), L.ForRange(s, 0, physical_value_size, index_type=index_type, body=[L.Assign(values[r*physical_value_size+s], dof_values[s])]) ] ) ] return code
def _generate_body(L, i, dof, mapping, gdim, tdim, cell_shape, offset=0): "Generate code for a single dof." # EnrichedElement is handled by having [None, ..., None] dual basis if not dof: msg = "evaluate_dof(s) for enriched element not implemented." return ([generate_error(L, msg, True)], 0.0) points = list(dof.keys()) # Generate different code if multiple points. (Otherwise ffc # compile time blows up.) if len(points) > 1: return _generate_multiple_points_body(L, i, dof, mapping, gdim, tdim, offset) # Get weights for mapping reference point to physical x = points[0] w = reference_to_physical_map(cell_shape)(x) # Map point onto physical element: y = F_K(x) code = [] y = L.Symbol("y") coordinate_dofs = L.Symbol("coordinate_dofs") vals = L.Symbol("vals") c = L.Symbol("c") for j in range(gdim): yy = 0.0 for k in range(len(w)): yy += w[k]*coordinate_dofs[k*gdim + j] code += [L.Assign(y[j], yy)] # Evaluate function at physical point code += [L.Call("f.evaluate", (vals, y, c))] # Map function values to the reference element F = _change_variables(L, mapping, gdim, tdim, offset) # Simple affine functions deserve special case: if len(F) == 1: return (code, dof[x][0][0]*F[0]) # Flatten multi-indices (index_map, _) = build_component_numbering([tdim] * len(dof[x][0][1]), ()) # Take inner product between components and weights value = 0.0 for (w, k) in dof[x]: value += w*F[index_map[k]] # Return eval code and value return (code, value)
def _generate_body(L, i, dof, mapping, gdim, tdim, cell_shape, offset=0): "Generate code for a single dof." # EnrichedElement is handled by having [None, ..., None] dual basis if not dof: msg = "evaluate_dof(s) for enriched element not implemented." return ([generate_error(L, msg, True)], 0.0) points = list(dof.keys()) # Generate different code if multiple points. (Otherwise ffc # compile time blows up.) if len(points) > 1: return _generate_multiple_points_body(L, i, dof, mapping, gdim, tdim, offset) # Get weights for mapping reference point to physical x = points[0] w = reference_to_physical_map(cell_shape)(x) # Map point onto physical element: y = F_K(x) code = [] y = L.Symbol("y") coordinate_dofs = L.Symbol("coordinate_dofs") vals = L.Symbol("vals") c = L.Symbol("c") for j in range(gdim): yy = 0.0 for k in range(len(w)): yy += w[k] * coordinate_dofs[k * gdim + j] code += [L.Assign(y[j], yy)] # Evaluate function at physical point code += [L.Call("f.evaluate", (vals, y, c))] # Map function values to the reference element F = _change_variables(L, mapping, gdim, tdim, offset) # Simple affine functions deserve special case: if len(F) == 1: return (code, dof[x][0][0] * F[0]) # Flatten multi-indices (index_map, _) = build_component_numbering([tdim] * len(dof[x][0][1]), ()) # Take inner product between components and weights value = 0.0 for (w, k) in dof[x]: value += w * F[index_map[k]] # Return eval code and value return (code, value)
def generate_evaluate_reference_basis_derivatives(L, data, parameters): # Cutoff for feature to disable generation of this code (consider removing after benchmarking final result) if isinstance(data, str): msg = "evaluate_reference_basis_derivatives: %s" % (data, ) return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Get some known dimensions element_cellname = data["cellname"] tdim = data["topological_dimension"] max_degree = data["max_degree"] reference_value_size = data["reference_value_size"] num_dofs = len(data["dofs_data"]) # Output argument reference_values = L.Symbol("reference_values") # Input arguments order = L.Symbol("order") num_points = L.Symbol("num_points") X = L.Symbol("X") # Loop indices ip = L.Symbol("ip") # point idof = L.Symbol("i") # dof c = L.Symbol("c") # component r = L.Symbol("r") # derivative number # Define symbol for number of derivatives of given order num_derivatives = L.Symbol("num_derivatives") reference_values_size = num_points * num_dofs * num_derivatives * reference_value_size # FIXME: validate these dimensions ref_values = L.FlattenedArray(reference_values, dims=(num_points, num_dofs, num_derivatives, reference_value_size)) # From evaluatebasis.py: #ref_values = L.FlattenedArray(reference_values, dims=(num_points, num_dofs, reference_value_size)) # Initialization (zeroing) and cutoffs outside valid range of orders setup_code = [ # Cutoff to evaluate_basis for order 0 L.If(L.EQ(order, 0), [ L.Call("evaluate_reference_basis", (reference_values, num_points, X)), L.Return() ]), # Compute number of derivatives of this order L.VariableDecl("const " + index_type, num_derivatives, value=L.Call("std::pow", (tdim, order))), # Reset output values to zero L.MemZero(reference_values, reference_values_size), # Cutoff for higher order than we have L.If(L.GT(order, max_degree), L.Return()), ] # If max_degree is zero, we don't need to generate any more code if max_degree == 0: return setup_code # Tabulate dmats tables for all dofs and all derivative directions dmats_names, dmats_code = generate_tabulate_dmats(L, data["dofs_data"]) # Generate code with static tables of expansion coefficients tables_code, coefficients_for_dof = generate_expansion_coefficients( L, data["dofs_data"]) # Generate code to compute tables of basisvalues basisvalues_code, basisvalues_for_degree, need_fiat_coordinates = \ generate_compute_basisvalues(L, data["dofs_data"], element_cellname, tdim, X, ip) # Generate all possible combinations of derivatives. combinations_code, combinations = _generate_combinations( L, tdim, max_degree, order, num_derivatives) # Define symbols for variables populated inside dof switch derivatives = L.Symbol("derivatives") reference_offset = L.Symbol("reference_offset") num_components = L.Symbol("num_components") # Get number of components of each basis function (>1 for dofs of piola mapped subelements) num_components_values = [ dof_data["num_components"] for dof_data in data["dofs_data"] ] # Offset into parent mixed element to first component of each basis function reference_offset_values = [ dof_data["reference_offset"] for dof_data in data["dofs_data"] ] # Max dimensions for the reference derivatives for each dof max_num_derivatives = tdim**max_degree max_num_components = max(num_components_values) # Add constant tables of these numbers tables_code += [ L.ArrayDecl("const " + index_type, reference_offset, num_dofs, values=reference_offset_values), L.ArrayDecl("const " + index_type, num_components, num_dofs, values=num_components_values), ] # Access reference derivatives compactly derivs = L.FlattenedArray(derivatives, dims=(num_components[idof], num_derivatives)) # Create code for all basis values (dofs). dof_cases = [] for i_dof, dof_data in enumerate(data["dofs_data"]): embedded_degree = dof_data["embedded_degree"] basisvalues = basisvalues_for_degree[embedded_degree] shape_dmats = numpy.shape(dof_data["dmats"][0]) if shape_dmats[0] != shape_dmats[1]: error("Something is wrong with the dmats:\n%s" % str(dof_data["dmats"])) aux = L.Symbol("aux") dmats = L.Symbol("dmats") dmats_old = L.Symbol("dmats_old") dmats_name = dmats_names[i_dof] # Create dmats matrix by multiplication comb = L.Symbol("comb") s = L.Symbol("s") t = L.Symbol("t") u = L.Symbol("u") tu = L.Symbol("tu") aux_computation_code = [ L.ArrayDecl("double", aux, shape_dmats[0], values=0), L.Comment("Declare derivative matrix (of polynomial basis)."), L.ArrayDecl("double", dmats, shape_dmats, values=0), L.Comment("Initialize dmats."), L.VariableDecl(index_type, comb, combinations[r, 0]), L.MemCopy(L.AddressOf(dmats_name[comb][0][0]), L.AddressOf(dmats[0][0]), shape_dmats[0] * shape_dmats[1]), L.Comment("Looping derivative order to generate dmats."), L.ForRange(s, 1, order, index_type=index_type, body=[ L.Comment("Store previous dmats matrix."), L.ArrayDecl("double", dmats_old, shape_dmats), L.MemCopy(L.AddressOf(dmats[0][0]), L.AddressOf(dmats_old[0][0]), shape_dmats[0] * shape_dmats[1]), L.Comment("Resetting dmats."), L.MemZero(L.AddressOf(dmats[0][0]), shape_dmats[0] * shape_dmats[1]), L.Comment("Update dmats using an inner product."), L.Assign(comb, combinations[r, s]), L.ForRange(t, 0, shape_dmats[0], index_type=index_type, body=L.ForRange( u, 0, shape_dmats[1], index_type=index_type, body=L.ForRange( tu, 0, shape_dmats[0], index_type=index_type, body=L.AssignAdd( dmats[t, u], dmats_name[comb, t, tu] * dmats_old[tu, u])))) ]), L.ForRange(s, 0, shape_dmats[0], index_type=index_type, body=L.ForRange(t, 0, shape_dmats[1], index_type=index_type, body=L.AssignAdd( aux[s], dmats[s, t] * basisvalues[t]))) ] # Unrolled loop over components of basis function n = dof_data["num_components"] compute_ref_derivs_code = [ L.Assign(derivs[cc][r], 0.0) for cc in range(n) ] compute_ref_derivs_code += [ L.ForRange(s, 0, shape_dmats[0], index_type=index_type, body=[ L.AssignAdd( derivs[cc][r], coefficients_for_dof[i_dof][cc][s] * aux[s]) for cc in range(n) ]) ] embedded_degree = dof_data["embedded_degree"] basisvalues = basisvalues_for_degree[embedded_degree] # Compute the derivatives of the basisfunctions on the reference (FIAT) element, # as the dot product of the new coefficients and basisvalues. case_code = [ L.Comment("Compute reference derivatives for dof %d." % i_dof), # Accumulate sum_s coefficients[s] * aux[s] L.ForRange(r, 0, num_derivatives, index_type=index_type, body=[aux_computation_code, compute_ref_derivs_code]) ] dof_cases.append((i_dof, case_code)) # Loop over all dofs, entering a different switch case in each iteration. # This is a legacy from the previous implementation where the loop # was in a separate function and all the setup above was also repeated # in a call for each dof. # TODO: Further refactoring is needed to improve on this situation, # but at least it's better than before. There's probably more code and # computations that can be shared between dofs, and this would probably # be easier to fix if mixed elements were handled separately! dof_loop_code = [ L.Comment("Loop over all dofs"), L.ForRange( idof, 0, num_dofs, index_type=index_type, body=[ L.ArrayDecl("double", derivatives, max_num_components * max_num_derivatives, 0.0), L.Switch(idof, dof_cases), L.ForRange( r, 0, num_derivatives, index_type=index_type, body=[ L.ForRange( c, 0, num_components[idof], index_type=index_type, body=[ L.Assign( ref_values[ip][idof][r][ reference_offset[idof] + c], derivs[c] [r]), # FIXME: validate ref_values dims ]), ]) ]), ] # Define loop over points final_loop_code = [ L.ForRange(ip, 0, num_points, index_type=index_type, body=basisvalues_code + dof_loop_code) ] # Stitch it all together code = (setup_code + dmats_code + tables_code + combinations_code + final_loop_code) return code
def transform_reference_basis_derivatives(self, L, ir, parameters): data = ir["evaluate_basis"] if isinstance(data, str): msg = "transform_reference_basis_derivatives: %s" % data return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Get some known dimensions #element_cellname = data["cellname"] gdim = data["geometric_dimension"] tdim = data["topological_dimension"] max_degree = data["max_degree"] reference_value_size = data["reference_value_size"] physical_value_size = data["physical_value_size"] num_dofs = len(data["dofs_data"]) max_g_d = gdim**max_degree max_t_d = tdim**max_degree # Output arguments values_symbol = L.Symbol("values") # Input arguments order = L.Symbol("order") num_points = L.Symbol("num_points") # FIXME: Currently assuming 1 point? reference_values = L.Symbol("reference_values") J = L.Symbol("J") detJ = L.Symbol("detJ") K = L.Symbol("K") # Internal variables transform = L.Symbol("transform") # Indices, I've tried to use these for a consistent purpose ip = L.Symbol("ip") # point i = L.Symbol("i") # physical component j = L.Symbol("j") # reference component k = L.Symbol("k") # order r = L.Symbol("r") # physical derivative number s = L.Symbol("s") # reference derivative number d = L.Symbol("d") # dof combinations_code = [] if max_degree == 0: # Don't need combinations num_derivatives_t = 1 # TODO: I think this is the right thing to do to make this still work for order=0? num_derivatives_g = 1 elif tdim == gdim: num_derivatives_t = L.Symbol("num_derivatives") num_derivatives_g = num_derivatives_t combinations_code += [ L.VariableDecl("const " + index_type, num_derivatives_t, L.Call("std::pow", (tdim, order))), ] # Add array declarations of combinations combinations_code_t, combinations_t = _generate_combinations(L, tdim, max_degree, order, num_derivatives_t) combinations_code += combinations_code_t combinations_g = combinations_t else: num_derivatives_t = L.Symbol("num_derivatives_t") num_derivatives_g = L.Symbol("num_derivatives_g") combinations_code += [ L.VariableDecl("const " + index_type, num_derivatives_t, L.Call("std::pow", (tdim, order))), L.VariableDecl("const " + index_type, num_derivatives_g, L.Call("std::pow", (gdim, order))), ] # Add array declarations of combinations combinations_code_t, combinations_t = _generate_combinations(L, tdim, max_degree, order, num_derivatives_t, suffix="_t") combinations_code_g, combinations_g = _generate_combinations(L, gdim, max_degree, order, num_derivatives_g, suffix="_g") combinations_code += combinations_code_t combinations_code += combinations_code_g # Define expected dimensions of argument arrays J = L.FlattenedArray(J, dims=(num_points, gdim, tdim)) detJ = L.FlattenedArray(detJ, dims=(num_points,)) K = L.FlattenedArray(K, dims=(num_points, tdim, gdim)) values = L.FlattenedArray(values_symbol, dims=(num_points, num_dofs, num_derivatives_g, physical_value_size)) reference_values = L.FlattenedArray(reference_values, dims=(num_points, num_dofs, num_derivatives_t, reference_value_size)) # Generate code to compute the derivative transform matrix transform_matrix_code = [ # Initialize transform matrix to all 1.0 L.ArrayDecl("double", transform, (max_g_d, max_t_d)), L.ForRanges( (r, 0, num_derivatives_g), (s, 0, num_derivatives_t), index_type=index_type, body=L.Assign(transform[r, s], 1.0) ), ] if max_degree > 0: transform_matrix_code += [ # Compute transform matrix entries, each a product of K entries L.ForRanges( (r, 0, num_derivatives_g), (s, 0, num_derivatives_t), (k, 0, order), index_type=index_type, body=L.AssignMul(transform[r, s], K[ip, combinations_t[s, k], combinations_g[r, k]]) ), ] # Initialize values to 0, will be added to inside loops values_init_code = [ L.MemZero(values_symbol, num_points * num_dofs * num_derivatives_g * physical_value_size), ] # Make offsets available in generated code reference_offsets = L.Symbol("reference_offsets") physical_offsets = L.Symbol("physical_offsets") dof_attributes_code = [ L.ArrayDecl("const " + index_type, reference_offsets, (num_dofs,), values=[dof_data["reference_offset"] for dof_data in data["dofs_data"]]), L.ArrayDecl("const " + index_type, physical_offsets, (num_dofs,), values=[dof_data["physical_offset"] for dof_data in data["dofs_data"]]), ] # Build dof lists for each mapping type mapping_dofs = defaultdict(list) for idof, dof_data in enumerate(data["dofs_data"]): mapping_dofs[dof_data["mapping"]].append(idof) # Generate code for each mapping type d = L.Symbol("d") transform_apply_code = [] for mapping in sorted(mapping_dofs): # Get list of dofs using this mapping idofs = mapping_dofs[mapping] # Select iteration approach over dofs if idofs == list(range(idofs[0], idofs[-1]+1)): # Contiguous dofrange = (d, idofs[0], idofs[-1]+1) idof = d else: # Stored const array of dof indices idofs_symbol = L.Symbol("%s_dofs" % mapping.replace(" ", "_")) dof_attributes_code += [ L.ArrayDecl("const " + index_type, idofs_symbol, (len(idofs),), values=idofs), ] dofrange = (d, 0, len(idofs)) idof = idofs_symbol[d] # NB! Array access to offsets, these are not Python integers reference_offset = reference_offsets[idof] physical_offset = physical_offsets[idof] # How many components does each basis function with this mapping have? # This should be uniform, i.e. there should be only one element in this set: num_reference_components, = set(data["dofs_data"][i]["num_components"] for i in idofs) M_scale, M_row, num_physical_components = generate_element_mapping( mapping, i, num_reference_components, tdim, gdim, J[ip], detJ[ip], K[ip] ) # transform_apply_body = [ # L.AssignAdd(values[ip, idof, r, physical_offset + k], # transform[r, s] * reference_values[ip, idof, s, reference_offset + k]) # for k in range(num_physical_components) # ] msg = "Using %s transform to map values back to the physical element." % mapping.replace("piola", "Piola") mapped_value = L.Symbol("mapped_value") transform_apply_code += [ L.ForRanges( dofrange, (s, 0, num_derivatives_t), (i, 0, num_physical_components), index_type=index_type, body=[ # Unrolled application of mapping to one physical component, # for affine this automatically reduces to # mapped_value = reference_values[..., reference_offset] L.Comment(msg), L.VariableDecl("const double", mapped_value, M_scale * sum(M_row[jj] * reference_values[ip, idof, s, reference_offset + jj] for jj in range(num_reference_components))), # Apply derivative transformation, for order=0 this reduces to # values[ip,idof,0,physical_offset+i] = transform[0,0]*mapped_value L.Comment("Mapping derivatives back to the physical element"), L.ForRanges( (r, 0, num_derivatives_g), index_type=index_type, body=[ L.AssignAdd(values[ip, idof, r, physical_offset + i], transform[r, s] * mapped_value) ]) ]) ] # Transform for each point point_loop_code = [ L.ForRange(ip, 0, num_points, index_type=index_type, body=( transform_matrix_code + transform_apply_code )) ] # Join code code = ( combinations_code + values_init_code + dof_attributes_code + point_loop_code ) return code
def generate_evaluate_reference_basis(L, data, parameters): """Generate code to evaluate element basisfunctions at an arbitrary point on the reference element. 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. This code is adapted from code in FFC which computed the basis from physical coordinates, and also to use UFLACS utilities. The FFC code has a comment "From FIAT_NEW.polynomial_set.tabulate()". """ # Cutoff for feature to disable generation of this code (consider # removing after benchmarking final result) if isinstance(data, str): msg = "evaluate_reference_basis: %s" % (data,) return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Get some known dimensions element_cellname = data["cellname"] tdim = data["topological_dimension"] reference_value_size = data["reference_value_size"] num_dofs = len(data["dofs_data"]) # Input geometry num_points = L.Symbol("num_points") X = L.Symbol("X") # Output values reference_values = L.Symbol("reference_values") ref_values = L.FlattenedArray(reference_values, dims=(num_points, num_dofs, reference_value_size)) # Loop indices ip = L.Symbol("ip") k = L.Symbol("k") c = L.Symbol("c") r = L.Symbol("r") # Generate code with static tables of expansion coefficients tables_code, coefficients_for_dof = generate_expansion_coefficients(L, data["dofs_data"]) # Reset reference_values[:] to 0 reset_values_code = [ L.ForRange(k, 0, num_points*num_dofs*reference_value_size, index_type=index_type, body=L.Assign(reference_values[k], 0.0)) ] setup_code = tables_code + reset_values_code # Generate code to compute tables of basisvalues basisvalues_code, basisvalues_for_degree, need_fiat_coordinates = \ generate_compute_basisvalues(L, data["dofs_data"], element_cellname, tdim, X, ip) # Accumulate products of basisvalues and coefficients into values accumulation_code = [ L.Comment("Accumulate products of coefficients and basisvalues"), ] for idof, dof_data in enumerate(data["dofs_data"]): embedded_degree = dof_data["embedded_degree"] num_components = dof_data["num_components"] num_members = dof_data["num_expansion_members"] # In ffc representation, this is extracted per dof # (but will coincide for some dofs of piola mapped elements): reference_offset = dof_data["reference_offset"] # Select the right basisvalues for this dof basisvalues = basisvalues_for_degree[embedded_degree] # Select the right coefficients for this dof coefficients = coefficients_for_dof[idof] # Generate basis accumulation loop if num_components > 1: # Could just simplify by using this generic code # and dropping the below two special cases accumulation_code += [ L.ForRange(c, 0, num_components, index_type=index_type, body=L.ForRange(r, 0, num_members, index_type=index_type, body=L.AssignAdd(ref_values[ip, idof, reference_offset + c], coefficients[c, r] * basisvalues[r]))) ] elif num_members > 1: accumulation_code += [ L.ForRange(r, 0, num_members, index_type=index_type, body=L.AssignAdd(ref_values[ip, idof, reference_offset], coefficients[0, r] * basisvalues[r])) ] else: accumulation_code += [ L.AssignAdd(ref_values[ip, idof, reference_offset], coefficients[0, 0] * basisvalues[0]) ] # TODO: Move this mapping to its own ufc function # e.g. finite_element::apply_element_mapping(reference_values, # J, K) # code += _generate_apply_mapping_to_computed_values(L, dof_data) # Only works for affine (no-op) # Stitch it all together code = [ setup_code, L.ForRange(ip, 0, num_points, index_type=index_type, body=basisvalues_code + accumulation_code) ] return code
def generate_evaluate_basis_derivatives(L, 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): msg = "evaluate_basis_derivatives: %s" % data return [generate_error(L, msg, True)] # 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"] physical_value_size = data["physical_value_size"] # Compute number of derivatives that has to be computed, and # declare an array to hold the values of the derivatives on the # reference element. values = L.Symbol("values") n = L.Symbol("n") dofs = L.Symbol("dofs") x = L.Symbol("x") coordinate_dofs = L.Symbol("coordinate_dofs") cell_orientation = L.Symbol("cell_orientation") if tdim == gdim: _t, _g = ("", "") num_derivatives_t = L.Symbol("num_derivatives") num_derivatives_g = L.Symbol("num_derivatives") code += [L.VariableDecl("std::size_t", num_derivatives_t, L.Call("std::pow", (tdim, n)))] else: _t, _g = ("_t", "_g") num_derivatives_t = L.Symbol("num_derivatives_t") num_derivatives_g = L.Symbol("num_derivatives_g") if max_degree > 0: code += [L.VariableDecl("std::size_t", num_derivatives_t, L.Call("std::pow", (tdim, n)))] code += [L.VariableDecl("std::size_t", num_derivatives_g, L.Call("std::pow", (gdim, n)))] # Reset all values. code += [L.MemZero(values, physical_value_size*num_derivatives_g)] # Handle values of argument 'n'. code += [L.Comment("Call evaluate_basis_all if order of derivatives is equal to zero.")] code += [L.If(L.EQ(n, 0), [L.Call("evaluate_basis", (L.Symbol("i"), values, x, coordinate_dofs, cell_orientation)), L.Return()])] # 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 code code += [L.Comment("If order of derivatives is greater than the maximum polynomial degree, return zeros.")] code += [L.If(L.GT(n, max_degree), [L.Return()])] # Generate geo code. code += jacobian(L, gdim, tdim, element_cellname) code += inverse_jacobian(L, gdim, tdim, element_cellname) if data["needs_oriented"] and tdim != gdim: code += orientation(L) code += fiat_coordinate_mapping(L, element_cellname, gdim) # Generate all possible combinations of derivatives. combinations_code_t, combinations_t = _generate_combinations(L, tdim, max_degree, n, num_derivatives_t, _t) code += combinations_code_t if tdim != gdim: combinations_code_g, combinations_g = _generate_combinations(L, gdim, max_degree, n, num_derivatives_g, "_g") code += combinations_code_g # Generate the transformation matrix. code += _generate_transform(L, element_cellname, gdim, tdim, max_degree) # Create code for all basis values (dofs). dof_cases = [] for i, dof_data in enumerate(data["dofs_data"]): dof_cases.append((i, _generate_dof_code(L, data, dof_data))) code += [L.Switch(L.Symbol("i"), dof_cases)] return code
def tabulate_dof_coordinates(self, L, ir, parameters): ir = ir["tabulate_dof_coordinates"] # Raise error if tabulate_dof_coordinates is ill-defined if not ir: msg = "tabulate_dof_coordinates is not defined for this element" return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Extract coordinates and cell dimension gdim = ir["gdim"] tdim = ir["tdim"] points = ir["points"] # Extract cellshape cell_shape = ir["cell_shape"] # Output argument dof_coordinates = L.FlattenedArray(L.Symbol("dof_coordinates"), dims=(len(points), gdim)) # Input argument coordinate_dofs = L.Symbol("coordinate_dofs") # Loop indices i = L.Symbol("i") k = L.Symbol("k") ip = L.Symbol("ip") # Basis symbol phi = L.Symbol("phi") # TODO: Get rid of all places that use reference_to_physical_map, it is restricted to a basis of degree 1 # Create code for evaluating coordinate mapping num_scalar_xdofs = _num_vertices(cell_shape) cg1_basis = reference_to_physical_map(cell_shape) phi_values = numpy.asarray([phi_comp for X in points for phi_comp in cg1_basis(X)]) assert len(phi_values) == len(points) * num_scalar_xdofs # TODO: Use precision parameter here phi_values = clamp_table_small_numbers(phi_values) code = [ L.Assign( dof_coordinates[ip][i], sum(phi_values[ip*num_scalar_xdofs + k] * coordinate_dofs[gdim*k + i] for k in range(num_scalar_xdofs)) ) for ip in range(len(points)) for i in range(gdim) ] # FIXME: This code assumes an affine coordinate field. # To get around that limitation, make this function take another argument # const ufc::coordinate_mapping * cm # and generate code like this: """ index_type X[tdim*num_dofs]; tabulate_dof_coordinates(X); cm->compute_physical_coordinates(x, X, coordinate_dofs); """ return code
def interpolate_vertex_values(self, L, ir, parameters): irdata = ir["interpolate_vertex_values"] # Raise error if interpolate_vertex_values is ill-defined if not irdata: msg = "interpolate_vertex_values is not defined for this element" return [generate_error(L, msg, parameters["convert_exceptions_to_warnings"])] # Handle unsupported elements. if isinstance(irdata, str): msg = "interpolate_vertex_values: %s" % irdata return [generate_error(L, msg, parameters["convert_exceptions_to_warnings"])] # Add code for Jacobian if necessary code = [] gdim = irdata["geometric_dimension"] tdim = irdata["topological_dimension"] cell_shape = ir["cell_shape"] if irdata["needs_jacobian"]: code += jacobian(L, gdim, tdim, cell_shape) code += inverse_jacobian(L, gdim, tdim, cell_shape) if irdata["needs_oriented"] and tdim != gdim: code += orientation(L) # Compute total value dimension for (mixed) element total_dim = irdata["physical_value_size"] # Generate code for each element value_offset = 0 space_offset = 0 for data in irdata["element_data"]: # Add vertex interpolation for this element code += [L.Comment("Evaluate function and change variables")] # Extract vertex values for all basis functions vertex_values = data["basis_values"] value_size = data["physical_value_size"] space_dim = data["space_dim"] mapping = data["mapping"] J = L.Symbol("J") J = L.FlattenedArray(J, dims=(gdim, tdim)) detJ = L.Symbol("detJ") K = L.Symbol("K") K = L.FlattenedArray(K, dims=(tdim, gdim)) # Create code for each value dimension: for k in range(value_size): # Create code for each vertex x_j for (j, values_at_vertex) in enumerate(vertex_values): if value_size == 1: values_at_vertex = [values_at_vertex] values = clamp_table_small_numbers(values_at_vertex) # Map basis functions using appropriate mapping # FIXME: sort out all non-affine mappings and make into a function # components = change_of_variables(values_at_vertex, k) w = [] if mapping == 'affine': w = values[k] elif mapping == 'contravariant piola': for index in range(space_dim): w += [sum(J[k, p]*values[p][index] for p in range(tdim))/detJ] elif mapping == 'covariant piola': for index in range(space_dim): w += [sum(K[p, k]*values[p][index] for p in range(tdim))] elif mapping == 'double covariant piola': for index in range(space_dim): w += [sum(K[p, k//tdim]*values[p][q][index]*K[q, k % tdim] for q in range(tdim) for p in range(tdim))] elif mapping == 'double contravariant piola': for index in range(space_dim): w += [sum(J[k//tdim, p]*values[p][q][index]*J[k % tdim, q] for q in range(tdim) for p in range(tdim))/(detJ*detJ)] else: error("Unknown mapping: %s" % mapping) # Contract coefficients and basis functions dof_values = L.Symbol("dof_values") dof_list = [dof_values[i + space_offset] for i in range(space_dim)] value = sum(p*q for (p, q) in zip(dof_list, w)) # Assign value to correct vertex index = j * total_dim + (k + value_offset) v_values = L.Symbol("vertex_values") code += [L.Assign(v_values[index], value)] # Update offsets for value- and space dimension value_offset += data["physical_value_size"] space_offset += data["space_dim"] return code
def generate_evaluate_basis_derivatives_all(L, data): """Like evaluate_basis, but return the values of all basis functions (dofs).""" if isinstance(data, str): msg = "evaluate_basis_derivatives_all: %s" % data return [generate_error(L, msg, True)] # Initialise return code code = [] # FIXME: KBO: Figure out which return format to use, either: # [dN0[0]/dx, dN0[0]/dy, dN0[1]/dx, dN0[1]/dy, dN1[0]/dx, # dN1[0]/dy, dN1[1]/dx, dN1[1]/dy, ...] # or # [dN0[0]/dx, dN1[0]/dx, ..., dN0[1]/dx, dN1[1]/dx, ..., # dN0[0]/dy, dN1[0]/dy, ..., dN0[1]/dy, dN1[1]/dy, ...] # or # [dN0[0]/dx, dN0[1]/dx, ..., dN1[0]/dx, dN1[1]/dx, ..., # dN0[0]/dy, dN0[1]/dy, ..., dN1[0]/dy, dN1[1]/dy, ...] # for vector (tensor elements), currently returning option 1. # FIXME: KBO: For now, just call evaluate_basis_derivatives and # map values accordingly, this will keep the amount of code at a # minimum. If it turns out that speed is an issue (overhead from # calling evaluate_basis), we can easily generate all the code. # Get total value shape and space dimension for entire element # (possibly mixed). physical_value_size = data["physical_value_size"] space_dimension = data["space_dimension"] max_degree = data["max_degree"] gdim = data["geometric_dimension"] tdim = data["topological_dimension"] n = L.Symbol("n") x = L.Symbol("x") coordinate_dofs = L.Symbol("coordinate_dofs") cell_orientation = L.Symbol("cell_orientation") values = L.Symbol("values") # Special case where space dimension is one (constant elements). if space_dimension == 1: code += [L.Comment("Element is constant, calling evaluate_basis_derivatives.")] code += [L.Call("evaluate_basis_derivatives", (0, n, values, x, coordinate_dofs, cell_orientation))] return code # Compute number of derivatives. if tdim == gdim: num_derivatives = L.Symbol("num_derivatives") else: num_derivatives = L.Symbol("num_derivatives_g") # If n == 0, call evaluate_basis. code += [L.Comment("Call evaluate_basis_all if order of derivatives is equal to zero.")] code += [L.If(L.EQ(n, 0), [L.Call("evaluate_basis_all", (values, x, coordinate_dofs, cell_orientation)), L.Return()])] code += [L.VariableDecl("unsigned int", num_derivatives, L.Call("std::pow", (gdim, n)))] num_vals = physical_value_size * num_derivatives # Reset values. code += [L.Comment("Set values equal to zero.")] code += [L.MemZero(values, num_vals*space_dimension)] # If n > max_degree, return zeros. code += [L.Comment("If order of derivatives is greater than the maximum polynomial degree, return zeros.")] code += [L.If(L.GT(n, max_degree), [L.Return()])] # Declare helper value to hold single dof values and reset. code += [L.Comment("Helper variable to hold values of a single dof.")] nds = gdim**max_degree * physical_value_size dof_values = L.Symbol("dof_values") code += [L.ArrayDecl("double", dof_values, (nds,), 0.0)] # Create loop over dofs that calls evaluate_basis_derivatives for # a single dof and inserts the values into the global array. code += [L.Comment("Loop dofs and call evaluate_basis_derivatives.")] values = L.FlattenedArray(values, dims=(space_dimension, num_vals)) r = L.Symbol("r") s = L.Symbol("s") loop_s = L.ForRange(s, 0, num_vals, index_type=index_type, body=[L.Assign(values[r, s], dof_values[s])]) code += [L.ForRange(r, 0, space_dimension, index_type=index_type, body=[L.Call("evaluate_basis_derivatives", (r, n, dof_values, x, coordinate_dofs, cell_orientation)), loop_s])] return code
def evaluate_basis(self, L, ir, parameters): data = ir["evaluate_basis"] # FIXME: does this make sense? if not data: msg = "evaluate_basis is not defined for this element" return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Handle unsupported elements. if isinstance(data, str): msg = "evaluate_basis: %s" % data return [generate_error(L, msg, parameters["convert_exceptions_to_warnings"])] # Get the element cell name and geometric dimension. element_cellname = data["cellname"] gdim = data["geometric_dimension"] tdim = data["topological_dimension"] # 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. # Get code snippets for Jacobian, Inverse of Jacobian and # mapping of coordinates from physical element to the FIAT # reference element. cm = L.Symbol("cm") X = L.Symbol("X") code = [L.ArrayDecl("double", X, (tdim), values=0)] J = L.Symbol("J") code += [L.ArrayDecl("double", J, (gdim*tdim,))] detJ = L.Symbol("detJ") code += [L.VariableDecl("double", detJ)] K = L.Symbol("K") code += [L.ArrayDecl("double", K, (gdim*tdim,))] x = L.Symbol("x") coordinate_dofs = L.Symbol("coordinate_dofs") cell_orientation = L.Symbol("cell_orientation") no_cm_code = [ L.Call("compute_jacobian_"+element_cellname+"_"+str(gdim)+"d", (J, coordinate_dofs)), L.Call("compute_jacobian_inverse_"+element_cellname+"_"+str(gdim)+"d", (K, detJ, J))] if data["needs_oriented"] and tdim != gdim: no_cm_code += orientation(L) if any((d["embedded_degree"] > 0) for d in data["dofs_data"]): k = L.Symbol("k") Y = L.Symbol("Y") no_cm_code += fiat_coordinate_mapping(L, element_cellname, gdim) if element_cellname in ('interval', 'triangle', 'tetrahedron'): no_cm_code += [L.Comment("Map to FFC reference coordinate"), L.ForRange(k, 0, tdim, index_type=index_type, body=[L.Assign(X[k], (Y[k] + 1.0)/2.0)])] else: no_cm_code += [L.ForRange(k, 0, tdim, index_type=index_type, body=[L.Assign(X[k], Y[k])])] code += [L.If(cm, L.Call("cm->compute_reference_geometry", (X, J, L.AddressOf(detJ), K, 1, x, coordinate_dofs, cell_orientation))), L.Else(no_cm_code)] reference_value_size = data["reference_value_size"] num_dofs = len(data["dofs_data"]) ref_values = L.Symbol("ref_values") code += [L.Comment("Evaluate basis on reference element"), L.ArrayDecl("double", ref_values, num_dofs*reference_value_size), L.Call("evaluate_reference_basis",(ref_values, 1, X))] physical_value_size = data["physical_value_size"] physical_values = L.Symbol("physical_values") i = L.Symbol("i") k = L.Symbol("k") values = L.Symbol("values") code += [L.Comment("Push forward"), L.ArrayDecl("double", physical_values, num_dofs*physical_value_size), L.Call("transform_reference_basis_derivatives",(physical_values, 0, 1, ref_values, X, J, L.AddressOf(detJ), K, cell_orientation)), L.ForRange(k, 0, physical_value_size, index_type=index_type, body=[ L.Assign(values[k], physical_values[physical_value_size*i + k]) ])] return code
def transform_reference_basis_derivatives(self, L, ir, parameters): data = ir["evaluate_basis"] if isinstance(data, str): msg = "transform_reference_basis_derivatives: %s" % data return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Get some known dimensions #element_cellname = data["cellname"] gdim = data["geometric_dimension"] tdim = data["topological_dimension"] max_degree = data["max_degree"] reference_value_size = data["reference_value_size"] physical_value_size = data["physical_value_size"] num_dofs = len(data["dofs_data"]) max_g_d = gdim**max_degree max_t_d = tdim**max_degree # Output arguments values_symbol = L.Symbol("values") # Input arguments order = L.Symbol("order") num_points = L.Symbol( "num_points") # FIXME: Currently assuming 1 point? reference_values = L.Symbol("reference_values") J = L.Symbol("J") detJ = L.Symbol("detJ") K = L.Symbol("K") # Internal variables transform = L.Symbol("transform") # Indices, I've tried to use these for a consistent purpose ip = L.Symbol("ip") # point i = L.Symbol("i") # physical component j = L.Symbol("j") # reference component k = L.Symbol("k") # order r = L.Symbol("r") # physical derivative number s = L.Symbol("s") # reference derivative number d = L.Symbol("d") # dof combinations_code = [] if max_degree == 0: # Don't need combinations num_derivatives_t = 1 # TODO: I think this is the right thing to do to make this still work for order=0? num_derivatives_g = 1 elif tdim == gdim: num_derivatives_t = L.Symbol("num_derivatives") num_derivatives_g = num_derivatives_t combinations_code += [ L.VariableDecl("const " + index_type, num_derivatives_t, L.Call("std::pow", (tdim, order))), ] # Add array declarations of combinations combinations_code_t, combinations_t = _generate_combinations( L, tdim, max_degree, order, num_derivatives_t) combinations_code += combinations_code_t combinations_g = combinations_t else: num_derivatives_t = L.Symbol("num_derivatives_t") num_derivatives_g = L.Symbol("num_derivatives_g") combinations_code += [ L.VariableDecl("const " + index_type, num_derivatives_t, L.Call("std::pow", (tdim, order))), L.VariableDecl("const " + index_type, num_derivatives_g, L.Call("std::pow", (gdim, order))), ] # Add array declarations of combinations combinations_code_t, combinations_t = _generate_combinations( L, tdim, max_degree, order, num_derivatives_t, suffix="_t") combinations_code_g, combinations_g = _generate_combinations( L, gdim, max_degree, order, num_derivatives_g, suffix="_g") combinations_code += combinations_code_t combinations_code += combinations_code_g # Define expected dimensions of argument arrays J = L.FlattenedArray(J, dims=(num_points, gdim, tdim)) detJ = L.FlattenedArray(detJ, dims=(num_points, )) K = L.FlattenedArray(K, dims=(num_points, tdim, gdim)) values = L.FlattenedArray(values_symbol, dims=(num_points, num_dofs, num_derivatives_g, physical_value_size)) reference_values = L.FlattenedArray(reference_values, dims=(num_points, num_dofs, num_derivatives_t, reference_value_size)) # Generate code to compute the derivative transform matrix transform_matrix_code = [ # Initialize transform matrix to all 1.0 L.ArrayDecl("double", transform, (max_g_d, max_t_d)), L.ForRanges((r, 0, num_derivatives_g), (s, 0, num_derivatives_t), index_type=index_type, body=L.Assign(transform[r, s], 1.0)), ] if max_degree > 0: transform_matrix_code += [ # Compute transform matrix entries, each a product of K entries L.ForRanges((r, 0, num_derivatives_g), (s, 0, num_derivatives_t), (k, 0, order), index_type=index_type, body=L.AssignMul( transform[r, s], K[ip, combinations_t[s, k], combinations_g[r, k]])), ] # Initialize values to 0, will be added to inside loops values_init_code = [ L.MemZero( values_symbol, num_points * num_dofs * num_derivatives_g * physical_value_size), ] # Make offsets available in generated code reference_offsets = L.Symbol("reference_offsets") physical_offsets = L.Symbol("physical_offsets") dof_attributes_code = [ L.ArrayDecl("const " + index_type, reference_offsets, (num_dofs, ), values=[ dof_data["reference_offset"] for dof_data in data["dofs_data"] ]), L.ArrayDecl("const " + index_type, physical_offsets, (num_dofs, ), values=[ dof_data["physical_offset"] for dof_data in data["dofs_data"] ]), ] # Build dof lists for each mapping type mapping_dofs = defaultdict(list) for idof, dof_data in enumerate(data["dofs_data"]): mapping_dofs[dof_data["mapping"]].append(idof) # Generate code for each mapping type d = L.Symbol("d") transform_apply_code = [] for mapping in sorted(mapping_dofs): # Get list of dofs using this mapping idofs = mapping_dofs[mapping] # Select iteration approach over dofs if idofs == list(range(idofs[0], idofs[-1] + 1)): # Contiguous dofrange = (d, idofs[0], idofs[-1] + 1) idof = d else: # Stored const array of dof indices idofs_symbol = L.Symbol("%s_dofs" % mapping.replace(" ", "_")) dof_attributes_code += [ L.ArrayDecl("const " + index_type, idofs_symbol, (len(idofs), ), values=idofs), ] dofrange = (d, 0, len(idofs)) idof = idofs_symbol[d] # NB! Array access to offsets, these are not Python integers reference_offset = reference_offsets[idof] physical_offset = physical_offsets[idof] # How many components does each basis function with this mapping have? # This should be uniform, i.e. there should be only one element in this set: num_reference_components, = set( data["dofs_data"][i]["num_components"] for i in idofs) M_scale, M_row, num_physical_components = generate_element_mapping( mapping, i, num_reference_components, tdim, gdim, J[ip], detJ[ip], K[ip]) # transform_apply_body = [ # L.AssignAdd(values[ip, idof, r, physical_offset + k], # transform[r, s] * reference_values[ip, idof, s, reference_offset + k]) # for k in range(num_physical_components) # ] msg = "Using %s transform to map values back to the physical element." % mapping.replace( "piola", "Piola") mapped_value = L.Symbol("mapped_value") transform_apply_code += [ L.ForRanges( dofrange, (s, 0, num_derivatives_t), (i, 0, num_physical_components), index_type=index_type, body=[ # Unrolled application of mapping to one physical component, # for affine this automatically reduces to # mapped_value = reference_values[..., reference_offset] L.Comment(msg), L.VariableDecl( "const double", mapped_value, M_scale * sum(M_row[jj] * reference_values[ip, idof, s, reference_offset + jj] for jj in range(num_reference_components))), # Apply derivative transformation, for order=0 this reduces to # values[ip,idof,0,physical_offset+i] = transform[0,0]*mapped_value L.Comment( "Mapping derivatives back to the physical element" ), L.ForRanges((r, 0, num_derivatives_g), index_type=index_type, body=[ L.AssignAdd( values[ip, idof, r, physical_offset + i], transform[r, s] * mapped_value) ]) ]) ] # Transform for each point point_loop_code = [ L.ForRange(ip, 0, num_points, index_type=index_type, body=(transform_matrix_code + transform_apply_code)) ] # Join code code = (combinations_code + values_init_code + dof_attributes_code + point_loop_code) return code
def generate_evaluate_basis_derivatives(L, 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): msg = "evaluate_basis_derivatives: %s" % data return [generate_error(L, msg, True)] # 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"] physical_value_size = data["physical_value_size"] # Compute number of derivatives that has to be computed, and # declare an array to hold the values of the derivatives on the # reference element. values = L.Symbol("values") n = L.Symbol("n") dofs = L.Symbol("dofs") x = L.Symbol("x") coordinate_dofs = L.Symbol("coordinate_dofs") cell_orientation = L.Symbol("cell_orientation") if tdim == gdim: _t, _g = ("", "") num_derivatives_t = L.Symbol("num_derivatives") num_derivatives_g = L.Symbol("num_derivatives") code += [ L.VariableDecl("std::size_t", num_derivatives_t, L.Call("std::pow", (tdim, n))) ] else: _t, _g = ("_t", "_g") num_derivatives_t = L.Symbol("num_derivatives_t") num_derivatives_g = L.Symbol("num_derivatives_g") if max_degree > 0: code += [ L.VariableDecl("std::size_t", num_derivatives_t, L.Call("std::pow", (tdim, n))) ] code += [ L.VariableDecl("std::size_t", num_derivatives_g, L.Call("std::pow", (gdim, n))) ] # Reset all values. code += [L.MemZero(values, physical_value_size * num_derivatives_g)] # Handle values of argument 'n'. code += [ L.Comment( "Call evaluate_basis_all if order of derivatives is equal to zero." ) ] code += [ L.If(L.EQ(n, 0), [ L.Call( "evaluate_basis", (L.Symbol("i"), values, x, coordinate_dofs, cell_orientation)), L.Return() ]) ] # 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 code code += [ L.Comment( "If order of derivatives is greater than the maximum polynomial degree, return zeros." ) ] code += [L.If(L.GT(n, max_degree), [L.Return()])] # Generate geo code. code += jacobian(L, gdim, tdim, element_cellname) code += inverse_jacobian(L, gdim, tdim, element_cellname) if data["needs_oriented"] and tdim != gdim: code += orientation(L) code += fiat_coordinate_mapping(L, element_cellname, gdim) # Generate all possible combinations of derivatives. combinations_code_t, combinations_t = _generate_combinations( L, tdim, max_degree, n, num_derivatives_t, _t) code += combinations_code_t if tdim != gdim: combinations_code_g, combinations_g = _generate_combinations( L, gdim, max_degree, n, num_derivatives_g, "_g") code += combinations_code_g # Generate the transformation matrix. code += _generate_transform(L, element_cellname, gdim, tdim, max_degree) # Create code for all basis values (dofs). dof_cases = [] for i, dof_data in enumerate(data["dofs_data"]): dof_cases.append((i, _generate_dof_code(L, data, dof_data))) code += [L.Switch(L.Symbol("i"), dof_cases)] return code
def tabulate_dof_coordinates(self, L, ir, parameters): ir = ir["tabulate_dof_coordinates"] # Raise error if tabulate_dof_coordinates is ill-defined if not ir: msg = "tabulate_dof_coordinates is not defined for this element" return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Extract coordinates and cell dimension gdim = ir["gdim"] tdim = ir["tdim"] points = ir["points"] # Extract cellshape cell_shape = ir["cell_shape"] # Output argument dof_coordinates = L.FlattenedArray(L.Symbol("dof_coordinates"), dims=(len(points), gdim)) # Input argument coordinate_dofs = L.Symbol("coordinate_dofs") # Loop indices i = L.Symbol("i") k = L.Symbol("k") ip = L.Symbol("ip") # Basis symbol phi = L.Symbol("phi") # TODO: Get rid of all places that use reference_to_physical_map, it is restricted to a basis of degree 1 # Create code for evaluating coordinate mapping num_scalar_xdofs = _num_vertices(cell_shape) cg1_basis = reference_to_physical_map(cell_shape) phi_values = numpy.asarray( [phi_comp for X in points for phi_comp in cg1_basis(X)]) assert len(phi_values) == len(points) * num_scalar_xdofs # TODO: Use precision parameter here phi_values = clamp_table_small_numbers(phi_values) code = [ L.Assign( dof_coordinates[ip][i], sum(phi_values[ip * num_scalar_xdofs + k] * coordinate_dofs[gdim * k + i] for k in range(num_scalar_xdofs))) for ip in range(len(points)) for i in range(gdim) ] # FIXME: This code assumes an affine coordinate field. # To get around that limitation, make this function take another argument # const ufc::coordinate_mapping * cm # and generate code like this: """ index_type X[tdim*num_dofs]; tabulate_dof_coordinates(X); cm->compute_physical_coordinates(x, X, coordinate_dofs); """ return code
def interpolate_vertex_values(self, L, ir, parameters): irdata = ir["interpolate_vertex_values"] # Raise error if interpolate_vertex_values is ill-defined if not irdata: msg = "interpolate_vertex_values is not defined for this element" return [ generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) ] # Handle unsupported elements. if isinstance(irdata, str): msg = "interpolate_vertex_values: %s" % irdata return [ generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) ] # Add code for Jacobian if necessary code = [] gdim = irdata["geometric_dimension"] tdim = irdata["topological_dimension"] cell_shape = ir["cell_shape"] if irdata["needs_jacobian"]: code += jacobian(L, gdim, tdim, cell_shape) code += inverse_jacobian(L, gdim, tdim, cell_shape) if irdata["needs_oriented"] and tdim != gdim: code += orientation(L) # Compute total value dimension for (mixed) element total_dim = irdata["physical_value_size"] # Generate code for each element value_offset = 0 space_offset = 0 for data in irdata["element_data"]: # Add vertex interpolation for this element code += [L.Comment("Evaluate function and change variables")] # Extract vertex values for all basis functions vertex_values = data["basis_values"] value_size = data["physical_value_size"] space_dim = data["space_dim"] mapping = data["mapping"] J = L.Symbol("J") J = L.FlattenedArray(J, dims=(gdim, tdim)) detJ = L.Symbol("detJ") K = L.Symbol("K") K = L.FlattenedArray(K, dims=(tdim, gdim)) # Create code for each value dimension: for k in range(value_size): # Create code for each vertex x_j for (j, values_at_vertex) in enumerate(vertex_values): if value_size == 1: values_at_vertex = [values_at_vertex] values = clamp_table_small_numbers(values_at_vertex) # Map basis functions using appropriate mapping # FIXME: sort out all non-affine mappings and make into a function # components = change_of_variables(values_at_vertex, k) w = [] if mapping == 'affine': w = values[k] elif mapping == 'contravariant piola': for index in range(space_dim): w += [ sum(J[k, p] * values[p][index] for p in range(tdim)) / detJ ] elif mapping == 'covariant piola': for index in range(space_dim): w += [ sum(K[p, k] * values[p][index] for p in range(tdim)) ] elif mapping == 'double covariant piola': for index in range(space_dim): w += [ sum(K[p, k // tdim] * values[p][q][index] * K[q, k % tdim] for q in range(tdim) for p in range(tdim)) ] elif mapping == 'double contravariant piola': for index in range(space_dim): w += [ sum(J[k // tdim, p] * values[p][q][index] * J[k % tdim, q] for q in range(tdim) for p in range(tdim)) / (detJ * detJ) ] else: error("Unknown mapping: %s" % mapping) # Contract coefficients and basis functions dof_values = L.Symbol("dof_values") dof_list = [ dof_values[i + space_offset] for i in range(space_dim) ] value = sum(p * q for (p, q) in zip(dof_list, w)) # Assign value to correct vertex index = j * total_dim + (k + value_offset) v_values = L.Symbol("vertex_values") code += [L.Assign(v_values[index], value)] # Update offsets for value- and space dimension value_offset += data["physical_value_size"] space_offset += data["space_dim"] return code
def evaluate_basis_all(self, L, ir, parameters): data = ir["evaluate_basis"] # Handle unsupported elements. if isinstance(data, str): msg = "evaluate_basis_all: %s" % data return [ generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) ] physical_value_size = data["physical_value_size"] space_dimension = data["space_dimension"] x = L.Symbol("x") coordinate_dofs = L.Symbol("coordinate_dofs") cell_orientation = L.Symbol("cell_orientation") values = L.Symbol("values") # Special case where space dimension is one (constant elements). if space_dimension == 1: code = [ L.Comment("Element is constant, calling evaluate_basis."), L.Call("evaluate_basis", (0, values, x, coordinate_dofs, cell_orientation)) ] return code r = L.Symbol("r") dof_values = L.Symbol("dof_values") if physical_value_size == 1: code = [ L.Comment("Helper variable to hold value of a single dof."), L.VariableDecl("double", dof_values, 0.0), L.Comment("Loop dofs and call evaluate_basis"), L.ForRange(r, 0, space_dimension, index_type=index_type, body=[ L.Call("evaluate_basis", (r, L.AddressOf(dof_values), x, coordinate_dofs, cell_orientation)), L.Assign(values[r], dof_values) ]) ] else: s = L.Symbol("s") code = [ L.Comment("Helper variable to hold values of a single dof."), L.ArrayDecl("double", dof_values, physical_value_size, 0.0), L.Comment("Loop dofs and call evaluate_basis"), L.ForRange(r, 0, space_dimension, index_type=index_type, body=[ L.Call("evaluate_basis", (r, dof_values, x, coordinate_dofs, cell_orientation)), L.ForRange( s, 0, physical_value_size, index_type=index_type, body=[ L.Assign( values[r * physical_value_size + s], dof_values[s]) ]) ]) ] return code
def evaluate_basis(self, L, ir, parameters): data = ir["evaluate_basis"] # FIXME: does this make sense? if not data: msg = "evaluate_basis is not defined for this element" return generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) # Handle unsupported elements. if isinstance(data, str): msg = "evaluate_basis: %s" % data return [ generate_error(L, msg, parameters["convert_exceptions_to_warnings"]) ] # Get the element cell name and geometric dimension. element_cellname = data["cellname"] gdim = data["geometric_dimension"] tdim = data["topological_dimension"] # 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. # Get code snippets for Jacobian, Inverse of Jacobian and # mapping of coordinates from physical element to the FIAT # reference element. cm = L.Symbol("cm") X = L.Symbol("X") code = [L.ArrayDecl("double", X, (tdim), values=0)] J = L.Symbol("J") code += [L.ArrayDecl("double", J, (gdim * tdim, ))] detJ = L.Symbol("detJ") code += [L.VariableDecl("double", detJ)] K = L.Symbol("K") code += [L.ArrayDecl("double", K, (gdim * tdim, ))] x = L.Symbol("x") coordinate_dofs = L.Symbol("coordinate_dofs") cell_orientation = L.Symbol("cell_orientation") no_cm_code = [ L.Call( "compute_jacobian_" + element_cellname + "_" + str(gdim) + "d", (J, coordinate_dofs)), L.Call( "compute_jacobian_inverse_" + element_cellname + "_" + str(gdim) + "d", (K, detJ, J)) ] if data["needs_oriented"] and tdim != gdim: no_cm_code += orientation(L) if any((d["embedded_degree"] > 0) for d in data["dofs_data"]): k = L.Symbol("k") Y = L.Symbol("Y") no_cm_code += fiat_coordinate_mapping(L, element_cellname, gdim) if element_cellname in ('interval', 'triangle', 'tetrahedron'): no_cm_code += [ L.Comment("Map to FFC reference coordinate"), L.ForRange(k, 0, tdim, index_type=index_type, body=[L.Assign(X[k], (Y[k] + 1.0) / 2.0)]) ] else: no_cm_code += [ L.ForRange(k, 0, tdim, index_type=index_type, body=[L.Assign(X[k], Y[k])]) ] code += [ L.If( cm, L.Call("cm->compute_reference_geometry", (X, J, L.AddressOf(detJ), K, 1, x, coordinate_dofs, cell_orientation))), L.Else(no_cm_code) ] reference_value_size = data["reference_value_size"] num_dofs = len(data["dofs_data"]) ref_values = L.Symbol("ref_values") code += [ L.Comment("Evaluate basis on reference element"), L.ArrayDecl("double", ref_values, num_dofs * reference_value_size), L.Call("evaluate_reference_basis", (ref_values, 1, X)) ] physical_value_size = data["physical_value_size"] physical_values = L.Symbol("physical_values") i = L.Symbol("i") k = L.Symbol("k") values = L.Symbol("values") code += [ L.Comment("Push forward"), L.ArrayDecl("double", physical_values, num_dofs * physical_value_size), L.Call("transform_reference_basis_derivatives", (physical_values, 0, 1, ref_values, X, J, L.AddressOf(detJ), K, cell_orientation)), L.ForRange(k, 0, physical_value_size, index_type=index_type, body=[ L.Assign( values[k], physical_values[physical_value_size * i + k]) ]) ] return code
def generate_evaluate_basis_derivatives_all(L, data): """Like evaluate_basis, but return the values of all basis functions (dofs).""" if isinstance(data, str): msg = "evaluate_basis_derivatives_all: %s" % data return [generate_error(L, msg, True)] # Initialise return code code = [] # FIXME: KBO: Figure out which return format to use, either: # [dN0[0]/dx, dN0[0]/dy, dN0[1]/dx, dN0[1]/dy, dN1[0]/dx, # dN1[0]/dy, dN1[1]/dx, dN1[1]/dy, ...] # or # [dN0[0]/dx, dN1[0]/dx, ..., dN0[1]/dx, dN1[1]/dx, ..., # dN0[0]/dy, dN1[0]/dy, ..., dN0[1]/dy, dN1[1]/dy, ...] # or # [dN0[0]/dx, dN0[1]/dx, ..., dN1[0]/dx, dN1[1]/dx, ..., # dN0[0]/dy, dN0[1]/dy, ..., dN1[0]/dy, dN1[1]/dy, ...] # for vector (tensor elements), currently returning option 1. # FIXME: KBO: For now, just call evaluate_basis_derivatives and # map values accordingly, this will keep the amount of code at a # minimum. If it turns out that speed is an issue (overhead from # calling evaluate_basis), we can easily generate all the code. # Get total value shape and space dimension for entire element # (possibly mixed). physical_value_size = data["physical_value_size"] space_dimension = data["space_dimension"] max_degree = data["max_degree"] gdim = data["geometric_dimension"] tdim = data["topological_dimension"] n = L.Symbol("n") x = L.Symbol("x") coordinate_dofs = L.Symbol("coordinate_dofs") cell_orientation = L.Symbol("cell_orientation") values = L.Symbol("values") # Special case where space dimension is one (constant elements). if space_dimension == 1: code += [ L.Comment( "Element is constant, calling evaluate_basis_derivatives.") ] code += [ L.Call("evaluate_basis_derivatives", (0, n, values, x, coordinate_dofs, cell_orientation)) ] return code # Compute number of derivatives. if tdim == gdim: num_derivatives = L.Symbol("num_derivatives") else: num_derivatives = L.Symbol("num_derivatives_g") # If n == 0, call evaluate_basis. code += [ L.Comment( "Call evaluate_basis_all if order of derivatives is equal to zero." ) ] code += [ L.If(L.EQ(n, 0), [ L.Call("evaluate_basis_all", (values, x, coordinate_dofs, cell_orientation)), L.Return() ]) ] code += [ L.VariableDecl("unsigned int", num_derivatives, L.Call("std::pow", (gdim, n))) ] num_vals = physical_value_size * num_derivatives # Reset values. code += [L.Comment("Set values equal to zero.")] code += [L.MemZero(values, num_vals * space_dimension)] # If n > max_degree, return zeros. code += [ L.Comment( "If order of derivatives is greater than the maximum polynomial degree, return zeros." ) ] code += [L.If(L.GT(n, max_degree), [L.Return()])] # Declare helper value to hold single dof values and reset. code += [L.Comment("Helper variable to hold values of a single dof.")] nds = gdim**max_degree * physical_value_size dof_values = L.Symbol("dof_values") code += [L.ArrayDecl("double", dof_values, (nds, ), 0.0)] # Create loop over dofs that calls evaluate_basis_derivatives for # a single dof and inserts the values into the global array. code += [L.Comment("Loop dofs and call evaluate_basis_derivatives.")] values = L.FlattenedArray(values, dims=(space_dimension, num_vals)) r = L.Symbol("r") s = L.Symbol("s") loop_s = L.ForRange(s, 0, num_vals, index_type=index_type, body=[L.Assign(values[r, s], dof_values[s])]) code += [ L.ForRange(r, 0, space_dimension, index_type=index_type, body=[ L.Call("evaluate_basis_derivatives", (r, n, dof_values, x, coordinate_dofs, cell_orientation)), loop_s ]) ] return code