Skip to content
Snippets Groups Projects
options.py 9.14 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
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 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


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] = _scheme_type_to_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] = _scheme_type_to_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_diagonal or opt.block_preconditioner_offdiagonal:
        assert opt.numerical_jacobian is False
        opt = opt.copy(generate_residuals=False,
                       generate_jacobians=False,
                       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)


def option_switch(opt):
    def _switch():
        if isinstance(opt, tuple):
            opts = opt
        else:
            assert isinstance(opt, str)
            opts = (opt,)
        try:
            for o in opts:
                if get_option(o):
                    return o
            return "default"
        except AttributeError:
            for o in opts:
                if get_form_option(o):
                    return o
            return "default"
    return _switch