diff --git a/python/dune/perftool/compile.py b/python/dune/perftool/compile.py index f77f9d308233cd1742eb988e7027e529a3ef479e..dd4b7b2641dbc87d7b91d63ec7df77af00571fc5 100644 --- a/python/dune/perftool/compile.py +++ b/python/dune/perftool/compile.py @@ -81,14 +81,15 @@ def compile_form(): from dune.perftool.options import get_option formdata, namedata = read_ufl(get_option("uflfile")) - from dune.perftool.generation import cache_context - if get_option("driver_file"): - with cache_context('driver', delete=True): - generate_driver(formdata.preprocessed_form, get_option("driver_file")) - - if get_option("operator_file"): - from dune.perftool.pdelab.localoperator import generate_localoperator_kernels - kernels = generate_localoperator_kernels(formdata, namedata) + from dune.perftool.generation import cache_context, global_context + with global_context(formdata=formdata, namedata=namedata): + if get_option("driver_file"): + with cache_context('driver', delete=True): + generate_driver(formdata.preprocessed_form, get_option("driver_file")) + + if get_option("operator_file"): + from dune.perftool.pdelab.localoperator import generate_localoperator_kernels + kernels = generate_localoperator_kernels(formdata, namedata) # TODO insert sophisticated analysis/feedback loops here if get_option("interactive"): diff --git a/python/dune/perftool/loopy/transformer.py b/python/dune/perftool/loopy/transformer.py index f043f5f1be99cb81cdc66615d40ffd62bcecd957..4f5ad837457446814048704de4fa808b735510d2 100644 --- a/python/dune/perftool/loopy/transformer.py +++ b/python/dune/perftool/loopy/transformer.py @@ -177,6 +177,7 @@ class UFL2LoopyVisitor(ModifiedTerminalTracker, UFL2PymbolicMapper, GeometryMapp from dune.perftool.pdelab.basis import lfs_child lfs = lfs_child(lfs, dimension_iname(count=count, context='arg')) residual_shape[icount] = name_lfs_bound(name_leaf_lfs(subel.sub_elements()[0], ma.restriction)) + subel = subel.sub_elements()[0] else: residual_shape[icount] = name_lfs_bound(lfs) @@ -251,9 +252,6 @@ class UFL2LoopyVisitor(ModifiedTerminalTracker, UFL2PymbolicMapper, GeometryMapp element = select_subelement(o.ufl_element(), self.component) leaf_element = element - # Have the issued instruction depend on the iname for this localfunction space - self.inames.append(LFSIndexPlaceholder(element, restriction, o.number())) - # Now treat the case of this being a vector finite element if element.num_sub_elements() > 0: # I cannot handle general mixed elements here... @@ -272,12 +270,15 @@ class UFL2LoopyVisitor(ModifiedTerminalTracker, UFL2PymbolicMapper, GeometryMapp # For the purpose of basis evaluation, we need to take the leaf element leaf_element = element.sub_elements()[0] + # Have the issued instruction depend on the iname for this localfunction space + self.inames.append(LFSIndexPlaceholder(leaf_element, restriction, o.number())) + if self.grad: from dune.perftool.pdelab.argument import name_testfunction_gradient - return Subscript(Variable(name_testfunction_gradient(leaf_element, restriction)), (LFSIndexPlaceholder(element, restriction, o.number()),)) + return Subscript(Variable(name_testfunction_gradient(leaf_element, restriction)), (LFSIndexPlaceholder(leaf_element, restriction, o.number()),)) else: from dune.perftool.pdelab.argument import name_testfunction - return Subscript(Variable(name_testfunction(leaf_element, restriction)), (LFSIndexPlaceholder(element, restriction, o.number()),)) + return Subscript(Variable(name_testfunction(leaf_element, restriction)), (LFSIndexPlaceholder(leaf_element, restriction, o.number()),)) def coefficient(self, o): # If this is a trialfunction, we do something entirely different diff --git a/python/dune/perftool/pdelab/driver.py b/python/dune/perftool/pdelab/driver.py index 861784c6d5f73509ea2fe3df47c2b3008772eceb..222a67548d3ba0d649f157ea29d4694bd244e6f7 100644 --- a/python/dune/perftool/pdelab/driver.py +++ b/python/dune/perftool/pdelab/driver.py @@ -13,6 +13,13 @@ from dune.perftool.generation import (generator_factory, ) +def has_constraints(element): + from ufl import MixedElement, VectorElement + if isinstance(element, MixedElement) and not isinstance(element, VectorElement): + return any(has_constraints(el) for el in element.sub_elements()) + return hasattr(element, "dirichlet_constraints") + + # Those symbol generators that depend on the meta data we hacked into # the ufl.FiniteElement should use fem_metadata_dependent_symbol instead of symbol. def get_constraints_metadata(element): @@ -353,15 +360,18 @@ def define_constraintscontainer(expr, name): return ["{} {};".format(cctype, name), "{}.clear();".format(name)] -@symbol -def name_constraintscontainer(element, dirichlet): - name = "{}{}_cc".format(FEM_name_mangling(element), "_dirichlet" if dirichlet else "").lower() - define_constraintscontainer(element, name) +# TODO this does not yet fully support having same elements with different constraints! +@fem_metadata_dependent_symbol +def name_constraintscontainer(expr): + name = "{}{}_cc".format(FEM_name_mangling(expr), "_dirichlet" if has_constraints(expr) else "").lower() + define_constraintscontainer(expr, name) return name @preamble def define_intersection_lambda(expression, name): + if expression is None: + return "auto {} = [&](const auto& x){{ return 0; }};".format(name) if expression.is_global: return "auto {} = [&](const auto& x){{ {} }};".format(name, expression.c_expr) else: @@ -369,16 +379,16 @@ def define_intersection_lambda(expression, name): @symbol -def name_bctype_lambda(dirichlet): - define_intersection_lambda(dirichlet, "glambda") - # TODO get access to the name data dict here! - return "glambda" +def name_bctype_lambda(name, dirichlet): + name = name + "_lambda" + define_intersection_lambda(dirichlet, name) + return name @preamble def define_bctype_function(dirichlet, name): gv = name_leafview() - bctype_lambda = name_bctype_lambda(dirichlet) + bctype_lambda = name_bctype_lambda(name, dirichlet) include_file('dune/pdelab/function/callableadapter.hh', filetag='driver') return "auto {} = Dune::PDELab::makeBoundaryConditionFromCallable({}, {});".format(name, gv, @@ -386,30 +396,65 @@ def define_bctype_function(dirichlet, name): ) +@preamble +def define_powergfs_constraints(name, child, dim): + include_file('dune/pdelab/constraints/common/constraintsparameters.hh', filetag='driver') + return "Dune::PDELab::PowerConstraintsParameters<decltype({}), {}> {}({});".format(child, dim, name, child) + + +@preamble +def define_compositegfs_constraints(name, *children): + include_file('dune/pdelab/constraints/common/constraintsparameters.hh', filetag='driver') + return "Dune::PDELab::CompositeConstraintsParameters<{}> {}({});".format(', '.join('decltype({})'.format(c) for c in children), + name, + ', '.join(c for c in children) + ) + + @symbol -def name_bctype_function(dirichlet): - define_bctype_function(dirichlet, "g") - # TODO get access to the name data dict here! - return "g" +def name_bctype_function(expr): + from ufl import MixedElement, VectorElement + if isinstance(expr, VectorElement): + element, (dirichlet, _) = get_constraints_metadata(expr) + # get the correct name from the corresponding UFL file + from dune.perftool.generation import get_global_context_value + name = get_global_context_value("namedata").get(id(dirichlet), "everywhere") + define_bctype_function(dirichlet, name) + pgfs_name = '{}_{}'.format(name, expr.num_sub_elements()) + define_powergfs_constraints(pgfs_name, name, expr.num_sub_elements()) + return pgfs_name + if isinstance(expr, MixedElement): + children = tuple(name_bctype_function(el) for el in expr.sub_elements()) + name = '_'.join(children) + define_compositegfs_constraints(name, *children) + return name + + # So, this seems to be a leaf! + element, (dirichlet, _) = get_constraints_metadata(expr) + + # get the correct name from the corresponding UFL file + from dune.perftool.generation import get_global_context_value + name = get_global_context_value("namedata").get(id(dirichlet), "everywhere") + + define_bctype_function(dirichlet, name) + return name @preamble -def assemble_constraints(name, element, dirichlet): - assert dirichlet - bctype_function = name_bctype_function(dirichlet) - gfs = name_gfs(element) +def assemble_constraints(name, expr): + bctype_function = name_bctype_function(expr) + gfs = name_gfs(expr) return "Dune::PDELab::constraints({}, {}, {});".format(bctype_function, gfs, name, ) -@fem_metadata_dependent_symbol +@symbol def name_assembled_constraints(expr): - element, (dirichlet, _) = get_constraints_metadata(expr) - name = name_constraintscontainer(element, dirichlet) - if dirichlet: - assemble_constraints(name, element, dirichlet) + name = name_constraintscontainer(expr) + if has_constraints(expr): + assemble_constraints(name, expr) return name @@ -790,10 +835,18 @@ def print_matrix(): @preamble -def define_gfs_name(element): +def define_gfs_name(element, prefix=()): + from ufl import MixedElement, VectorElement + if isinstance(element, VectorElement): + gfs = name_gfs(element) + return "{}.name(\"data_{}\");".format(gfs, "_".join(str(p) for p in prefix)) + if isinstance(element, MixedElement): + for (i, el) in enumerate(element.sub_elements()): + define_gfs_name(el, prefix + (i,)) + return "" + # This is a scalar leaf gfs = name_gfs(element) - # TODO make something sensible here - return "{}.name(\"bla\");".format(gfs) + return "{}.name(\"data_{}\");".format(gfs, "_".join(str(p) for p in prefix)) @preamble diff --git a/python/dune/perftool/pdelab/localoperator.py b/python/dune/perftool/pdelab/localoperator.py index 439dcf84dee15bb02a9e06d8c376702ef5c81652..a7c28b06164912e5b1d00aaa9f15e458301e9d0c 100644 --- a/python/dune/perftool/pdelab/localoperator.py +++ b/python/dune/perftool/pdelab/localoperator.py @@ -276,62 +276,60 @@ def generate_localoperator_kernels(formdata, namedata): # Have a data structure collect the generated kernels operator_kernels = {} - from dune.perftool.generation import global_context - with global_context(formdata=formdata, namedata=namedata): - with global_context(form_type='residual'): - # Generate the necessary residual methods - for measure in set(i.integral_type() for i in form.integrals()): - with global_context(integral_type=measure): - # Reset the outer loop - from dune.perftool.loopy.transformer import set_outerloop - set_outerloop(None) - - enum_pattern() - pattern_baseclass() - enum_alpha() - kernel = generate_kernel(form.integrals_by_type(measure)) - - # Maybe add numerical differentiation - if get_option("numerical_jacobian"): - include_file("dune/pdelab/localoperator/defaultimp.hh", filetag="operatorfile") - - _, loptype = class_type_from_cache("operator") - which = ufl_measure_to_pdelab_measure(measure) - base_class("Dune::PDELab::NumericalJacobian{}<{}>".format(which, loptype), classtag="operator") - - # Add the initializer list for that base class - ini = name_initree_member() - ini_constructor = name_initree_constructor_param() - initializer_list("Dune::PDELab::NumericalJacobian{}<{}>".format(which, loptype), - ["{}.get<double>(\"numerical_epsilon.{}\", 1e-9)".format(ini_constructor, ini, which.lower())], - classtag="operator", - ) + with global_context(form_type='residual'): + # Generate the necessary residual methods + for measure in set(i.integral_type() for i in form.integrals()): + with global_context(integral_type=measure): + # Reset the outer loop + from dune.perftool.loopy.transformer import set_outerloop + set_outerloop(None) + + enum_pattern() + pattern_baseclass() + enum_alpha() + kernel = generate_kernel(form.integrals_by_type(measure)) + + # Maybe add numerical differentiation + if get_option("numerical_jacobian"): + include_file("dune/pdelab/localoperator/defaultimp.hh", filetag="operatorfile") + + _, loptype = class_type_from_cache("operator") + which = ufl_measure_to_pdelab_measure(measure) + base_class("Dune::PDELab::NumericalJacobian{}<{}>".format(which, loptype), classtag="operator") + + # Add the initializer list for that base class + ini = name_initree_member() + ini_constructor = name_initree_constructor_param() + initializer_list("Dune::PDELab::NumericalJacobian{}<{}>".format(which, loptype), + ["{}.get<double>(\"numerical_epsilon.{}\", 1e-9)".format(ini_constructor, ini, which.lower())], + classtag="operator", + ) operator_kernels[(measure, 'residual')] = kernel - # Generate the necessary jacobian methods - if not get_option("numerical_jacobian"): - from ufl import derivative - from ufl.algorithms import expand_derivatives - jacform = expand_derivatives(derivative(form, form.coefficients()[0])) - - with global_context(form_type="jacobian"): - for measure in set(i.integral_type() for i in jacform.integrals()): - # Reset the outer loop - from dune.perftool.loopy.transformer import set_outerloop - set_outerloop(None) - - with global_context(integral_type=measure): - kernel = generate_kernel(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: - operator_kernels[(it, 'jacobian')] = None + # Generate the necessary jacobian methods + if not get_option("numerical_jacobian"): + from ufl import derivative + from ufl.algorithms import expand_derivatives + jacform = expand_derivatives(derivative(form, form.coefficients()[0])) + + with global_context(form_type="jacobian"): + for measure in set(i.integral_type() for i in jacform.integrals()): + # Reset the outer loop + from dune.perftool.loopy.transformer import set_outerloop + set_outerloop(None) + + with global_context(integral_type=measure): + kernel = generate_kernel(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: + operator_kernels[(it, 'jacobian')] = None # TODO: JacobianApply for matrix-free computations. diff --git a/python/dune/perftool/ufl/execution.py b/python/dune/perftool/ufl/execution.py index 7b5d4a2b289cb8cfc72f2f39485b731b849c8c14..9faaa5e9eec073f621d1075dea29c97636164b7d 100644 --- a/python/dune/perftool/ufl/execution.py +++ b/python/dune/perftool/ufl/execution.py @@ -87,3 +87,22 @@ class FiniteElement(ufl.FiniteElement): # Initialize the original finite element from ufl ufl.FiniteElement.__init__(self, *args, **kwargs) + + +class VectorElement(ufl.VectorElement): + def __init__(self, *args, **kwargs): + if ('dirichlet_constraints' in kwargs) or ('dirichlet_expression' in kwargs): + # Get dirichlet_constraints and convert it to Expression if necessary! + self.dirichlet_constraints = kwargs.pop('dirichlet_constraints', 'return true;') + if isinstance(self.dirichlet_constraints, str): + self.dirichlet_constraints = Expression(self.dirichlet_constraints) + assert isinstance(self.dirichlet_constraints, Expression) + + # Get dirichlet_constraints and convert it to Expression if necessary! + self.dirichlet_expression = kwargs.pop('dirichlet_expression', 'return 0.0;') + if isinstance(self.dirichlet_expression, str): + self.dirichlet_expression = Expression(self.dirichlet_expression) + assert isinstance(self.dirichlet_expression, Expression) + + # Initialize the original finite element from ufl + ufl.VectorElement.__init__(self, *args, **kwargs) diff --git a/test/stokes/stokes.ufl b/test/stokes/stokes.ufl index 0af20a0836bae61ea56ab922e3b767e7f14713d3..e84868a14111d0bd4e3b587ecdc97a32577de9b7 100644 --- a/test/stokes/stokes.ufl +++ b/test/stokes/stokes.ufl @@ -1,5 +1,8 @@ +v_bctype = Expression("if (x[0] < 1e-8) return 1; else return 0;", on_intersection=True) +v_dirichlet = Expression("Dune::FieldVector<double, 2> y(0.0); y[0] = 4*x[1]*(1.-x[1]); return y;") + cell = triangle -P2 = VectorElement("Lagrange", cell, 2) +P2 = VectorElement("Lagrange", cell, 2, dirichlet_constraints=v_bctype, dirichlet_expression=v_dirichlet) P1 = FiniteElement("Lagrange", cell, 1) TH = P2 * P1