diff --git a/cmake/modules/DunePerftoolMacros.cmake b/cmake/modules/DunePerftoolMacros.cmake index 0b9e63eb51ed57166004e16501fece7411c997ed..071901bbbbf327d50665085512ad5435438596fd 100644 --- a/cmake/modules/DunePerftoolMacros.cmake +++ b/cmake/modules/DunePerftoolMacros.cmake @@ -114,7 +114,7 @@ function(add_generated_executable) --operator-file ${GEN_OPERATOR} --driver-file ${GEN_DRIVER} ${GEN_FORM_COMPILER_ARGS} - ${GEN_UFLFILE} + --uflfile ${GEN_UFLFILE} DEPENDS ${GEN_UFLFILE} ${UFL2PDELAB_SOURCES} ${GEN_DEPENDS} COMMENT "Running ufl2pdelab for the target ${GEN_TARGET}" ) diff --git a/python/dune/perftool/compile.py b/python/dune/perftool/compile.py index d5427fa6fe15f98d10ab0e3e491abc0ae7697432..d2639ac6934b10a936711744c515890eba2d0a02 100644 --- a/python/dune/perftool/compile.py +++ b/python/dune/perftool/compile.py @@ -14,7 +14,7 @@ from dune.perftool.generation import (delete_cache_items, global_context, ) from dune.perftool.interactive import start_interactive_session -from dune.perftool.options import get_option +from dune.perftool.options import get_option, initialize_options from dune.perftool.pdelab.driver import generate_driver from dune.perftool.pdelab.localoperator import (generate_localoperator_basefile, generate_localoperator_file, @@ -102,6 +102,7 @@ def read_ufl(uflfile): # This function is the entrypoint of the ufl2pdelab executable def compile_form(): + initialize_options() formdatas, data = read_ufl(get_option("uflfile")) with global_context(data=data, formdatas=formdatas): diff --git a/python/dune/perftool/options.py b/python/dune/perftool/options.py index ded7826e994cd51a017415825775f7a5d5d8ebcd..a98082347a45edafb2edd5fa09f4cb4a9c3c7ae3 100644 --- a/python/dune/perftool/options.py +++ b/python/dune/perftool/options.py @@ -1,96 +1,98 @@ """ Manage the command line options to the form compiler executable """ from argparse import ArgumentParser -from os import path -from pytools import memoize +from os.path import abspath +from pytools import ImmutableRecord from dune.common.parametertree.parser import parse_ini_file -@memoize -def get_form_compiler_arguments(): - # define an argument parser. - parser = ArgumentParser(description="Compile UFL files to PDELab C++ code", epilog="Please report bugs to dominic.kempf@iwr.uni-heidelberg.de") - parser.add_argument("uflfile", type=str, nargs=1, help="the UFL file to compile") - parser.add_argument("--driver-file", type=str, help="The filename for the generated driver header") - parser.add_argument("--operator-file", type=str, help="The filename for the generated local operator header") +class PerftoolOption(ImmutableRecord): + """ Data structure representing a single formcompiler option """ + def __init__(self, + default=None, + helpstr="Undocumented feature!", + process=lambda x: x, + type=type(None), + ): + ImmutableRecord.__init__(self, + helpstr=helpstr, + default=default, + process=process, + type=type, + ) + + +class PerftoolOptionsArray(ImmutableRecord): + """ A collection of form compiler arguments """ + def __init__(self, **kwargs): + opts = {k: v.default for k, v in PerftoolOptionsArray.__dict__.items() if isinstance(v, PerftoolOption)} + opts.update(kwargs) + ImmutableRecord.__init__(self, **opts) + + uflfile = PerftoolOption(helpstr="the UFL file to compile", process=abspath) + driver_file = PerftoolOption(helpstr="The filename for the generated driver header", process=abspath) + operator_file = PerftoolOption(helpstr="The filename for the generated local operator header", process=abspath) + numerical_jacobian = PerftoolOption(default=False, helpstr="use numerical jacobians (only makes sense, if uflpdelab for some reason fails to generate analytic jacobians)") + matrix_free = PerftoolOption(default=False, helpstr="Use iterative solver with matrix free jacobian application") + explicit_time_stepping = PerftoolOption(default=False, helpstr="use explicit time stepping") + exact_solution_expression = PerftoolOption(helpstr="name of the exact solution expression in the ufl file") + compare_dofs = PerftoolOption(helpstr="maximal allowed maximum error of difference between degrees of freedom vectors of numerical solution and interpolation of exact solution (NOTE: requires --exact-solution-expression)") + compare_l2errorsquared = PerftoolOption(helpstr="maximal allowed l2 error squared of difference between numerical solution and interpolation of exact solution (NOTE: requires --exact-solution-expression)") + interactive = PerftoolOption(default=False, helpstr="whether the optimization process should be guided interactively (also useful for debugging)") + print_transformations = PerftoolOption(default=False, helpstr="print out dot files after ufl tree transformations") + print_transformations_dir = PerftoolOption(default=".", helpstr="place where to put dot files (can be omitted)", process=abspath) + quadrature_order = PerftoolOption(type=int, helpstr="Quadrature order used for all integrals.") + diagonal_transformation_matrix = PerftoolOption(default=False, helpstr="set option if the jacobian of the transformation is diagonal (axiparallel grids)") + constant_transformation_matrix = PerftoolOption(default=False, helpstr="set option if the jacobian of the transformation is constant on a cell") + ini_file = PerftoolOption(helpstr="An inifile to use. A generated driver will be hard-coded to it, a [formcompiler] section will be used as default values to form compiler arguments (use snake case)", process=abspath) + opcounter = PerftoolOption(default=False, helpstr="Count operations. Note: In this case only oparor applications are generated since solving and operator counting does not work. You probably want to set instrumentation level>0.") + time_opcounter = PerftoolOption(default=False, helpstr="Generate opcounter codepath. Can be used for timing opcounter programs without setting the opcounter option.") + instrumentation_level = PerftoolOption(default=0, helpstr="Control time/opcounter measurements. 0-do nothing, 1-measure program as a whole, 2-operator applications, 3-measure kernel (eg. alpha-volume, ...), 4-parts of kernel (eg. stage 1-3 of SF)") + project_basedir = PerftoolOption(helpstr="The base (build) directory of the dune-perftool project", process=abspath) + fastdg = PerftoolOption(default=False, helpstr="Use FastDGGridOperator from PDELab.") + sumfact = PerftoolOption(default=False, helpstr="Use sumfactorization") + vectorize_quad = PerftoolOption(default=False, helpstr="whether to generate code with explicit vectorization") + vectorize_grads = PerftoolOption(default=False, helpstr="whether to generate code with explicit vectorization") + turn_off_diagonal_jacobian = PerftoolOption(default=False, helpstr="Do not use diagonal_jacobian transformation on the ufl tree and cast result of jacobianInverseTransposed into a FieldMatrix.") + + +# Until more sophisticated logic is needed, we keep the actual option data in this module +_options = PerftoolOptionsArray() + + +def initialize_options(): + """ This should be called in entrypoint methods to correctly set up an options array """ + global _options + _options = update_options_from_commandline(_options) + _options = update_options_from_inifile(_options) + + +def update_options_from_commandline(opt): + """ Return an options array object with updated values from the commandline """ + assert isinstance(opt, PerftoolOptionsArray) + parser = ArgumentParser(description="Compile UFL files to PDELab C++ code", + epilog="Please report bugs to dominic.kempf@iwr.uni-heidelberg.de", + ) parser.add_argument('--version', action='version', version='%(prog)s 0.1') - parser.add_argument("--numerical-jacobian", action="store_true", help="use numerical jacobians (only makes sense, if uflpdelab for some reason fails to generate analytic jacobians)") - parser.add_argument("--matrix-free", action="store_true", help="use iterative solver with matrix free jacobian application") - parser.add_argument("--explicit-time-stepping", action="store_true", help="use explicit time stepping") - parser.add_argument( - "--exact-solution-expression", - type=str, - help="name of the exact solution expression in the ufl file") - parser.add_argument( - "--compare-dofs", - type=str, - help="maximal allowed maximum error of difference between degrees of freedom vectors of numerical solution and interpolation of exact solution (NOTE: requires --exact-solution-expression)" - ) - parser.add_argument( - "--compare-l2errorsquared", - type=str, - help="maximal allowed l2 error squared of difference between numerical solution and interpolation of exact solution (NOTE: requires --exact-solution-expression)" - ) - parser.add_argument("--interactive", action="store_true", help="whether the optimization process should be guided interactively (also useful for debugging)") - parser.add_argument("--print-transformations", action="store_true", help="print out dot files after ufl tree transformations") - parser.add_argument("--print-transformations-dir", type=str, help="place where to put dot files (can be omitted)") - parser.add_argument("--quadrature-order", type=int, help="Quadrature order used for all integrals.") - parser.add_argument("--diagonal-transformation-matrix", action="store_true", help="set option if the jacobian of the transformation is diagonal (axiparallel grids)") - parser.add_argument("--constant-transformation-matrix", action="store_true", help="set option if the jacobian of the transformation is constant on a cell") - parser.add_argument("--ini-file", type=str, help="An inifile to use. A generated driver will be hard-coded to it, a [formcompiler] section will be used as default values to form compiler arguments (use snake case)") - parser.add_argument("--opcounter", action="store_true", help="Count operations. Note: In this case only oparor applications are generated since solving and operator counting does not work. You probably want to set instrumentation level>0.") - parser.add_argument("--time-opcounter", action="store_true", help="Generate opcounter codepath. Can be used for timing opcounter programs without setting the opcounter option.") - parser.add_argument("--instrumentation-level", type=int, default=0, help="Control time/opcounter measurements. 0-do nothing, 1-measure program as a whole, 2-operator applications, 3-measure kernel (eg. alpha-volume, ...), 4-parts of kernel (eg. stage 1-3 of SF)") - parser.add_argument("--project-basedir", type=str, help="The base (build) directory of the dune-perftool project") - parser.add_argument("--fastdg", action="store_true", help="Use FastDGGridOperator from PDELab.") - # TODO at some point this help description should be updated - parser.add_argument("--sumfact", action="store_true", help="Use sumfactorization") - parser.add_argument("--vectorize-quad", action="store_true", help="whether to generate code with explicit vectorization") - parser.add_argument("--vectorize-grads", action="store_true", help="whether to generate code with explicit vectorization") - parser.add_argument("--turn-off-diagonal-jacobian", action="store_true", help="Do not use diagonal_jacobian transformation on the ufl tree and cast result of jacobianInverseTransposed into a FieldMatrix.") - - # Modify the positional argument to not be a list - args = vars(parser.parse_args()) - - # Delistify the uflfile parameter - args["uflfile"] = args["uflfile"][0] - - # Turn any relative paths into absolute ones for consistency - if args["driver_file"]: - args["driver_file"] = path.abspath(args["driver_file"]) - if args["operator_file"]: - args["operator_file"] = path.abspath(args["operator_file"]) - if args["ini_file"]: - args["ini_file"] = path.abspath(args["ini_file"]) - - # Return the argument dict. This result is memoized to turn all get_option calls into simple dict lookups. - return args - - -_option_dict = {} -_arguments_read = False - - -def init_option_dict(): - """Add form compile arguments to options dict""" - global _arguments_read - if not _arguments_read: - _option_dict.update(get_form_compiler_arguments()) - _arguments_read = True - - # Read arguments from the given inifile - if _option_dict["ini_file"]: - inifile = parse_ini_file(_option_dict["ini_file"]) - for key, value in inifile.get("formcompiler", {}).items(): - if key not in _option_dict or _option_dict[key] is None: - _option_dict[key] = value - elif not _option_dict[key]: - # As `bool("0")` is True, we need to eval bools - if isinstance(_option_dict[key], bool): - _option_dict[key] = eval(value) - else: - _option_dict[key] = type(_option_dict[key])(value) + for k, v in type(opt).__dict__.items(): + if isinstance(v, PerftoolOption): + cmdopt = "--{}".format(k.replace('_', '-')) + _type = v.type + if _type is type(None): + _type = type(v.default) + if _type is type(None): + _type = str + parser.add_argument(cmdopt, help=v.helpstr, type=_type) + parsedargs = {k: v for k, v in vars(parser.parse_args()).items() if v is not None} + return opt.copy(**parsedargs) + + +def update_options_from_inifile(opt): + """ Return an options array object with updated values from an inifile """ + if opt.ini_file: + opt = opt.copy(**parse_ini_file(opt.ini_file).get("formcompiler", {})) + return opt def set_option(key, value): @@ -101,22 +103,12 @@ def set_option(key, value): any other options. """ - # Make sure form compile arguments were read - init_option_dict() + global _options + _options = _options.copy(key=value) - _option_dict.update({key: value}) - -def get_option(key, default=None): - try: - __IPYTHON__ - return default - except: - """Return the value corresponding to key from option dictionary""" - # Make sure form compile arguments were read - init_option_dict() - - return _option_dict.get(key, default) +def get_option(key): + return getattr(_options, key) def option_switch(opt): diff --git a/python/dune/perftool/ufl/transformations/__init__.py b/python/dune/perftool/ufl/transformations/__init__.py index d4bd87f862e7ca12611daea6a6cdb4523b0f287f..3ffb9b7ff8af4efec7032568d5379ea689cc094d 100644 --- a/python/dune/perftool/ufl/transformations/__init__.py +++ b/python/dune/perftool/ufl/transformations/__init__.py @@ -20,9 +20,9 @@ class UFLTransformationWrapper(object): # Write out a dot file from dune.perftool.options import get_option - if get_option("print_transformations", False): + if get_option("print_transformations"): import os - dir = get_option("print_transformations_dir", os.getcwd()) + dir = get_option("print_transformations_dir") for i, exprtowrite in enumerate(expr): filename = "trafo_{}_{}_{}{}.dot".format(self.name, str(self.counter).zfill(4), "in" if before else "out", "_{}".format(i) if len(expr) > 1 else "")