Skip to content
Snippets Groups Projects
options.py 10.7 KiB
Newer Older
Dominic Kempf's avatar
Dominic Kempf committed
""" Manage the command line options to the form compiler executable """

from argparse import ArgumentParser
from os.path import abspath
from pytools import ImmutableRecord, memoize
import cerberus
import yaml
import pkg_resources
from six.moves import configparser
from six import StringIO
from contextlib import contextmanager
class CodegenOptionsValidator(cerberus.Validator):
    # A validator that accepts the helpstr field in the scheme
    def _validate_helpstr(self, helpstr, field, value):
        """ Describe the option

        The rule's arguments are validated against this schema:
        {'type': 'string'}
        """
        return True


def _load_scheme(form=False):
    resource_package = __name__
    if form:
        resource_path = 'options_form.yaml'
    else:
        resource_path = 'options_global.yaml'
    yaml_stream = pkg_resources.resource_string(resource_package, resource_path)
    try:
        scheme = yaml.safe_load(yaml_stream)
    except Exception as e:
        raise e
    return scheme
Dominic Kempf's avatar
Dominic Kempf committed


class CodegenGlobalOptionsArray(ImmutableRecord):
    """ A collection of form compiler arguments """
    def __init__(self, **kwargs):
        # Set the default values from the yaml scheme as defaults
        scheme = _load_scheme()
        opts = {k: v['default'] for k, v in scheme.items()}
Dominic Kempf's avatar
Dominic Kempf committed
        opts.update(**kwargs)
        ImmutableRecord.__init__(self, **opts)


class CodegenFormOptionsArray(ImmutableRecord):
    """ A collection of form-specific form compiler arguments """
    def __init__(self, **kwargs):
        # Set the default values from the yaml scheme as defaults
        scheme = _load_scheme(form=True)
        opts = {k: v['default'] for k, v in scheme.items()}
Dominic Kempf's avatar
Dominic Kempf committed
        opts.update(**kwargs)
        ImmutableRecord.__init__(self, **opts)


# Until more sophisticated logic is needed, we keep the actual option data in this module
_global_options = CodegenGlobalOptionsArray()
_form_options = {}


def show_options():
    # TODO: This needs to be adjusted to options-validation
    def subopt(arr):
        for k, v in arr.__dict__.items():
            if isinstance(v, CodegenOption) and v.helpstr is not None:
                print("{}\n    {}".format(k, v.helpstr))

    print("This is a summary of options available for the code generation process:\n")
    print("The following options can be given in the [formcompiler] section:")
    subopt(CodegenGlobalOptionsArray)

    print("\nThefollowing options can be given in a form-specific subsection of [formcompiler]:")
    subopt(CodegenFormOptionsArray)


Dominic Kempf's avatar
Dominic Kempf committed
def initialize_options():
    """ Initialize the options from the command line """
    global _global_options
    _global_options = update_options_from_commandline(_global_options)
    _global_options = update_options_from_inifile(_global_options)

    # Validate global options
    scheme_global = _load_scheme()
    validator_global = CodegenOptionsValidator(scheme_global, require_all=True)
    if not validator_global.validate(_global_options.__dict__):
        raise RuntimeError("Global options validation failed: {}".format(validator_global.errors))

    # Validate form options
    scheme_form = _load_scheme(form=True)
    validator_form = CodegenOptionsValidator(scheme_form, require_all=True)
    for form in [i.strip() for i in _global_options.operators.split(",")]:
        if not validator_form.validate(_form_options[form].__dict__):
            raise RuntimeError("Form options validation failed: {}".format(validator_form.errors))

Dominic Kempf's avatar
Dominic Kempf committed

def _scheme_type_to_type(scheme_type):
    assert isinstance(scheme_type, str)
    if scheme_type == 'string':
        return str
    if scheme_type == 'boolean':
        return bool
    if scheme_type == 'integer':
        return int
    if scheme_type == 'float':
        return float


def _transform_type(scheme_type, a):
    if scheme_type == 'boolean':
        return bool(int(a))
    else:
        return _scheme_type_to_type(scheme_type)(a)


Dominic Kempf's avatar
Dominic Kempf committed
def update_options_from_commandline(opt):
    """ Return an options array object with updated values from the commandline """
    assert isinstance(opt, CodegenGlobalOptionsArray)
    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')

    # Load global options scheme
    scheme = _load_scheme()

    # Add all options that have a helpstr to the command line parser
    for k, v in scheme.items():
        if v['helpstr'] is not None:
Dominic Kempf's avatar
Dominic Kempf committed
            cmdopt = "--{}".format(k.replace('_', '-'))
            parser.add_argument(cmdopt, help=v['helpstr'], type=_scheme_type_to_type(v['type']))
Dominic Kempf's avatar
Dominic Kempf committed
    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:
        config = configparser.ConfigParser()
Dominic Kempf's avatar
Dominic Kempf committed

        # Read ini file
        try:
            config.read(opt.ini_file)
        except configparser.MissingSectionHeaderError:
            # Config parser doesn't like ini files where without section. For
            # this case we introduce a [root] section on top.
            ini_str = '[root]\n' + open(opt.ini_file, 'r').read()
            ini_fp = StringIO(ini_str)
            config = configparser.RawConfigParser()
            config.readfp(ini_fp)
        # Parse global options
        scheme = _load_scheme()
        options = dict(config.items('formcompiler'))
        for k, v in options.items():
            assert k in scheme
            options[k] = _transform_type(scheme[k]['type'], v)
        opt = opt.copy(**options)

        # Parse form options
        scheme = _load_scheme(form=True)
Dominic Kempf's avatar
Dominic Kempf committed
        for form in [i.strip() for i in opt.operators.split(",")]:
            section = 'formcompiler.{}'.format(form)
            options = {}
            if config.has_section(section):
                options = dict(config.items('formcompiler.{}'.format(form)))
                for k, v in options.items():
                    assert k in scheme
                    options[k] = _transform_type(scheme[k]['type'], v)
            _form_options[form] = CodegenFormOptionsArray(**options)
Dominic Kempf's avatar
Dominic Kempf committed

    return opt


@memoize
def process_global_options(opt):
    """ Make sure that the options have been fully processed """
    opt = expand_architecture_options(opt)

    if opt.overlapping:
        opt = opt.copy(parallel=True)

    return opt


@memoize
def process_form_options(opt, form):
    if opt.sumfact:
        opt = opt.copy(unroll_dimension_loops=True,
                       quadrature_mixins="sumfact",
                       basis_mixins="sumfact",
Dominic Kempf's avatar
Dominic Kempf committed
                       accumulation_mixins="sumfact",
Dominic Kempf's avatar
Dominic Kempf committed

    if opt.blockstructured:
        opt = opt.copy(accumulation_mixins="blockstructured",
                       quadrature_mixins="blockstructured",
Marcel Koch's avatar
Marcel Koch committed
                       basis_mixins="blockstructured"
    if opt.control:
        opt = opt.copy(accumulation_mixins="control")

Dominic Kempf's avatar
Dominic Kempf committed
    if opt.numerical_jacobian:
        opt = opt.copy(generate_jacobians=False, generate_jacobian_apply=False)

René Heß's avatar
René Heß committed
    if opt.form is None:
Dominic Kempf's avatar
Dominic Kempf committed
        opt = opt.copy(form=form)

René Heß's avatar
René Heß committed
    if opt.classname is None:
Dominic Kempf's avatar
Dominic Kempf committed
        opt = opt.copy(classname="{}Operator".format(form))

René Heß's avatar
René Heß committed
    if opt.filename is None:
Dominic Kempf's avatar
Dominic Kempf committed
        opt = opt.copy(filename="{}_{}_file.hh".format(get_option("target_name"), opt.classname))

    if opt.block_preconditioner_pointdiagonal:
Dominic Kempf's avatar
Dominic Kempf committed
        opt = opt.copy(generate_jacobians=False,
                       basis_mixins="sumfact_pointdiagonal",
                       accumulation_mixins="sumfact_pointdiagonal",
                       )

Dominic Kempf's avatar
Dominic Kempf committed
    if opt.block_preconditioner_diagonal or opt.block_preconditioner_offdiagonal:
        assert opt.numerical_jacobian is False
        opt = opt.copy(generate_residuals=False,
Dominic Kempf's avatar
Dominic Kempf committed
                       matrix_free=True,
                       )

    if opt.matrix_free:
        opt = opt.copy(generate_jacobian_apply=True)

    return opt


def expand_architecture_options(opt):
    if opt.architecture == "haswell":
        return opt.copy(max_vector_width=256)
    elif opt.architecture == "knl":
        return opt.copy(max_vector_width=512)
    elif opt.architecture == "skylake":
        return opt.copy(max_vector_width=512)
    else:
        raise NotImplementedError("Architecture {} not known!".format(opt.architecture))


def set_option(key, value):
    """Add the key value pair to the options.

    If the key is already in the options dictionary its value will be
    overwritten.  Form compiler arguments will always be set before
    any other options.
    """
    global _global_options
    _global_options = process_global_options(_global_options).copy(**{key: value})


def set_form_option(key, value, form=None):
    if form is None:
        from dune.codegen.generation import get_global_context_value
        form = get_global_context_value("form_identifier", 0)
    if isinstance(form, int):
        form = get_option("operators").split(",")[form].strip()
    _form_options[form] = _form_options[form].copy(**{key: value})


def get_option(key):
    processed_global_opts = process_global_options(_global_options)
    return getattr(processed_global_opts, key)


def get_form_option(key, form=None):
    if form is None:
        from dune.codegen.generation import get_global_context_value
        form = get_global_context_value("form_identifier", 0)
    if isinstance(form, int):
        form = get_option("operators").split(",")[form].strip()
    processed_form_opts = process_form_options(_form_options[form], form)
    return getattr(processed_form_opts, key)


@contextmanager
def option_context(conditional=True, **opts):
    """ A context manager that sets a given option and restores it on exit. """
    # Backup old values and set to new ones
    if conditional:
        backup = {}
        for k, v in opts.items():
            backup[k] = get_option(k)
            set_option(k, v)

    yield

    if conditional:
        # Restore old values
        for k in opts.keys():
            set_option(k, backup[k])


@contextmanager
def form_option_context(conditional=True, **opts):
    """ A context manager that sets a given form option and restores it on exit """
    if conditional:
        form = opts.pop("form", None)

        # Backup old values and set to new ones
        backup = {}
        for k, v in opts.items():
            backup[k] = get_form_option(k, form=form)
            set_form_option(k, v, form=form)

    yield

    # Restore old values
    if conditional:
        for k in opts.keys():
            set_form_option(k, backup[k], form=form)