Source code for simudo.fem.function_space


from collections import OrderedDict

from cached_property import cached_property

import dolfin
import ufl

from ..util import SetattrInitMixin

__all__ = [
    'MixedFunctionSpace',
    'MixedFunctionHelper',
    'WithSubfunctionsMixin',
]

class MixedFunctionLikeMixin(object):
    def __init__(self, mixed_function_space, function=None):
        ''' pass `function` to use an existing dolfin function instead
        of allocating a new one '''
        self.mixed_function_space = mixed_function_space
        if function is not None:
            # TODO: check function space matches
            self.function = function

    @cached_property
    def function(self):
        return self._make_dolfin_function[0](
            self.mixed_function_space.function_space)

    def split(self):
        return self.mixed_function_space.split(self.function)

class MixedFunction(MixedFunctionLikeMixin):
    # the tuple is there to prevent methodification
    _make_dolfin_function = (dolfin.Function,)

class MixedTestFunction(MixedFunctionLikeMixin):
    _make_dolfin_function = (dolfin.TestFunction,)

[docs]class MixedFunctionSpace(object): '''Convenience class for dealing with mixed function spaces. ''' MixedFunction = MixedFunction MixedTestFunction = MixedTestFunction def __init__(self, mesh, subspace_descriptors, function_space_cache): ''' Parameters ---------- mesh: dolfin.Mesh subspace_descriptors: iterable List of dict with keys: element, trial_key, trial_units, test_key, test_units function_space_cache: FunctionSpaceCache Used to recycle existing FunctionSpace. ''' self.mesh = mesh self.subspace_descriptors = tuple(subspace_descriptors) self.function_space_cache = function_space_cache @cached_property def subspace_descriptors_dict(self): ''' Like `subspace_descriptors`, except it's a dictionary where the keys are "trial_key" as well as "test_key". ''' r = {} for desc in self.subspace_descriptors: r[desc['trial_key']] = desc r[desc['test_key' ]] = desc return r
[docs] def get_element(self): ''' use `element` property instead ''' descs = self.subspace_descriptors elements = [desc['element'] for desc in descs] n = len(elements) if n == 0: raise AssertionError() elif n == 1: return elements[0] else: return dolfin.MixedElement(elements)
[docs] def get_function_space(self): ''' use `function_space` property instead ''' return self.function_space_cache.FunctionSpace( self.mesh, self.element)
@cached_property def element(self): return self.get_element() @cached_property def function_space(self): return self.get_function_space() @cached_property def function_subspaces(self): descs = self.subspace_descriptors W = self.function_space keys = [desc['trial_key'] for desc in descs] n = len(keys) if n == 1: return {keys[0]: W} else: return {key: W.sub(i) for i, key in enumerate(keys)}
[docs] def make_function(self, **kwargs): return self.MixedFunction(self, **kwargs)
[docs] def make_test_function(self, **kwargs): return self.MixedTestFunction(self, **kwargs)
[docs] def split(self, dolfin_function, kind=None): ''' returns OrderedDict of (key: subfunction*unit) ''' if kind is None: if isinstance(dolfin_function, dolfin.Function): kind = 'trial' elif isinstance(dolfin_function, ufl.Argument): kind = 'test' else: raise TypeError( "failed to identify whether trial or test function") descs = self.subspace_descriptors n = len(descs) if n == 1: subfunctions = (dolfin_function,) else: subfunctions = dolfin.split(dolfin_function) return OrderedDict((inf[kind+'_key'], inf[kind+'_units']*sub) for (inf, sub) in zip(descs, subfunctions))
[docs]class MixedFunctionHelper(SetattrInitMixin): '''Utility mixin for objects that hold a mixed function make up of many subfunctions that need to be gathered from different objects, for example :py:class:`.poisson_drift_diffusion.PoissonDriftDiffusion`. Parameters ---------- mesh_util: :py:class:`.mesh_util.MeshUtil` Instance of MeshUtil. subspace_descriptors_for_solution_space: sequence of dict Sequence of subspace descriptor dicts. This property must contain the subspace descriptors that will be used to build the solution space. Typically one would gather these by calling :py:attr:`WithSubfunctionsMixin.solution_subspace_descriptors` on all the objects that contain subspace descriptors. ''' @property def mesh(self): return self.mesh_util.mesh @property def function_space_cache(self): return self.mesh_util.function_space_cache @property def solution_function(self): '''Dolfin Function.''' return self.solution_mixed_function_data['trial'].function @property def solution_test_function(self): '''Dolfin TestFunction.''' return self.solution_mixed_function_data['test'].function @cached_property def solution_mixed_function_data(self): trial = self.make_solution_mixed_function() test = self.solution_mixed_space.make_test_function() split = OrderedDict(tuple(trial.split().items()) + tuple(test.split().items())) return dict(trial=trial, test=test, split=split)
[docs] def make_solution_mixed_function(self): ''' intercept this to use your own Function instead of allocating a new one ''' return self.solution_mixed_space.make_function()
@cached_property def solution_mixed_space(self): descs = self.subspace_descriptors_for_solution_space return MixedFunctionSpace( mesh=self.mesh, subspace_descriptors=descs, function_space_cache=self.function_space_cache)
[docs] def make_tolerance_function(self): """ Collect "trial_tolerance" from each of the subspaces and assign it as a constant in each subspace. This function is generally used in the Newton solver to determine if the iterative updates are less than an absolute tolerance. Returns ------- function: MixedFunction The function. Use the ``.function`` attribute to get the dolfin Function. """ space = self.solution_mixed_space mixedfunction = space.make_function() for k, subfunction in mixedfunction.split().items(): tol = space.subspace_descriptors_dict[k]['trial_tolerance'] # units = space.subspace_descriptors_dict[k]['trial_units'] self.mesh_util.function_subspace_registry.assign_scalar( subfunction.magnitude, tol ) return mixedfunction
[docs]class WithSubfunctionsMixin(SetattrInitMixin): '''Utility mixin for objects that need subfunctions (:py:class:`.poisson_drift_diffusion.MixedPoisson`, :py:class:`.poisson_drift_diffusion.MixedQflBand`, etc). Parameters ---------- key: str Unique prefix to prevent clashes between trial/test function names across instances (for example, "CB" to distinguish between "CB/j" and "VB/j"). subfunctions_info: Sequence of dicts with keys "{trial,test}_{name,units}" and "element". mixed_function_solution_object: :py:class:`MixedFunctionHelper` Where to register subfunctions. ''' @property def solution_subspace_descriptors(self): '''Creates properly namespaced subspace descriptors. Returns ------- : Returns properly namespaced subspace descriptors by prefixing the descriptors in :py:attr:`~subfunctions_info` with :code:`self.key + "/"`. ''' descs = [] prefix = self.subfunction_prefix for inf in self.subfunctions_info: desc = inf.copy() desc['trial_key'] = prefix(desc['trial_key']) desc['test_key' ] = prefix(desc['test_key' ]) descs.append(desc) return descs
[docs] def get_subfunction(self, name): name = self.subfunction_prefix(name) return (self.mixed_function_solution_object .solution_mixed_function_data['split'][name])
[docs] def subfunction_prefix(self, name): '''Apply prefix to this subfunction name.''' return '/'.join((self.key, name))