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 made 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 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))