Source code for ffc.quadrature.cpp

# -*- coding: utf-8 -*-
"This module defines rules and algorithms for generating C++ code."

# Copyright (C) 2009-2017 Anders Logg
#
# This file is part of FFC.
#
# FFC is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FFC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with FFC. If not, see <http://www.gnu.org/licenses/>.
#
# Modified by Kristian B. Oelgaard 2011
# Modified by Marie E. Rognes 2010
# Modified by Martin Sandve Alnæs 2013-2017
# Modified by Chris Richardson 2017

# Python modules
import re
import numpy
import platform

# UFL modules
from ufl import custom_integral_types

# FFC modules
from ffc.log import debug, error


# Default precision for formatting floats
default_precision = numpy.finfo("double").precision + 1  # == 16


# ufc class names

[docs]def make_classname(prefix, basename, signature): pre = prefix.lower() + "_" if prefix else "" sig = str(signature).lower()
return "%s%s_%s" % (pre, basename, sig)
[docs]def make_integral_classname(prefix, integral_type, form_id, subdomain_id): basename = "%s_integral_%s" % (integral_type, str(form_id).lower())
return make_classname(prefix, basename, subdomain_id) # Mapping of restrictions _fixed_map = {None: "", "+": "_0", "-": "_1"} _choose_map = lambda r: _fixed_map[r] if r in _fixed_map else "_%s" % str(r) # FIXME: MSA: Using a dict to collect functions in a namespace is weird # and makes the code harder to follow, change to a class # with member functions instead! # FIXME: KBO: format is a builtin_function, i.e., we should use a different name. # Formatting rules format = {} # Program flow format.update({ "return": lambda v: "return %s;" % str(v), "grouping": lambda v: "(%s)" % v, "block": lambda v: "{%s}" % v, "block begin": "{", "block end": "}", "list": lambda v: format["block"](format["list separator"].join([str(l) for l in v])), "switch": lambda v, cases, default=None, numbers=None: _generate_switch(v, cases, default, numbers), "exception": lambda v: "throw std::runtime_error(\"%s\");" % v, "warning": lambda v: 'std::cerr << "*** FFC warning: " << "%s" << std::endl;' % v, "comment": lambda v: "// %s" % v, "if": lambda c, v: "if (%s)\n{\n%s\n}\n" % (c, v), "loop": lambda i, j, k: "for (unsigned int %s = %s; %s < %s; %s++)" % (i, j, i, k, i), "generate loop": lambda v, w, _indent=0: _generate_loop(v, w, _indent), "is equal": " == ", "not equal": " != ", "less than": " < ", "greater than": " > ", "less equal": " <= ", "greater equal": " >= ", "and": " && ", "or": " || ", "not": lambda v: "!(%s)" % v, "do nothing": "// Do nothing" }) # Declarations format.update({ "declaration": lambda t, n, v=None: _declaration(t, n, v), "float declaration": "double", "int declaration": "int", "uint declaration": "unsigned int", "static const uint declaration": "static const unsigned int", "static const float declaration": "static const double", "vector table declaration": "std::vector< std::vector<double> >", "double array declaration": "double*", "const double array declaration": "const double*", "const float declaration": lambda v, w: "const double %s = %s;" % (v, w), "const uint declaration": lambda v, w: "const unsigned int %s = %s;" % (v, w), "dynamic array": lambda t, n, s: "%s *%s = new %s[%s];" % (t, n, t, s), "static array": lambda t, n, s: "static %s %s[%d];" % (t, n, s), "fixed array": lambda t, n, s: "%s %s[%d];" % (t, n, s), "delete dynamic array": lambda n, s=None: _delete_array(n, s), "create foo": lambda v: "new %s()" % v, "create factory": lambda v: "create_%s()" % v }) # Mathematical operators format.update({ "add": lambda v: " + ".join(v), "iadd": lambda v, w: "%s += %s;" % (str(v), str(w)), "sub": lambda v: " - ".join(v), "neg": lambda v: "-%s" % v, "mul": lambda v: "*".join(v), "imul": lambda v, w: "%s *= %s;" % (str(v), str(w)), "div": lambda v, w: "%s/%s" % (str(v), str(w)), "inverse": lambda v: "(1.0/%s)" % v, "std power": lambda base, exp: "std::pow(%s, %s)" % (base, exp), "exp": lambda v: "std::exp(%s)" % str(v), "ln": lambda v: "std::log(%s)" % str(v), "cos": lambda v: "std::cos(%s)" % str(v), "sin": lambda v: "std::sin(%s)" % str(v), "tan": lambda v: "std::tan(%s)" % str(v), "cosh": lambda v: "std::cosh(%s)" % str(v), "sinh": lambda v: "std::sinh(%s)" % str(v), "tanh": lambda v: "std::tanh(%s)" % str(v), "acos": lambda v: "std::acos(%s)" % str(v), "asin": lambda v: "std::asin(%s)" % str(v), "atan": lambda v: "std::atan(%s)" % str(v), "atan_2": lambda v1, v2: "std::atan2(%s,%s)" % (str(v1), str(v2)), "erf": lambda v: "erf(%s)" % str(v), "bessel_i": lambda v, n: "boost::math::cyl_bessel_i(%s, %s)" % (str(n), str(v)), "bessel_j": lambda v, n: "boost::math::cyl_bessel_j(%s, %s)" % (str(n), str(v)), "bessel_k": lambda v, n: "boost::math::cyl_bessel_k(%s, %s)" % (str(n), str(v)), "bessel_y": lambda v, n: "boost::math::cyl_neumann(%s, %s)" % (str(n), str(v)), "absolute value": lambda v: "std::abs(%s)" % str(v), "min value": lambda l, r: "std::min(%s, %s)" % (str(l), str(r)), "max value": lambda l, r: "std::max(%s, %s)" % (str(l), str(r)), "sqrt": lambda v: "std::sqrt(%s)" % str(v), "addition": lambda v: _add(v), "multiply": lambda v: _multiply(v), "power": lambda base, exp: _power(base, exp), "inner product": lambda v, w: _inner_product(v, w), "assign": lambda v, w: "%s = %s;" % (v, str(w)), "component": lambda v, k: _component(v, k) }) # Formatting used in tabulate_tensor format.update({ "geometry tensor": lambda j, a: "G%d_%s" % (j, "_".join(["%d" % i for i in a])) }) # Geometry related variable names (from code snippets). format.update({ "entity index": "entity_indices", "num entities": "num_global_entities", "cell": lambda s: "ufc::shape::%s" % s, "J": lambda i, j, m, n: "J[%d]" % _flatten(i, j, m, n), "inv(J)": lambda i, j, m, n: "K[%d]" % _flatten(i, j, m, n), "det(J)": lambda r=None: "detJ%s" % _choose_map(r), "cell volume": lambda r=None: "volume%s" % _choose_map(r), "circumradius": lambda r=None: "circumradius%s" % _choose_map(r), "facet area": "facet_area", "min facet edge length": lambda r: "min_facet_edge_length", "max facet edge length": lambda r: "max_facet_edge_length", "scale factor": "det", "transform": lambda t, i, j, m, n, r: _transform(t, i, j, m, n, r), "normal component": lambda r, j: "n%s%s" % (_choose_map(r), j), "x coordinate": "X", "y coordinate": "Y", "z coordinate": "Z", "ip coordinates": lambda i, j: "X%d[%d]" % (i, j), "affine map table": lambda i, j: "FEA%d_f%d" % (i, j), "coordinate_dofs": lambda r=None: "coordinate_dofs%s" % _choose_map(r) }) # UFC function arguments and class members (names) format.update({ "element tensor": lambda i: "A[%s]" % i, "element tensor term": lambda i, j: "A%d[%s]" % (j, i), "coefficient": lambda j, k: format["component"]("w", [j, k]), "argument basis num": "i", "argument derivative order": "n", "argument values": "values", "argument coordinates": "dof_coordinates", "facet": lambda r: "facet%s" % _choose_map(r), "vertex": "vertex", "argument axis": "i", "argument dimension": "d", "argument entity": "i", "member global dimension": "_global_dimension", "argument dofs": "dofs", "argument dof num": "i", "argument dof values": "dof_values", "argument vertex values": "vertex_values", "argument sub": "i", # sub element "argument subdomain": "subdomain_id", # sub domain }) # Formatting used in evaluatedof. format.update({ "dof vals": "vals", "dof result": "result", "dof X": lambda i: "X_%d" % i, "dof D": lambda i: "D_%d" % i, "dof W": lambda i: "W_%d" % i, "dof copy": lambda i: "copy_%d" % i, "dof physical coordinates": "y" }) # Formatting used in evaluate_basis, evaluate_basis_derivatives and quadrature # code generators. format.update({ # evaluate_basis and evaluate_basis_derivatives "tmp value": lambda i: "tmp%d" % i, "tmp ref value": lambda i: "tmp_ref%d" % i, "local dof": "dof", "basisvalues": "basisvalues", "coefficients": lambda i: "coefficients%d" % (i), "num derivatives": lambda t_or_g: "num_derivatives" + t_or_g, "derivative combinations": lambda t_or_g: "combinations" + t_or_g, "transform matrix": "transform", "transform Jinv": "Jinv", "dmats": lambda i: "dmats%s" % (i), "dmats old": "dmats_old", "reference derivatives": "derivatives", "dof values": "dof_values", "dof map if": lambda i, j: "%d <= %s && %s <= %d"\ % (i, format["argument basis num"], format["argument basis num"], j), "dereference pointer": lambda n: "*%s" % n, "reference variable": lambda n: "&%s" % n, "call basis": lambda i, s: "evaluate_basis(%s, %s, x, coordinate_dofs, cell_orientation);" % (i, s), "call basis_all": "evaluate_basis_all(values, x, coordinate_dofs, cell_orientation);", "call basis_derivatives": lambda i, s: "evaluate_basis_derivatives(%s, n, %s, x, coordinate_dofs, cell_orientation);" % (i, s), "call basis_derivatives_all": lambda i, s: "evaluate_basis_derivatives_all(n, %s, x, coordinate_dofs, cell_orientation);" % s, # quadrature code generators "integration points": "ip", "first free index": "j", "second free index": "k", "geometry constant": lambda i: "G[%d]" % i, "ip constant": lambda i: "I[%d]" % i, "basis constant": lambda i: "B[%d]" % i, "conditional": lambda i: "C[%d]" % i, "evaluate conditional": lambda i, j, k: "(%s) ? %s : %s" % (i, j, k), # "geometry constant": lambda i: "G%d" % i, # "ip constant": lambda i: "I%d" % i, # "basis constant": lambda i: "B%d" % i, "function value": lambda i: "F%d" % i, "nonzero columns": lambda i: "nzc%d" % i, "weight": lambda i: "W" if i is None else "W%d" % (i), "psi name": lambda c, et, e, co, d, a: _generate_psi_name(c, et, e, co, d, a), # both "free indices": ["r", "s", "t", "u"], "matrix index": lambda i, j, range_j: _matrix_index(i, str(j), str(range_j)), "quadrature point": lambda i, gdim: "quadrature_points + %s*%d" % (i, gdim), "facet_normal_custom": lambda gdim: _generate_facet_normal_custom(gdim), }) # Misc format.update({ "bool": lambda v: {True: "true", False: "false"}[v], "str": lambda v: "%s" % v, "int": lambda v: "%d" % v, "list separator": ", ", "block separator": ",\n", "new line": "\\\n", "tabulate tensor": lambda m: _tabulate_tensor(m), }) # Code snippets from ffc.quadrature.codesnippets import * format.update({ "compute_jacobian": lambda tdim, gdim, r=None: compute_jacobian[tdim][gdim] % {"restriction": _choose_map(r)}, "compute_jacobian_inverse": lambda tdim, gdim, r=None: compute_jacobian_inverse[tdim][gdim] % {"restriction": _choose_map(r)}, "orientation": lambda tdim, gdim, r=None: orientation_snippet % {"restriction": _choose_map(r)} if tdim != gdim else "", "facet determinant": lambda tdim, gdim, r=None: facet_determinant[tdim][gdim] % {"restriction": _choose_map(r)}, "fiat coordinate map": lambda cell, gdim: fiat_coordinate_map[cell][gdim], "generate normal": lambda tdim, gdim, i: _generate_normal(tdim, gdim, i), "generate cell volume": lambda tdim, gdim, i, r=None: _generate_cell_volume(tdim, gdim, i, r), "generate circumradius": lambda tdim, gdim, i, r=None: _generate_circumradius(tdim, gdim, i, r), "generate facet area": lambda tdim, gdim: facet_area[tdim][gdim], "generate min facet edge length": lambda tdim, gdim, r=None: min_facet_edge_length[tdim][gdim] % {"restriction": _choose_map(r)}, "generate max facet edge length": lambda tdim, gdim, r=None: max_facet_edge_length[tdim][gdim] % {"restriction": _choose_map(r)}, "generate ip coordinates": lambda g, t, num_ip, name, ip, r=None: (ip_coordinates[t][g][0], ip_coordinates[t][g][1] % {"restriction": _choose_map(r), "ip": ip, "name": name, "num_ip": num_ip}), "scale factor snippet": scale_factor, "map onto physical": map_onto_physical, "evaluate basis snippet": eval_basis, "combinations": combinations_snippet, "transform snippet": transform_snippet, "evaluate function": evaluate_f, "ufc comment": comment_ufc, "dolfin comment": comment_dolfin, "header_h": header_h, "header_c": header_c, "footer": footer, "eval_basis_decl": eval_basis_decl, "eval_basis_init": eval_basis_init, "eval_basis": eval_basis, "eval_basis_copy": eval_basis_copy, "eval_derivs_decl": eval_derivs_decl, "eval_derivs_init": eval_derivs_init, "eval_derivs": eval_derivs, "eval_derivs_copy": eval_derivs_copy, "extract_cell_coordinates": lambda offset, r: "const double* coordinate_dofs_%d = coordinate_dofs + %d;" % (r, offset) }) # Helper functions for formatting def _declaration(type, name, value=None): if value is None: return "%s %s;" % (type, name) return "%s %s = %s;" % (type, name, str(value)) def _component(var, k): if not isinstance(k, (list, tuple)): k = [k] return "%s" % var + "".join("[%s]" % str(i) for i in k) def _delete_array(name, size=None): if size is None: return "delete [] %s;" % name f_r = format["free indices"][0] code = format["generate loop"](["delete [] %s;" % format["component"](name, f_r)], [(f_r, 0, size)]) code.append("delete [] %s;" % name) return "\n".join(code) def _multiply(factors): """ Generate string multiplying a list of numbers or strings. If a factor is zero, the whole product is zero. Any factors equal to one are ignored. """ # FIXME: This could probably be way more robust and elegant. cpp_str = format["str"] non_zero_factors = [] for f in factors: # Round-off if f is smaller than epsilon if isinstance(f, (int, float)): if abs(f) < format["epsilon"]: return cpp_str(0) if abs(f - 1.0) < format["epsilon"]: continue # Convert to string f = cpp_str(f) # Return zero if any factor is zero if f == "0": return cpp_str(0) # If f is 1, don't add it to list of factors if f == "1": continue # If sum-like, parentheseze factor if "+" in f or "-" in f: f = "(%s)" % f non_zero_factors += [f] if len(non_zero_factors) == 0: return cpp_str(1.0) return "*".join(non_zero_factors) def _add(terms): "Generate string summing a list of strings." # FIXME: Subtract absolute value of negative numbers result = " + ".join([str(t) for t in terms if (str(t) != "0")]) if result == "": return format["str"](0) return result def _power(base, exponent): "Generate code for base^exponent." if exponent >= 0: return _multiply(exponent * (base,)) else: return "1.0 / (%s)" % _power(base, -exponent) def _inner_product(v, w): "Generate string for v[0]*w[0] + ... + v[n]*w[n]." # Check that v and w have same length assert(len(v) == len(w)), "Sizes differ in inner-product!" # Special case, zero terms if len(v) == 0: return format["float"](0) # Straightforward handling when we only have strings if isinstance(v[0], str): return _add([_multiply([v[i], w[i]]) for i in range(len(v))]) # Fancy handling of negative numbers etc result = None eps = format["epsilon"] add = format["add"] sub = format["sub"] neg = format["neg"] mul = format["mul"] fl = format["float"] for (c, x) in zip(v, w): if result: if abs(c - 1.0) < eps: result = add([result, x]) elif abs(c + 1.0) < eps: result = sub([result, x]) elif c > eps: result = add([result, mul([fl(c), x])]) elif c < -eps: result = sub([result, mul([fl(-c), x])]) else: if abs(c - 1.0) < eps: result = x elif abs(c + 1.0) < eps: result = neg(x) elif c > eps: result = mul([fl(c), x]) elif c < -eps: result = neg(mul([fl(-c), x])) return result def _transform(type, i, j, m, n, r): map_name = {"J": "J", "JINV": "K"}[type] + _choose_map(r) return (map_name + "[%d]") % _flatten(i, j, m, n) # FIXME: Input to _generate_switch should be a list of tuples (i, case) def _generate_switch(variable, cases, default=None, numbers=None): "Generate switch statement from given variable and cases" # Special case: no cases and no default if len(cases) == 0 and default is None: return format["do nothing"] elif len(cases) == 0: return default # Special case: one case and no default if len(cases) == 1 and default is None: return cases[0] # Create numbers for switch if numbers is None: numbers = list(range(len(cases))) # Create switch code = "switch (%s)\n{\n" % variable for (i, case) in enumerate(cases): code += "case %d:\n {\n %s\n break;\n }\n" % (numbers[i], indent(case, 2)) code += "}\n" # Default value if default: code += "\n" + default return code def _tabulate_tensor(vals): "Tabulate a multidimensional tensor. (Replace tabulate_matrix and tabulate_vector)." # Prefetch formats to speed up code generation f_block = format["block"] f_list_sep = format["list separator"] f_block_sep = format["block separator"] # FIXME: KBO: Change this to "float" once issue in set_float_formatting is fixed. f_float = format["floating point"] f_epsilon = format["epsilon"] # Create numpy array and get shape. tensor = numpy.array(vals) shape = numpy.shape(tensor) if len(shape) == 1: # Create zeros if value is smaller than tolerance. values = [] for v in tensor: if abs(v) < f_epsilon: values.append(f_float(0.0)) else: values.append(f_float(v)) # Format values. return f_block(f_list_sep.join(values)) elif len(shape) > 1: return f_block(f_block_sep.join([_tabulate_tensor(tensor[i]) for i in range(shape[0])])) else: error("Not an N-dimensional array:\n%s" % tensor) def _generate_loop(lines, loop_vars, _indent): "This function generates a loop over a vector or matrix." # Prefetch formats to speed up code generation. f_loop = format["loop"] f_begin = format["block begin"] f_end = format["block end"] f_comment = format["comment"] if not loop_vars: return lines code = [] for ls in loop_vars: # Get index and lower and upper bounds. index, lower, upper = ls # Loop index. code.append(indent(f_loop(index, lower, upper), _indent)) code.append(indent(f_begin, _indent)) # Increase indentation. _indent += 2 # If this is the last loop, write values. if index == loop_vars[-1][0]: for l in lines: code.append(indent(l, _indent)) # Decrease indentation and write end blocks. indices = [var[0] for var in loop_vars] indices.reverse() for index in indices: _indent -= 2 code.append(indent(f_end + " " + f_comment("end loop over '%s'" % index), _indent)) return code def _matrix_index(i, j, range_j): "Map the indices in a matrix to an index in an array i.e., m[i][j] -> a[i*range(j)+j]" if i == 0: access = j elif i == 1: access = format["add"]([range_j, j]) else: irj = format["mul"]([format["str"](i), range_j]) access = format["add"]([irj, j]) return access def _generate_psi_name(counter, entity_type, entity, component, derivatives, avg): """Generate a name for the psi table of the form: FE#_f#_v#_C#_D###_A#, where '#' will be an integer value. FE - is a simple counter to distinguish the various bases, it will be assigned in an arbitrary fashion. f - denotes facets if applicable, range(element.num_facets()). v - denotes vertices if applicable, range(num_vertices). C - is the component number if any (flattened in the case of tensor valued functions) D - is the number of derivatives in each spatial direction if any. If the element is defined in 3D, then D012 means d^3(*)/dydz^2. A - denotes averaged over cell (AC) or facet (AF) """ name = "FE%d" % counter if entity_type == "facet": name += "_f%d" % entity elif entity_type == "vertex": name += "_v%d" % entity if component != () and component != []: name += "_C%d" % component if any(derivatives): name += "_D" + "".join(map(str, derivatives)) if avg == "cell": name += "_AC" elif avg == "facet": name += "_AF" return name def _generate_normal(tdim, gdim, integral_type, reference_normal=False): "Generate code for computing normal" # Choose snippets direction = normal_direction[tdim][gdim] assert (gdim in facet_normal[tdim]),\ "Facet normal not yet implemented for this tdim/gdim combo" normal = facet_normal[tdim][gdim] # Choose restrictions if integral_type == "exterior_facet": code = direction % {"restriction": "", "facet": "facet"} code += normal % {"direction": "", "restriction": ""} elif integral_type == "interior_facet": code = direction % {"restriction": _choose_map("+"), "facet": "facet_0"} code += normal % {"direction": "", "restriction": _choose_map("+")} code += normal % {"direction": "!", "restriction": _choose_map("-")} else: error("Unsupported integral_type: %s" % str(integral_type)) return code def _generate_facet_normal_custom(gdim): "Generate code for setting facet normal in custom integrals" code = format["comment"]("Set facet normal components for current quadrature point\n") for i in range(gdim): code += "const double n_0%d = facet_normals[%d*ip + %d];\n" % (i, gdim, i) code += "const double n_1%d = - facet_normals[%d*ip + %d];\n" % (i, gdim, i) return code def _generate_cell_volume(tdim, gdim, integral_type, r=None): "Generate code for computing cell volume." # Choose snippets volume = cell_volume[tdim][gdim] # Choose restrictions if integral_type in ("cell", "exterior_facet"): code = volume % {"restriction": ""} elif integral_type == "interior_facet": code = volume % {"restriction": _choose_map("+")} code += volume % {"restriction": _choose_map("-")} elif integral_type in custom_integral_types: code = volume % {"restriction": _choose_map(r)} else: error("Unsupported integral_type: %s" % str(integral_type)) return code def _generate_circumradius(tdim, gdim, integral_type, r=None): "Generate code for computing a cell's circumradius." # Choose snippets radius = circumradius[tdim][gdim] # Choose restrictions if integral_type in ("cell", "exterior_facet", "vertex"): code = radius % {"restriction": ""} elif integral_type == "interior_facet": code = radius % {"restriction": _choose_map("+")} code += radius % {"restriction": _choose_map("-")} elif integral_type in custom_integral_types: code = radius % {"restriction": _choose_map(r)} else: error("Unsupported integral_type: %s" % str(integral_type)) return code def _flatten(i, j, m, n): return i * n + j # Other functions
[docs]def indent(block, num_spaces): "Indent each row of the given string block with n spaces." indentation = " " * num_spaces
return indentation + ("\n" + indentation).join(block.split("\n"))
[docs]def count_ops(code): "Count the number of operations in code (multiply-add pairs)." num_add = code.count(" + ") + code.count(" - ") num_multiply = code.count("*") + code.count("/")
return (num_add + num_multiply) // 2
[docs]def set_float_formatting(precision): "Set floating point formatting based on precision." # Set default if not set if precision is None: precision = default_precision # Options for float formatting # f1 = "%%.%df" % precision # f2 = "%%.%de" % precision f1 = "%%.%dg" % precision f2 = "%%.%dg" % precision f_int = "%%.%df" % 1 eps = eval("1e-%s" % precision) # Regular float formatting def floating_point_regular(v): if abs(v - round(v, 1)) < eps: return f_int % v elif abs(v) < 100.0: return f1 % v else: return f2 % v # Special float formatting on Windows (remove extra leading zero) def floating_point_windows(v): return floating_point_regular(v).replace("e-0", "e-").replace("e+0", "e+") # Set float formatting if platform.system() == "Windows": format["float"] = floating_point_windows else: format["float"] = floating_point_regular # FIXME: KBO: Remove once we agree on the format of 'f1' format["floating point"] = format["float"] # Set machine precision
format["epsilon"] = 10.0 * eval("1e-%s" % precision) # Hack to propagate precision to uflacs internals... #import ffc.uflacs.language.format_value #ffc.uflacs.language.format_value.set_float_precision(precision)
[docs]def set_exception_handling(convert_exceptions_to_warnings): "Set handling of exceptions." if convert_exceptions_to_warnings:
format["exception"] = format["warning"] # Declarations to examine types = [["double"], ["const", "double"], ["const", "double", "*", "const", "*"], ["int"], ["const", "int"], ["unsigned", "int"], ["bool"], ["const", "bool"], ["static", "unsigned", "int"], ["const", "unsigned", "int"]] # Special characters and delimiters special_characters = ["+", "-", "*", "/", "=", ".", " ", ";", "(", ")", "\\", "{", "}", "[", "]", "!"]
[docs]def remove_unused(code, used_set=set()): """ Remove unused variables from a given C++ code. This is useful when generating code that will be compiled with gcc and parameters -Wall -Werror, in which case gcc returns an error when seeing a variable declaration for a variable that is never used. Optionally, a set may be specified to indicate a set of variables names that are known to be used a priori. """ # Dictionary of (declaration_line, used_lines) for variables variables = {} # List of variable names (so we can search them in order) variable_names = [] lines = code.split("\n") for (line_number, line) in enumerate(lines): # Exclude commented lines. if line[:2] == "//" or line[:3] == "///": continue # Split words words = [word for word in line.split(" ") if word != ""] # Remember line where variable is declared for type in [type for type in types if " ".join(type) in " ".join(words)]: # Fewer matches than line below. # for type in [type for type in types if len(words) > len(type)]: variable_type = words[0:len(type)] variable_name = words[len(type)] # Skip special characters if variable_name in special_characters: continue # Test if any of the special characters are present in the variable name # If this is the case, then remove these by assuming that the 'real' name # is the first entry in the return list. This is implemented to prevent # removal of e.g. 'double array[6]' if it is later used in a loop as 'array[i]' if variable_type == type: # Create correct variable name (e.g. y instead of # y[2]) for variables with separators seps_present = [sep for sep in special_characters if sep in variable_name] if seps_present: variable_name = sorted([variable_name.split(sep)[0] for sep in seps_present]) variable_name = variable_name[0] variables[variable_name] = (line_number, []) if variable_name not in variable_names: variable_names += [variable_name] # Mark line for used variables for variable_name in variables: (declaration_line, used_lines) = variables[variable_name] if _variable_in_line(variable_name, line) and line_number > declaration_line: variables[variable_name] = (declaration_line, used_lines + [line_number]) # Reverse the order of the variable names to catch variables used # only by variables that are removed variable_names.reverse() # Remove declarations that are not used removed_lines = [] for variable_name in variable_names: (declaration_line, used_lines) = variables[variable_name] for line in removed_lines: if line in used_lines: used_lines.remove(line) if not used_lines and variable_name not in used_set: debug("Removing unused variable: %s" % variable_name) lines[declaration_line] = None # KBO: Need to completely remove line for evaluate_basis* to work # lines[declaration_line] = "// " + lines[declaration_line] removed_lines += [declaration_line]
return "\n".join([line for line in lines if line is not None]) def _variable_in_line(variable_name, line): "Check if variable name is used in line" if variable_name not in line: return False for character in special_characters: line = line.replace(character, "\\" + character) delimiter = "[" + ",".join(["\\" + c for c in special_characters]) + "]" return re.search(delimiter + variable_name + delimiter, line) is not None