Source code for ufl.corealg.multifunction

"""Base class for multifunctions with UFL ``Expr`` type dispatch."""
# Copyright (C) 2008-2016 Martin Sandve Alnæs
#
# This file is part of UFL (https://www.fenicsproject.org)
#
# SPDX-License-Identifier:    LGPL-3.0-or-later
#
# Modified by Massimiliano Leoni, 2016

import inspect

from ufl.core.expr import Expr
from ufl.core.ufl_type import UFLType


[docs]def get_num_args(function): """Return the number of arguments accepted by *function*.""" sig = inspect.signature(function) return len(sig.parameters) + 1
[docs]def memoized_handler(handler): """Function decorator to memoize ``MultiFunction`` handlers.""" def _memoized_handler(self, o): c = getattr(self, "_memoized_handler_cache") r = c.get(o) if r is None: r = handler(self, o) c[o] = r return r return _memoized_handler
[docs]class MultiFunction(object): """Base class for collections of non-recursive expression node handlers. Subclass this (remember to call the ``__init__`` method of this class), and implement handler functions for each ``Expr`` type, using the lower case handler name of the type (``exprtype._ufl_handler_name_``). This class is optimized for efficient type based dispatch in the ``__call__`` operator via typecode based lookup of the handler function bound to the algorithm object. Of course Python's function call overhead still applies. """ _handlers_cache = {} def __init__(self): """Initialise.""" # Analyse class properties and cache handler data the # first time this is run for a particular class # (cached for each algorithm for performance) algorithm_class = type(self) cache_data = MultiFunction._handlers_cache.get(algorithm_class) if not cache_data: handler_names = [None] * len(Expr._ufl_all_classes_) # Iterate over the inheritance chain for each Expr # subclass (NB! This assumes that all UFL classes inherits # from a single Expr subclass and that the first # superclass is always from the UFL Expr hierarchy!) for classobject in Expr._ufl_all_classes_: for c in classobject.mro(): # Register classobject with handler for the first # encountered superclass try: handler_name = c._ufl_handler_name_ except AttributeError as attribute_error: if type(classobject) is not UFLType: raise attribute_error # Default handler name for UFL types handler_name = UFLType._ufl_handler_name_ if hasattr(self, handler_name): handler_names[classobject._ufl_typecode_] = handler_name break is_cutoff_type = [get_num_args(getattr(self, name)) == 2 for name in handler_names] cache_data = (handler_names, is_cutoff_type) MultiFunction._handlers_cache[algorithm_class] = cache_data # Build handler list for this particular class (get functions # bound to self, these cannot be cached) handler_names, is_cutoff_type = cache_data self._handlers = [getattr(self, name) for name in handler_names] self._is_cutoff_type = is_cutoff_type # Create cache for memoized_handler self._memoized_handler_cache = {} def __call__(self, o, *args): """Delegate to handler function based on typecode of first argument.""" return self._handlers[o._ufl_typecode_](o, *args)
[docs] def undefined(self, o, *args): """Trigger error for types with missing handlers.""" raise ValueError(f"No handler defined for {o._ufl_class_.__name__}.")
[docs] def reuse_if_untouched(self, o, *ops): """Reuse object if operands are the same objects. Use in your own subclass by setting e.g. :: expr = MultiFunction.reuse_if_untouched as a default rule. """ if all(a is b for a, b in zip(o.ufl_operands, ops)): return o else: return o._ufl_expr_reconstruct_(*ops)
# Set default behaviour for any UFLType as undefined ufl_type = undefined