"""Types for representing function spaces."""
# Copyright (C) 2015-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
# Modified by Cecile Daversin-Catty, 2018
import typing
import numpy as np
from ufl.core.ufl_type import UFLObject
from ufl.domain import join_domains
from ufl.duals import is_dual, is_primal
from ufl.utils.sequences import product
# Export list for ufl.classes
__all_classes__ = [
"AbstractFunctionSpace",
"FunctionSpace",
"DualSpace",
"MixedFunctionSpace",
"TensorProductFunctionSpace",
]
[docs]class AbstractFunctionSpace(object):
"""Abstract function space."""
[docs] def ufl_sub_spaces(self):
"""Return ufl sub spaces."""
raise NotImplementedError(
f"Missing implementation of ufl_sub_spaces in {self.__class__.__name__}."
)
[docs]class BaseFunctionSpace(AbstractFunctionSpace, UFLObject):
"""Base function space."""
def __init__(self, domain, element, label=""):
"""Initialise."""
if domain is None:
# DOLFIN hack
# TODO: Is anything expected from element.cell in this case?
pass
else:
try:
domain_cell = domain.ufl_cell()
except AttributeError:
raise ValueError(
"Expected non-abstract domain for initalization of function space."
)
else:
if element.cell != domain_cell:
raise ValueError("Non-matching cell of finite element and domain.")
AbstractFunctionSpace.__init__(self)
self._label = label
self._ufl_domain = domain
self._ufl_element = element
@property
def components(self) -> typing.Dict[typing.Tuple[int, ...], int]:
"""Get the numbering of the components of the element of this space.
Returns:
A map from the components of the values on a physical cell (eg (0, 1))
to flat component numbers on the reference cell (eg 1)
"""
from ufl.pullback import SymmetricPullback
if isinstance(self._ufl_element.pullback, SymmetricPullback):
return self._ufl_element.pullback._symmetry
if len(self._ufl_element.sub_elements) == 0:
return {(): 0}
components = {}
offset = 0
c_offset = 0
for s in self.ufl_sub_spaces():
for i, j in enumerate(np.ndindex(s.value_shape)):
components[(offset + i,)] = c_offset + s.components[j]
c_offset += max(s.components.values()) + 1
offset += s.value_size
return components
[docs] def label(self):
"""Return label of boundary domains to differentiate restricted and unrestricted."""
return self._label
[docs] def ufl_sub_spaces(self):
"""Return ufl sub spaces."""
return ()
[docs] def ufl_domain(self):
"""Return ufl domain."""
return self._ufl_domain
[docs] def ufl_element(self):
"""Return ufl element."""
return self._ufl_element
[docs] def ufl_domains(self):
"""Return ufl domains."""
domain = self.ufl_domain()
if domain is None:
return ()
else:
return (domain,)
def _ufl_hash_data_(self, name=None):
"""UFL hash data."""
name = name or "BaseFunctionSpace"
domain = self.ufl_domain()
element = self.ufl_element()
if domain is None:
ddata = None
else:
ddata = domain._ufl_hash_data_()
if element is None:
edata = None
else:
edata = element._ufl_hash_data_()
return (name, ddata, edata, self.label())
def _ufl_signature_data_(self, renumbering, name=None):
"""UFL signature data."""
name = name or "BaseFunctionSpace"
domain = self.ufl_domain()
element = self.ufl_element()
if domain is None:
ddata = None
else:
ddata = domain._ufl_signature_data_(renumbering)
if element is None:
edata = None
else:
edata = element._ufl_signature_data_()
return (name, ddata, edata, self.label())
def __repr__(self):
"""Representation."""
return f"BaseFunctionSpace({self._ufl_domain!r}, {self._ufl_element!r})"
@property
def value_shape(self) -> typing.Tuple[int, ...]:
"""Return the shape of the value space on a physical domain."""
return self._ufl_element.pullback.physical_value_shape(self._ufl_element, self._ufl_domain)
@property
def value_size(self) -> int:
"""Return the integer product of the value shape on a physical domain."""
return product(self.value_shape)
[docs]class FunctionSpace(BaseFunctionSpace, UFLObject):
"""Representation of a Function space."""
_primal = True
_dual = False
[docs] def dual(self):
"""Get the dual of the space."""
return DualSpace(self._ufl_domain, self._ufl_element, label=self.label())
def _ufl_hash_data_(self):
"""UFL hash data."""
return BaseFunctionSpace._ufl_hash_data_(self, "FunctionSpace")
def _ufl_signature_data_(self, renumbering):
"""UFL signature data."""
return BaseFunctionSpace._ufl_signature_data_(self, renumbering, "FunctionSpace")
def __repr__(self):
"""Representation."""
return f"FunctionSpace({self._ufl_domain!r}, {self._ufl_element!r})"
def __str__(self):
"""String."""
return f"FunctionSpace({self._ufl_domain}, {self._ufl_element})"
[docs]class DualSpace(BaseFunctionSpace, UFLObject):
"""Representation of a Dual space."""
_primal = False
_dual = True
def __init__(self, domain, element, label=""):
"""Initialise."""
BaseFunctionSpace.__init__(self, domain, element, label)
[docs] def dual(self):
"""Get the dual of the space."""
return FunctionSpace(self._ufl_domain, self._ufl_element, label=self.label())
def _ufl_hash_data_(self):
"""UFL hash data."""
return BaseFunctionSpace._ufl_hash_data_(self, "DualSpace")
def _ufl_signature_data_(self, renumbering):
"""UFL signature data."""
return BaseFunctionSpace._ufl_signature_data_(self, renumbering, "DualSpace")
def __repr__(self):
"""Representation."""
return f"DualSpace({self._ufl_domain!r}, {self._ufl_element!r})"
def __str__(self):
"""String."""
return f"DualSpace({self._ufl_domain}, {self._ufl_element})"
[docs]class TensorProductFunctionSpace(AbstractFunctionSpace, UFLObject):
"""Tensor product function space."""
def __init__(self, *function_spaces):
"""Initialise."""
AbstractFunctionSpace.__init__(self)
self._ufl_function_spaces = function_spaces
[docs] def ufl_sub_spaces(self):
"""Return ufl sub spaces."""
return self._ufl_function_spaces
def _ufl_hash_data_(self):
"""UFL hash data."""
return ("TensorProductFunctionSpace",) + tuple(
V._ufl_hash_data_() for V in self.ufl_sub_spaces()
)
def _ufl_signature_data_(self, renumbering):
"""UFL signature data."""
return ("TensorProductFunctionSpace",) + tuple(
V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces()
)
def __repr__(self):
"""Representation."""
return f"TensorProductFunctionSpace(*{self._ufl_function_spaces!r})"
def __str__(self):
"""String."""
return self.__repr__()
[docs]class MixedFunctionSpace(AbstractFunctionSpace, UFLObject):
"""Mixed function space."""
def __init__(self, *args):
"""Initialise."""
AbstractFunctionSpace.__init__(self)
self._ufl_function_spaces = args
self._ufl_elements = list()
for fs in args:
if isinstance(fs, BaseFunctionSpace):
self._ufl_elements.append(fs.ufl_element())
else:
raise ValueError("Expecting BaseFunctionSpace objects")
# A mixed FS is only primal/dual if all the subspaces are primal/dual"
self._primal = all([is_primal(subspace) for subspace in self._ufl_function_spaces])
self._dual = all([is_dual(subspace) for subspace in self._ufl_function_spaces])
[docs] def ufl_sub_spaces(self):
"""Return ufl sub spaces."""
return self._ufl_function_spaces
[docs] def ufl_sub_space(self, i):
"""Return i-th ufl sub space."""
return self._ufl_function_spaces[i]
[docs] def dual(self, *args):
"""Return the dual to this function space.
If no additional arguments are passed then a MixedFunctionSpace is
returned whose components are the duals of the originals.
If additional arguments are passed, these must be integers. In this
case, the MixedFunctionSpace which is returned will have dual
components in the positions corresponding to the arguments passed, and
the original components in the other positions.
"""
if args:
spaces = [
space.dual() if i in args else space
for i, space in enumerate(self._ufl_function_spaces)
]
return MixedFunctionSpace(*spaces)
else:
return MixedFunctionSpace(*[space.dual() for space in self._ufl_function_spaces])
[docs] def ufl_elements(self):
"""Return ufl elements."""
return self._ufl_elements
[docs] def ufl_element(self):
"""Return ufl element."""
if len(self._ufl_elements) == 1:
return self._ufl_elements[0]
else:
raise ValueError(
"Found multiple elements. Cannot return only one. "
"Consider building a FunctionSpace from a MixedElement "
"in case of homogeneous dimension."
)
[docs] def ufl_domains(self):
"""Return ufl domains."""
domainlist = []
for s in self._ufl_function_spaces:
domainlist.extend(s.ufl_domains())
return join_domains(domainlist)
[docs] def ufl_domain(self):
"""Return ufl domain."""
domains = self.ufl_domains()
if len(domains) == 1:
return domains[0]
elif domains:
raise ValueError("Found multiple domains, cannot return just one.")
else:
return None
[docs] def num_sub_spaces(self):
"""Return number of subspaces."""
return len(self._ufl_function_spaces)
def _ufl_hash_data_(self):
"""UFL hash data."""
return ("MixedFunctionSpace",) + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces())
def _ufl_signature_data_(self, renumbering):
"""UFL signature data."""
return ("MixedFunctionSpace",) + tuple(
V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces()
)
def __repr__(self):
"""Representation."""
return f"MixedFunctionSpace(*{self._ufl_function_spaces!r})"
def __str__(self):
"""String."""
return self.__repr__()