def __init__(self, module, F, M, u): """ *Arguments* module (Python module) The module to use for specific form manipulations (typically ufl or dolfin) F (tuple or Form) tuple of (bilinear, linear) forms or linear form M (Form) functional or linear form u (Coefficient) The coefficient considered as the unknown. """ # Store module self.module = module # Store solution Coefficient/Function self.u = u # Extract the lhs (bilinear form), rhs (linear form), goal # (functional), weak residual (linear form) linear_case = (isinstance(F, (tuple, list)) and len(F) == 2) if linear_case: self.lhs, self.rhs = F try: self.goal = action(M, u) except: self.goal = M # Allow functionals as input as well self.weak_residual = self.rhs - action(self.lhs, u) else: self.lhs = self.module.derivative(F, u) self.rhs = F self.goal = M self.weak_residual = -F # At least check that final forms have correct rank assert (len(extract_arguments(self.lhs)) == 2) assert (len(extract_arguments(self.rhs)) == 1) assert (len(extract_arguments(self.goal)) == 0) assert (len(extract_arguments(self.weak_residual)) == 1) # Store map from identifiers to names for forms and generated # coefficients self.ec_names = {} # Use predefined names for the forms in the primal problem self.ec_names[id(self.lhs)] = "lhs" self.ec_names[id(self.rhs)] = "rhs" self.ec_names[id(self.goal)] = "goal" # Initialize other required data self.initialize_data()
def compute_form_with_arity(form, arity, arguments=None): """Compute parts of form of given arity.""" # Extract all arguments in form if arguments is None: arguments = extract_arguments(form) if len(arguments) < arity: warning("Form has no parts with arity %d." % arity) return 0 * form # Assuming that the form is not a sum of terms # that depend on different arguments, e.g. (u+v)*dx # would result in just v*dx. But that doesn't make # any sense anyway. sub_arguments = set(arguments[:arity]) pe = PartExtracter(sub_arguments) def _transform(e): e, provides = pe.visit(e) if provides == sub_arguments: return e return Zero() res = transform_integrands(form, _transform) return res
def compute_form_action(form, coefficient): """Compute the action of a form on a Coefficient. This works simply by replacing the last Argument with a Coefficient on the same function space (element). The form returned will thus have one Argument less and one additional Coefficient at the end if no Coefficient has been provided. """ # TODO: Check whatever makes sense for coefficient # Extract all arguments arguments = extract_arguments(form) # Pick last argument (will be replaced) u = arguments[-1] e = u.element() if coefficient is None: coefficient = Coefficient(e) else: #ufl_assert(coefficient.element() == e, \ if coefficient.element() != e: debug( "Computing action of form on a coefficient in a different element space." ) return replace(form, {u: coefficient})
def compute_form_action(form, coefficient): """Compute the action of a form on a Coefficient. This works simply by replacing the last Argument with a Coefficient on the same function space (element). The form returned will thus have one Argument less and one additional Coefficient at the end if no Coefficient has been provided. """ # TODO: Check whatever makes sense for coefficient # Extract all arguments arguments = extract_arguments(form) # Pick last argument (will be replaced) u = arguments[-1] e = u.element() if coefficient is None: coefficient = Coefficient(e) else: #ufl_assert(coefficient.element() == e, \ if coefficient.element() != e: debug("Computing action of form on a coefficient in a different element space.") return replace(form, { u: coefficient })
def compute_energy_norm(form, coefficient): """Compute the a-norm of a Coefficient given a form a. This works simply by replacing the two Arguments with a Coefficient on the same function space (element). The Form returned will thus be a functional with no Arguments, and one additional Coefficient at the end if no coefficient has been provided. """ arguments = extract_arguments(form) ufl_assert(len(arguments) == 2, "Expecting bilinear form.") v, u = arguments e = u.element() e2 = v.element() ufl_assert( e == e2, "Expecting equal finite elements for test and trial functions, got '%s' and '%s'." % (str(e), str(e2))) if coefficient is None: coefficient = Coefficient(e) else: ufl_assert(coefficient.element() == e, \ "Trying to compute action of form on a "\ "coefficient in an incompatible element space.") return replace(form, {u: coefficient, v: coefficient})
def initialize_data(self): """ Extract required objects for defining error control forms. This will be stored, reused and in particular named. """ # Developer's note: The UFL-FFC-DOLFIN--PyDOLFIN toolchain for # error control is quite fine-tuned. In particular, the order # of coefficients in forms is (and almost must be) used for # their assignment. This means that the order in which these # coefficients are defined matters and should be considered # fixed. from ufl import FiniteElement, Coefficient from ufl.algorithms.elementtransformations import tear, increase_order # Primal trial element space self._V = self.u.element() # Primal test space == Dual trial space Vhat = extract_arguments(self.weak_residual)[0].element() # Discontinuous version of primal trial element space self._dV = tear(self._V) # Extract cell and geometric dimension cell = self._V.cell() gdim = cell.geometric_dimension() # Coefficient representing improved dual E = increase_order(Vhat) self._Ez_h = Coefficient(E) self.ec_names[id(self._Ez_h)] = "__improved_dual" # Coefficient representing cell bubble function B = FiniteElement("B", cell, gdim + 1) self._b_T = Coefficient(B) self.ec_names[id(self._b_T)] = "__cell_bubble" # Coefficient representing strong cell residual self._R_T = Coefficient(self._dV) self.ec_names[id(self._R_T)] = "__cell_residual" # Coefficient representing cell cone function C = FiniteElement("DG", cell, gdim) self._b_e = Coefficient(C) self.ec_names[id(self._b_e)] = "__cell_cone" # Coefficient representing strong facet residual self._R_dT = Coefficient(self._dV) self.ec_names[id(self._R_dT)] = "__facet_residual" # Define discrete dual on primal test space self._z_h = Coefficient(Vhat) self.ec_names[id(self._z_h)] = "__discrete_dual_solution" # Piecewise constants for assembling indicators self._DG0 = FiniteElement("DG", cell, 0)
def initialize_data(self): """ Extract required objects for defining error control forms. This will be stored and reused. """ # Developer's note: The UFL-FFC-DOLFIN--PyDOLFIN toolchain for # error control is quite fine-tuned. In particular, the order # of coefficients in forms is (and almost must be) used for # their assignment. This means that the order in which these # coefficients are defined matters and should be considered # fixed. # Primal trial element space self._V = self.u.function_space() # Primal test space == Dual trial space Vhat = extract_arguments(self.weak_residual)[0].function_space() # Discontinuous version of primal trial element space self._dV = tear(self._V) # Extract cell and geometric dimension mesh = self._V.mesh() dim = mesh.topology().dim() # Function representing improved dual E = increase_order(Vhat) self._Ez_h = Function(E) # Function representing cell bubble function B = FunctionSpace(mesh, "B", dim + 1) self._b_T = Function(B) self._b_T.vector()[:] = 1.0 # Function representing strong cell residual self._R_T = Function(self._dV) # Function representing cell cone function C = FunctionSpace(mesh, "DG", dim) self._b_e = Function(C) # Function representing strong facet residual self._R_dT = Function(self._dV) # Function for discrete dual on primal test space self._z_h = Function(Vhat) # Piecewise constants for assembling indicators self._DG0 = FunctionSpace(mesh, "DG", 0)
def entity_avg(integrand, measure, argument_multiindices): arguments = extract_arguments(integrand) if len(arguments) == 1: a, = arguments integrand = ufl.replace(integrand, {a: ufl.Argument(a.function_space(), number=0, part=a.part())}) argument_multiindices = (argument_multiindices[a.number()], ) degree = estimate_total_polynomial_degree(integrand) form = integrand * measure fd = compute_form_data(form, do_estimate_degrees=False, do_apply_function_pullbacks=False) itg_data, = fd.integral_data integral, = itg_data.integrals integrand = integral.integrand() return integrand, degree, argument_multiindices
def compute_form_arities(form): """Return set of arities of terms present in form.""" #ufl_assert(form.is_preprocessed(), "Assuming a preprocessed form.") # Extract all arguments present in form arguments = extract_arguments(form) arities = set() for arity in range(len(arguments)+1): # Compute parts with arity "arity" parts = compute_form_with_arity(form, arity, arguments) # Register arity if "parts" does not vanish if parts and parts.integrals(): arities.add(arity) return arities
def compute_form_arities(form): """Return set of arities of terms present in form.""" #ufl_assert(form.is_preprocessed(), "Assuming a preprocessed form.") # Extract all arguments present in form arguments = extract_arguments(form) arities = set() for arity in range(len(arguments) + 1): # Compute parts with arity "arity" parts = compute_form_with_arity(form, arity, arguments) # Register arity if "parts" does not vanish if parts and parts.integrals(): arities.add(arity) return arities
def cell_residual(self): """ Generate and return (bilinear, linear) forms defining linear variational problem for the strong cell residual """ # Define trial and test functions for the cell residuals on # discontinuous version of primal trial space R_T = self.module.TrialFunction(self._dV) v = self.module.TestFunction(self._dV) # Extract original test function in the weak residual v_h = extract_arguments(self.weak_residual)[0] # Define forms defining linear variational problem for cell # residual v_T = self._b_T * v a_R_T = inner(v_T, R_T) * dx() L_R_T = replace(self.weak_residual, {v_h: v_T}) return (a_R_T, L_R_T)
def adjoint(form, reordered_arguments=None): """UFL form operator: Given a combined bilinear form, compute the adjoint form by changing the ordering (count) of the test and trial functions. By default, new Argument objects will be created with opposite ordering. However, if the adjoint form is to be added to other forms later, their arguments must match. In that case, the user must provide a tuple reordered_arguments=(u2,v2). """ # ufl.adjoint creates new Arguments if no reordered_arguments is # given. To avoid that, always pass reordered_arguments with # firedrake.Argument objects. if reordered_arguments is None: v, u = extract_arguments(form) reordered_arguments = (Argument(u.element(), u.function_space(), count=v.count()), Argument(v.element(), v.function_space(), count=u.count())) return ufl.adjoint(form, reordered_arguments)
def form_info(form): ufl_assert(isinstance(form, Form), "Expecting a Form.") bf = extract_arguments(form) cf = extract_coefficients(form) ci = form.integrals(Measure.CELL) ei = form.integrals(Measure.EXTERIOR_FACET) ii = form.integrals(Measure.INTERIOR_FACET) pi = form.integrals(Measure.POINT) mi = form.integrals(Measure.MACRO_CELL) s = "Form info:\n" s += " rank: %d\n" % len(bf) s += " num_coefficients: %d\n" % len(cf) s += " num_cell_integrals: %d\n" % len(ci) s += " num_exterior_facet_integrals: %d\n" % len(ei) s += " num_interior_facet_integrals: %d\n" % len(ii) s += " num_point_integrals: %d\n" % len(pi) s += " num_macro_cell_integrals: %d\n" % len(mi) for f in cf: if f._name: s += "\n" s += " Coefficient %d is named '%s'" % (f._count, f._name) s += "\n" for itg in ci: s += "\n" s += integral_info(itg) for itg in ei: s += "\n" s += integral_info(itg) for itg in ii: s += "\n" s += integral_info(itg) for itg in mi: s += "\n" s += integral_info(itg) return s
def compute_energy_norm(form, coefficient): """Compute the a-norm of a Coefficient given a form a. This works simply by replacing the two Arguments with a Coefficient on the same function space (element). The Form returned will thus be a functional with no Arguments, and one additional Coefficient at the end if no coefficient has been provided. """ arguments = extract_arguments(form) ufl_assert(len(arguments) == 2, "Expecting bilinear form.") v, u = arguments e = u.element() e2 = v.element() ufl_assert(e == e2, "Expecting equal finite elements for test and trial functions, got '%s' and '%s'." % (str(e), str(e2))) if coefficient is None: coefficient = Coefficient(e) else: ufl_assert(coefficient.element() == e, \ "Trying to compute action of form on a "\ "coefficient in an incompatible element space.") return replace(form, { u: coefficient, v: coefficient })
def compute_form_adjoint(form, reordered_arguments=None): """Compute the adjoint of a bilinear form. This works simply by changing the ordering (count) of the two arguments. """ arguments = extract_arguments(form) ufl_assert(len(arguments) == 2, "Expecting bilinear form.") v, u = arguments ufl_assert(v.count() < u.count(), "Mistaken assumption in code!") if reordered_arguments is None: reordered_arguments = (Argument(u.element()), Argument(v.element())) reordered_u, reordered_v = reordered_arguments ufl_assert(reordered_u.count() < reordered_v.count(), "Ordering of new arguments is the same as the old arguments!") ufl_assert(reordered_u.element() == u.element(), "Element mismatch between new and old arguments (trial functions).") ufl_assert(reordered_v.element() == v.element(), "Element mismatch between new and old arguments (test functions).") return replace(form, {v: reordered_v, u: reordered_u})
def facet_residual(self): """ Generate and return (bilinear, linear) forms defining linear variational problem for the strong facet residual(s) """ # Define trial and test functions for the facet residuals on # discontinuous version of primal trial space R_e = self.module.TrialFunction(self._dV) v = self.module.TestFunction(self._dV) # Extract original test function in the weak residual v_h = extract_arguments(self.weak_residual)[0] # Define forms defining linear variational problem for facet # residual v_e = self._b_e * v a_R_dT = ( (inner(v_e('+'), R_e('+')) + inner(v_e('-'), R_e('-'))) * dS() + inner(v_e, R_e) * ds()) L_R_dT = (replace(self.weak_residual, {v_h: v_e}) - inner(v_e, self._R_T) * dx()) return (a_R_dT, L_R_dT)
def adjoint(form, reordered_arguments=None): """UFL form operator: Given a combined bilinear form, compute the adjoint form by changing the ordering (number) of the test and trial functions. By default, new Argument objects will be created with opposite ordering. However, if the adjoint form is to be added to other forms later, their arguments must match. In that case, the user must provide a tuple reordered_arguments=(u2,v2). """ # ufl.adjoint creates new Arguments if no reordered_arguments is # given. To avoid that, always pass reordered_arguments with # firedrake.Argument objects. if reordered_arguments is None: v, u = extract_arguments(form) reordered_arguments = (Argument(u.function_space(), number=v.number(), part=v.part()), Argument(v.function_space(), number=u.number(), part=u.part())) return ufl.adjoint(form, reordered_arguments)
def compute_form_adjoint(form, reordered_arguments=None): """Compute the adjoint of a bilinear form. This works simply by changing the ordering (count) of the two arguments. """ arguments = extract_arguments(form) ufl_assert(len(arguments) == 2, "Expecting bilinear form.") v, u = arguments ufl_assert(v.count() < u.count(), "Mistaken assumption in code!") if reordered_arguments is None: reordered_arguments = (Argument(u.element()), Argument(v.element())) reordered_u, reordered_v = reordered_arguments ufl_assert(reordered_u.count() < reordered_v.count(), "Ordering of new arguments is the same as the old arguments!") ufl_assert( reordered_u.element() == u.element(), "Element mismatch between new and old arguments (trial functions).") ufl_assert( reordered_v.element() == v.element(), "Element mismatch between new and old arguments (test functions).") return replace(form, {v: reordered_v, u: reordered_u})
def compute_form_with_arity(form, arity, arguments=None): """Compute parts of form of given arity.""" # Extract all arguments in form if arguments is None: arguments = extract_arguments(form) if len(arguments) < arity: warning("Form has no parts with arity %d." % arity) return 0*form # Assuming that the form is not a sum of terms # that depend on different arguments, e.g. (u+v)*dx # would result in just v*dx. But that doesn't make # any sense anyway. sub_arguments = set(arguments[:arity]) pe = PartExtracter(sub_arguments) def _transform(e): e, provides = pe.visit(e) if provides == sub_arguments: return e return Zero() res = transform_integrands(form, _transform) return res
def prepare_input_arguments(forms, object_names, reserved_objects): """ Extract required input arguments to UFLErrorControlGenerator. *Arguments* forms (tuple) Three (linear case) or two (nonlinear case) forms specifying the primal problem and the goal object_names (dict) Map from object ids to object names reserved_names (dict) Map from reserved object names to object ids *Returns* tuple (of length 3) containing Form or tuple A single linear form or a tuple of a bilinear and a linear form Form A linear form or a functional for the goal functional Coefficient The coefficient considered as the unknown """ # Check that we get a tuple of forms expecting_tuple_msg = "Expecting tuple of forms, got %s" % str(forms) assert(isinstance(forms, (list, tuple))), expecting_tuple_msg def __is_nonlinear(forms): return len(forms) == 2 def __is_linear(forms): return len(forms) == 3 # Extract Coefficient labelled as 'unknown' u = reserved_objects.get("unknown", None) if __is_nonlinear(forms): (F, M) = forms # Check that unknown is defined assert (u), "Can't extract 'unknown'. The Coefficient representing the unknown must be labelled by 'unknown' for nonlinear problems." # Check that forms have the expected rank assert(len(extract_arguments(F)) == 1) assert(len(extract_arguments(M)) == 0) # Return primal, goal and unknown return (F, M, u) elif __is_linear(forms): # Throw error if unknown is given, don't quite know what to do # with this case yet if u: error("'unknown' defined: not implemented for linear problems") (a, L, M) = forms # Check that forms have the expected rank arguments = extract_arguments(a) assert(len(arguments) == 2) assert(len(extract_arguments(L)) == 1) assert(len(extract_arguments(M)) == 1) # Standard case: create default Coefficient in trial space and # label it __discrete_primal_solution V = arguments[1].element() u = Coefficient(V) object_names[id(u)] = "__discrete_primal_solution" return ((a, L), M, u) else: error("Wrong input tuple length: got %s, expected 2 or 3-tuple" % str(forms))
def compile_element(expression, dual_space=None, parameters=None, name="evaluate"): """Generate code for point evaluations. :arg expression: A UFL expression (may contain up to one coefficient, or one argument) :arg dual_space: if the expression has an argument, should we also distribute residual data? :returns: Some coffee AST """ if parameters is None: parameters = default_parameters() else: _ = default_parameters() _.update(parameters) parameters = _ expression = tsfc.ufl_utils.preprocess_expression(expression) # # Collect required coefficients try: arg, = extract_coefficients(expression) argument_multiindices = () coefficient = True if expression.ufl_shape: tensor_indices = tuple(gem.Index() for s in expression.ufl_shape) else: tensor_indices = () except ValueError: arg, = extract_arguments(expression) finat_elem = create_element(arg.ufl_element()) argument_multiindices = (finat_elem.get_indices(), ) argument_multiindex, = argument_multiindices value_shape = finat_elem.value_shape if value_shape: tensor_indices = argument_multiindex[-len(value_shape):] else: tensor_indices = () coefficient = False # Replace coordinates (if any) builder = firedrake_interface.KernelBuilderBase() domain = expression.ufl_domain() # Translate to GEM cell = domain.ufl_cell() dim = cell.topological_dimension() point = gem.Variable('X', (dim,)) point_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('X', rank=(dim,))) config = dict(interface=builder, ufl_cell=cell, precision=parameters["precision"], point_indices=(), point_expr=point, argument_multiindices=argument_multiindices) context = tsfc.fem.GemPointContext(**config) # Abs-simplification expression = tsfc.ufl_utils.simplify_abs(expression) # Translate UFL -> GEM if coefficient: assert dual_space is None f_arg = [builder._coefficient(arg, "f")] else: f_arg = [] translator = tsfc.fem.Translator(context) result, = map_expr_dags(translator, [expression]) b_arg = [] if coefficient: if expression.ufl_shape: return_variable = gem.Indexed(gem.Variable('R', expression.ufl_shape), tensor_indices) result_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('R', rank=expression.ufl_shape)) result = gem.Indexed(result, tensor_indices) else: return_variable = gem.Indexed(gem.Variable('R', (1,)), (0,)) result_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('R', rank=(1,))) else: return_variable = gem.Indexed(gem.Variable('R', finat_elem.index_shape), argument_multiindex) result = gem.Indexed(result, tensor_indices) if dual_space: elem = create_element(dual_space.ufl_element()) if elem.value_shape: var = gem.Indexed(gem.Variable("b", elem.value_shape), tensor_indices) b_arg = [ast.Decl(SCALAR_TYPE, ast.Symbol("b", rank=elem.value_shape))] else: var = gem.Indexed(gem.Variable("b", (1, )), (0, )) b_arg = [ast.Decl(SCALAR_TYPE, ast.Symbol("b", rank=(1, )))] result = gem.Product(result, var) result_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('R', rank=finat_elem.index_shape)) # Unroll max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent result, = gem.optimise.unroll_indexsum([result], predicate=predicate) # Translate GEM -> COFFEE result, = gem.impero_utils.preprocess_gem([result]) impero_c = gem.impero_utils.compile_gem([(return_variable, result)], tensor_indices) body = generate_coffee(impero_c, {}, parameters["precision"]) # Build kernel tuple kernel_code = builder.construct_kernel("pyop2_kernel_" + name, [result_arg] + b_arg + f_arg + [point_arg], body) return kernel_code
def compile_element(expression, dual_space=None, parameters=None, name="evaluate"): """Generate code for point evaluations. :arg expression: A UFL expression (may contain up to one coefficient, or one argument) :arg dual_space: if the expression has an argument, should we also distribute residual data? :returns: Some coffee AST """ if parameters is None: parameters = default_parameters() else: _ = default_parameters() _.update(parameters) parameters = _ expression = tsfc.ufl_utils.preprocess_expression(expression) # # Collect required coefficients try: arg, = extract_coefficients(expression) argument_multiindices = () coefficient = True if expression.ufl_shape: tensor_indices = tuple(gem.Index() for s in expression.ufl_shape) else: tensor_indices = () except ValueError: arg, = extract_arguments(expression) finat_elem = create_element(arg.ufl_element()) argument_multiindices = (finat_elem.get_indices(), ) argument_multiindex, = argument_multiindices value_shape = finat_elem.value_shape if value_shape: tensor_indices = argument_multiindex[-len(value_shape):] else: tensor_indices = () coefficient = False # Replace coordinates (if any) builder = firedrake_interface.KernelBuilderBase(scalar_type=ScalarType_c) domain = expression.ufl_domain() # Translate to GEM cell = domain.ufl_cell() dim = cell.topological_dimension() point = gem.Variable('X', (dim, )) point_arg = ast.Decl(ScalarType_c, ast.Symbol('X', rank=(dim, ))) config = dict(interface=builder, ufl_cell=cell, precision=parameters["precision"], point_indices=(), point_expr=point, argument_multiindices=argument_multiindices) context = tsfc.fem.GemPointContext(**config) # Abs-simplification expression = tsfc.ufl_utils.simplify_abs(expression) # Translate UFL -> GEM if coefficient: assert dual_space is None f_arg = [builder._coefficient(arg, "f")] else: f_arg = [] translator = tsfc.fem.Translator(context) result, = map_expr_dags(translator, [expression]) b_arg = [] if coefficient: if expression.ufl_shape: return_variable = gem.Indexed( gem.Variable('R', expression.ufl_shape), tensor_indices) result_arg = ast.Decl(ScalarType_c, ast.Symbol('R', rank=expression.ufl_shape)) result = gem.Indexed(result, tensor_indices) else: return_variable = gem.Indexed(gem.Variable('R', (1, )), (0, )) result_arg = ast.Decl(ScalarType_c, ast.Symbol('R', rank=(1, ))) else: return_variable = gem.Indexed( gem.Variable('R', finat_elem.index_shape), argument_multiindex) result = gem.Indexed(result, tensor_indices) if dual_space: elem = create_element(dual_space.ufl_element()) if elem.value_shape: var = gem.Indexed(gem.Variable("b", elem.value_shape), tensor_indices) b_arg = [ ast.Decl(ScalarType_c, ast.Symbol("b", rank=elem.value_shape)) ] else: var = gem.Indexed(gem.Variable("b", (1, )), (0, )) b_arg = [ast.Decl(ScalarType_c, ast.Symbol("b", rank=(1, )))] result = gem.Product(result, var) result_arg = ast.Decl(ScalarType_c, ast.Symbol('R', rank=finat_elem.index_shape)) # Unroll max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent result, = gem.optimise.unroll_indexsum([result], predicate=predicate) # Translate GEM -> COFFEE result, = gem.impero_utils.preprocess_gem([result]) impero_c = gem.impero_utils.compile_gem([(return_variable, result)], tensor_indices) body = generate_coffee(impero_c, {}, parameters["precision"], ScalarType_c) # Build kernel tuple kernel_code = builder.construct_kernel( "pyop2_kernel_" + name, [result_arg] + b_arg + f_arg + [point_arg], body) return kernel_code
def splitMultiLinearExpr(expr, arguments=None): if arguments is None: arguments = extract_arguments(expr) return MultiLinearExprSplitter(arguments).visit(expr)