diff --git a/python/dune/perftool/pdelab/localoperator.py b/python/dune/perftool/pdelab/localoperator.py index 190882c0b3a9b1e1882a164a309d75ed4b78d665..e1ae4c43b0ab23bcc9a8448bad3b1b1f962f3aeb 100644 --- a/python/dune/perftool/pdelab/localoperator.py +++ b/python/dune/perftool/pdelab/localoperator.py @@ -3,6 +3,8 @@ from os.path import splitext import logging +import numpy as np + from dune.perftool.options import (get_form_option, get_option, option_switch) @@ -707,81 +709,7 @@ def cgen_class_from_cache(tag, members=[]): return Class(basename, base_classes=base_classes, members=[constructor] + members + pm + decls, tparam_decls=tparams) -def generate_localoperator_kernels(operator): - logger = logging.getLogger(__name__) - - data = get_global_context_value("data") - original_form = data.object_by_name[get_form_option("form")] - from dune.perftool.ufl.preprocess import preprocess_form - - if get_form_option("adjoint"): - # Generate adjoint operator - # - # The jacobian of the adjoint form is just the jacobian of the - # original form with test and ansazt function swapped. A a - # linear form you have to subtract the derivative of the - # objective function w.r.t the ansatz function to get the - # final residual formulation of the adjoint. - # - # TODO: This might only be true for linear problems. Adjust - # documentation as knowledge improves ;) - assert get_form_option("objective_function") is not None - assert get_form_option("control") is False - - from ufl import derivative, adjoint, action, replace - from ufl.classes import Coefficient - - # Jacobian of the adjoint form - jacform = derivative(original_form, original_form.coefficients()[0]) - adjoint_jacform = adjoint(jacform) - - # Derivative of objective function w.r.t. state - objective = data.object_by_name[get_form_option("objective_function")] - objective_jacobian = derivative(objective, objective.coefficients()[0]) - - # Replace coefficient belonging to ansatz function with new coefficient - element = objective.coefficients()[0].ufl_element() - coeff = Coefficient(element, count=3) - objective_jacobian = replace(objective_jacobian, {objective.coefficients()[0]: coeff}) - if len(adjoint_jacform.coefficients()) > 0: - adjoint_jacform = replace(adjoint_jacform, {adjoint_jacform.coefficients()[0]: coeff}) - - # Residual of the adjoint form - adjoint_form = action(adjoint_jacform, original_form.coefficients()[0]) - adjoint_form = adjoint_form + objective_jacobian - - # Update form and original_form - original_form = adjoint_form - form = preprocess_form(adjoint_form).preprocessed_form - - elif get_form_option("control"): - # Generate control operator - # - # This is the normal form derived w.r.t. the control - # variable. Right now this is olny implemented for scalar - # controls. - assert get_form_option("control_variable") is not None - - control = data.object_by_name[get_form_option("control_variable")] - assert control.ufl_shape is () - - from ufl import diff, replace - from ufl.classes import Coefficient - control_form = diff(original_form, control) - # element = control_form.coefficients()[0].ufl_element() - # coeff = Coefficient(element, count=3) - # control_form = replace(control_form, {control_form.coefficients()[0]: coeff}) - original_form = control_form - form = preprocess_form(control_form).preprocessed_form - - else: - form = preprocess_form(original_form).preprocessed_form - - - # Reset the generation cache - from dune.perftool.generation import delete_cache_items - delete_cache_items() - +def local_operator_default_settings(operator, form): # Manage includes and base classes that we always need include_file('dune/pdelab/gridfunctionspace/gridfunctionspace.hh', filetag="operatorfile") include_file('dune/pdelab/localoperator/idefault.hh', filetag="operatorfile") @@ -825,10 +753,9 @@ def generate_localoperator_kernels(operator): base_class('Dune::PDELab::InstationaryLocalOperatorDefaultMethods<{}>' .format(rf), classtag="operator") - # Have a data structure collect the generated kernels - operator_kernels = {} - logger.info("generate_localoperator_kernels: create residual methods") +def generate_residual_kernels(form): + logger = logging.getLogger(__name__) with global_context(form_type='residual'): # Generate the necessary residual methods for measure in set(i.integral_type() for i in form.integrals()): @@ -883,63 +810,181 @@ def generate_localoperator_kernels(operator): classtag="operator", ) - operator_kernels[(measure, 'residual')] = kernel + return {(measure, 'residual'): kernel} - # Generate the necessary jacobian methods - if not get_form_option("numerical_jacobian"): - logger.info("generate_localoperator_kernels: create jacobian methods") - from ufl import derivative - jacform = derivative(original_form, original_form.coefficients()[0]) - from dune.perftool.ufl.preprocess import preprocess_form - jacform = preprocess_form(jacform).preprocessed_form +def generate_jacobian_kernels(form, original_form): + logger = logging.getLogger(__name__) - with global_context(form_type="jacobian"): - if get_form_option("generate_jacobians"): - for measure in set(i.integral_type() for i in jacform.integrals()): - logger.info("generate_localoperator_kernels: measure {}".format(measure)) - with global_context(integral_type=measure): - with global_context(kernel=assembler_routine_name()): - kernel = [k for k in get_backend(interface="generate_kernels_per_integral")(jacform.integrals_by_type(measure))] - operator_kernels[(measure, 'jacobian')] = kernel + from ufl import derivative + jacform = derivative(original_form, original_form.coefficients()[0]) + + from dune.perftool.ufl.preprocess import preprocess_form + jacform = preprocess_form(jacform).preprocessed_form + + operator_kernels = {} + with global_context(form_type="jacobian"): + if get_form_option("generate_jacobians"): + for measure in set(i.integral_type() for i in jacform.integrals()): + logger.info("generate_localoperator_kernels: measure {}".format(measure)) + with global_context(integral_type=measure): + from dune.perftool.pdelab.signatures import assembler_routine_name + with global_context(kernel=assembler_routine_name()): + kernel = [k for k in get_backend(interface="generate_kernels_per_integral")(jacform.integrals_by_type(measure))] + operator_kernels[(measure, 'jacobian')] = kernel + + # Generate dummy functions for those kernels, that vanished in the differentiation process + # We *could* solve this problem by using lambda_* terms but we do not really want that, so + # we use empty jacobian assembly methods instead + alpha_measures = set(i.integral_type() for i in form.integrals()) + jacobian_measures = set(i.integral_type() for i in jacform.integrals()) + for it in alpha_measures - jacobian_measures: + with global_context(integral_type=it): + from dune.perftool.pdelab.signatures import assembly_routine_signature + operator_kernels[(it, 'jacobian')] = [LoopyKernelMethod(assembly_routine_signature(), kernel=None)] + + # Jacobian apply methods for matrix-free computations + if get_form_option("matrix_free"): + # The apply vector has reserved index 1 so we directly use Coefficient class from ufl + from ufl import Coefficient + apply_coefficient = Coefficient(form.coefficients()[0].ufl_element(), 1) + + # Create application of jacobian on vector + from ufl import action + jac_apply_form = action(jacform, apply_coefficient) + + # Create kernel for jacobian application + with global_context(form_type="jacobian_apply"): + for measure in set(i.integral_type() for i in jac_apply_form.integrals()): + with global_context(integral_type=measure): + with global_context(kernel=assembler_routine_name()): + kernel = [k for k in get_backend(interface="generate_kernels_per_integral")(jac_apply_form.integrals_by_type(measure))] + operator_kernels[(measure, 'jacobian_apply')] = kernel # Generate dummy functions for those kernels, that vanished in the differentiation process # We *could* solve this problem by using lambda_* terms but we do not really want that, so # we use empty jacobian assembly methods instead alpha_measures = set(i.integral_type() for i in form.integrals()) - jacobian_measures = set(i.integral_type() for i in jacform.integrals()) - for it in alpha_measures - jacobian_measures: + jacobian_apply_measures = set(i.integral_type() for i in jac_apply_form.integrals()) + for it in alpha_measures - jacobian_apply_measures: with global_context(integral_type=it): from dune.perftool.pdelab.signatures import assembly_routine_signature - operator_kernels[(it, 'jacobian')] = [LoopyKernelMethod(assembly_routine_signature(), kernel=None)] - - # Jacobian apply methods for matrix-free computations - if get_form_option("matrix_free"): - # The apply vector has reserved index 1 so we directly use Coefficient class from ufl - from ufl import Coefficient - apply_coefficient = Coefficient(form.coefficients()[0].ufl_element(), 1) - - # Create application of jacobian on vector - from ufl import action - jac_apply_form = action(jacform, apply_coefficient) - - # Create kernel for jacobian application - with global_context(form_type="jacobian_apply"): - for measure in set(i.integral_type() for i in jac_apply_form.integrals()): - with global_context(integral_type=measure): - with global_context(kernel=assembler_routine_name()): - kernel = [k for k in get_backend(interface="generate_kernels_per_integral")(jac_apply_form.integrals_by_type(measure))] - operator_kernels[(measure, 'jacobian_apply')] = kernel - - # Generate dummy functions for those kernels, that vanished in the differentiation process - # We *could* solve this problem by using lambda_* terms but we do not really want that, so - # we use empty jacobian assembly methods instead - alpha_measures = set(i.integral_type() for i in form.integrals()) - jacobian_apply_measures = set(i.integral_type() for i in jac_apply_form.integrals()) - for it in alpha_measures - jacobian_apply_measures: - with global_context(integral_type=it): - from dune.perftool.pdelab.signatures import assembly_routine_signature - operator_kernels[(it, 'jacobian_apply')] = [LoopyKernelMethod(assembly_routine_signature(), kernel=None)] + operator_kernels[(it, 'jacobian_apply')] = [LoopyKernelMethod(assembly_routine_signature(), kernel=None)] + + return operator_kernels + + +def generate_localoperator_kernels(operator): + logger = logging.getLogger(__name__) + + data = get_global_context_value("data") + original_form = data.object_by_name[get_form_option("form")] + from dune.perftool.ufl.preprocess import preprocess_form + + if get_form_option("adjoint"): + # Generate adjoint operator + # + # The jacobian of the adjoint form is just the jacobian of the + # original form with test and ansazt function swapped. A a + # linear form you have to subtract the derivative of the + # objective function w.r.t the ansatz function to get the + # final residual formulation of the adjoint. + # + # TODO: This might only be true for linear problems. Adjust + # documentation as knowledge improves ;) + assert get_form_option("objective_function") is not None + assert get_form_option("control") is False + + from ufl import derivative, adjoint, action, replace + from ufl.classes import Coefficient + + # Jacobian of the adjoint form + jacform = derivative(original_form, original_form.coefficients()[0]) + adjoint_jacform = adjoint(jacform) + + # Derivative of objective function w.r.t. state + objective = data.object_by_name[get_form_option("objective_function")] + objective_jacobian = derivative(objective, objective.coefficients()[0]) + + # Replace coefficient belonging to ansatz function with new coefficient + element = objective.coefficients()[0].ufl_element() + coeff = Coefficient(element, count=3) + objective_jacobian = replace(objective_jacobian, {objective.coefficients()[0]: coeff}) + if len(adjoint_jacform.coefficients()) > 0: + adjoint_jacform = replace(adjoint_jacform, {adjoint_jacform.coefficients()[0]: coeff}) + + # Residual of the adjoint form + adjoint_form = action(adjoint_jacform, original_form.coefficients()[0]) + adjoint_form = adjoint_form + objective_jacobian + + # Update form and original_form + original_form = adjoint_form + form = preprocess_form(adjoint_form).preprocessed_form + + elif get_form_option("control"): + # Generate control operator + # + # This is the normal form derived w.r.t. the control + # variable. + assert get_form_option("control_variable") is not None + controls = [data.object_by_name[ctrl.strip()] for ctrl in get_form_option("control_variable").split(",")] + assert len(controls) == 1 + + # We need to transform numpy ints to python native ints + def _unravel(flat_index, shape): + multi_index = np.unravel_index(flat_index, shape) + multi_index = tuple(int(i) for i in multi_index) + return multi_index + + forms = [] + for control in controls: + shape = control.ufl_shape + flat_length = np.prod(shape) + for i in range(flat_length): + c = control[_unravel(i, shape)] + control_form = diff(original_form, control) + forms.append(preprocess_form(control_form).preprocessed_form) + + # Used to create local operator default settings + form = preprocess_form(original_form).preprocessed_form + + # control = data.object_by_name[get_form_option("control_variable")] + # assert control.ufl_shape is () + + # from ufl import diff, replace + # from ufl.classes import Coefficient + # control_form = diff(original_form, control) + # # element = control_form.coefficients()[0].ufl_element() + # # coeff = Coefficient(element, count=3) + # # control_form = replace(control_form, {control_form.coefficients()[0]: coeff}) + # original_form = control_form + # form = preprocess_form(control_form).preprocessed_form + + else: + form = preprocess_form(original_form).preprocessed_form + + + # Reset the generation cache + from dune.perftool.generation import delete_cache_items + delete_cache_items() + + # Have a data structure collect the generated kernels + operator_kernels = {} + + # Generate things needed for all local operator files + local_operator_default_settings(operator, form) + + if get_form_option("control"): + logger.info("generate_localoperator_kernels: create methods for control operator") + operator_kernels.update(generate_control_kernels(forms)) + else: + logger.info("generate_localoperator_kernels: create residual methods") + operator_kernels.update(generate_residual_kernels(form)) + + # Generate the necessary jacobian methods + if not get_form_option("numerical_jacobian"): + logger.info("generate_localoperator_kernels: create jacobian methods") + operator_kernels.update(generate_jacobian_kernels(form, original_form)) # Return the set of generated kernels return operator_kernels