Source code for simudo.mesh.topology


from functools import reduce

__all__ = ['CellRegion' , 'FacetRegion',
           'CellRegions', 'FacetRegions']

class XRegionsBase(object):
    def __init__(self, mapping=None):
        if mapping is None:
            mapping = dict()
        super().__setattr__('_mapping_', mapping)

    def __getattr__(self, name):
        return self[name]

    def __getitem__(self, name):
        if isinstance(name, (set, frozenset)):
            return {k: self[k] for k in name}

        mapping = self._mapping_
        try:
            return self._mapping_[name]
        except KeyError:
            self._mapping_[name] = value = self._create_object(name)
            return value

    def __setattr__(self, name, value):
        self[name] = value

    def __setitem__(self, name, value):
        self._mapping_[name] = value

    def __repr__(self):
        short_class_name = type(self).__name__.split('.')[-1]
        return '{}({{{}}})'.format(
            short_class_name, ''.join(
                '\n  {!r}: {!r}'.format(k, v)
                for k, v in self._mapping_.items()))

[docs]class CellRegions(XRegionsBase): '''Convenient container for cell region objects. Attributes ---------- \_mapping\_: dict Dictionary of cell regions. Methods ------- \_\_getitem\_\_(name) I.e. :code:`obj[name]`. If :code:`name` is already in :py:attr:`_mapping_`, it is retrieved from there; otherwise a :py:class:`CellRegion` is created with that name, stored inside :py:attr:`_mapping_`, and returned. If :code:`name` is a :code:`set`, then a subset of the :py:attr:`_mapping_` dictionary is returned with those keys. \_\_getattr\_\_(name) I.e. :code:`obj.$name`. Redirected to :code:`obj[name]`. ''' def _create_object(self, name): return CellRegion(name)
[docs]class FacetRegions(XRegionsBase): '''Convenient container for facet region objects. See :py:class:`CellRegions`. ''' def _create_object(self, name): return FacetRegion(name)
class AbstractCellRegion(object): pass class AbstractFacetRegion(object): pass class AlgebraicMixin(object): def evaluate(self, context): raise NotImplementedError() def __hash__(self): return hash(tuple((k, getattr(self, k)) for k in self.kwarg_names)) def __eq__(self, other): return type(self) == type(other) and all( getattr(self, k) == getattr(other, k) for k in self.kwarg_names)
[docs]class CellRegion(AlgebraicMixin, AbstractCellRegion): '''Abstract cell region. Can be evaluated down to a set of subdomain markers using :py:meth:`.MeshData.evaluate_topology`. To access a cell region predefined (named) in the mesh generator, instantiate this class directly with a :code:`name` argument. '''
[docs] def __new__(cls, *args, **kwargs): if cls is CellRegion: return CellRegionByName(*args, **kwargs) return super().__new__(cls)
[docs] def __and__(self, x): '''Return the intersection of this cell region and another.''' return CellRegionAnd((self, x))
[docs] def __or__(self, x): '''Return the union of this cell region and another.''' return CellRegionOr((self, x))
[docs] def __xor__(self, x): '''Return the symmetric difference of this cell region and another.''' return CellRegionXor((self, x))
[docs] def __sub__(self, x): '''Return the subtraction of the other cell region from this one.''' return CellRegionSub((self, x))
[docs] def boundary(self, x): ''' Signed boundary between `self` and `x`. ''' return CellBoundaryFacetRegion((self, x))
[docs] def internal_facets(self): ''' Get all internal facets. ''' return CellRegionInternalFacetRegion((self,))
[docs] def subdomain_internal_facets(self): ''' Get internal facets excluding boundaries across cell values (subdomains). ''' return CellRegionSubdomainInternalFacetRegion((self,))
[docs] @staticmethod def null(): '''Get null cell region that matches nothing.''' return NullCellRegion()
class NullCellRegion(CellRegion): def __repr__(self): return "null" def evaluate(self, context): return set() class CellRegionByName(CellRegion): kwarg_names = ('name',) def __init__(self, name): self.name = name def evaluate(self, context): return context['region_name_to_cvs'][self.name] def __repr__(self): return 'c{!r}'.format(self.name) class OperatorMixin(object): kwarg_names = ('operands',) def __init__(self, operands): self.operands = tuple(operands) self.check_operands() def check_operands(self): pass def evaluate(self, context): return reduce(self.binary_operator, ( o.evaluate(context) for o in self.operands)) def __repr__(self): return '({} {})'.format(self.operator_name, ' '.join(map(repr, self.operands))) class CellRegionOperator(OperatorMixin, CellRegion): def check_operands(self): if not all(isinstance(x, AbstractCellRegion) for x in self.operands): raise TypeError("operands must derive from AbstractCellRegion") class CellRegionAnd(CellRegionOperator): operator_name = '&' def binary_operator(self, x, y): return x & y class CellRegionOr(CellRegionOperator): operator_name = '|' def binary_operator(self, x, y): return x | y class CellRegionXor(CellRegionOperator): operator_name = '^' def binary_operator(self, x, y): return x ^ y class CellRegionSub(CellRegionOperator): operator_name = '-' def binary_operator(self, x, y): return x - y
[docs]class FacetRegion(AlgebraicMixin, AbstractFacetRegion): '''Abstract facet region. Can be evaluated down to a set of :code:`(facet_marker_value, facet_sign)` using :py:meth:`.MeshData.evaluate_topology`. To access a facet region predefined (named) in the mesh generator, instantiate this class directly with a :code:`name` argument. '''
[docs] def __new__(cls, *args, **kwargs): if cls is FacetRegion: return FacetRegionByName(*args, **kwargs) return super().__new__(cls)
[docs] def __and__(self, x): '''Return the intersection of this facet region and another.''' return FacetRegionAnd((self, x))
[docs] def __or__(self, x): '''Return the union of this facet region and another.''' return FacetRegionOr((self, x))
[docs] def __xor__(self, x): '''Return the symmetric difference of this facet region and another.''' return FacetRegionXor((self, x))
[docs] def __sub__(self, x): '''Return the subtraction of the other facet region from this one.''' return FacetRegionSub((self, x))
[docs] def flip(self): ''' Invert facet signedness. For example, :code:`X.boundary(Y) == Y.boundary(X).flip()` ''' return FacetRegionFlip((self,))
[docs] def both(self): ''' Both facet sides, i.e. :code:`f.both() == (f | f.flip())`''' return FacetRegionBothSides((self,))
[docs] def unsigned(self): ''' Erase facet signedness information by setting :code:`sign = 1` for every :code:`(facet_value, sign)` pair. ''' return FacetRegionUnsigned((self,))
[docs] @staticmethod def null(): '''Get null facet region that matches nothing.''' return NullFacetRegion()
class NullFacetRegion(FacetRegion): def __repr__(self): return "null" def evaluate(self, context): return set() class FacetRegionByName(FacetRegion): kwarg_names = ('name',) def __init__(self, name): self.name = name def evaluate(self, context): return context['facet_name_to_fvs'][self.name] def __repr__(self): return 'f{!r}'.format(self.name) class FacetRegionOperator(OperatorMixin, FacetRegion): def check_operands(self): if not all(isinstance(x, AbstractFacetRegion) for x in self.operands): raise TypeError("operands must derive from AbstractFacetRegion") class FacetRegionAnd(FacetRegionOperator): operator_name = '&' def binary_operator(self, x, y): return x & y class FacetRegionOr(FacetRegionOperator): operator_name = '|' def binary_operator(self, x, y): return x | y class FacetRegionXor(FacetRegionOperator): operator_name = '^' def binary_operator(self, x, y): return x ^ y class FacetRegionSub(FacetRegionOperator): operator_name = '-' def binary_operator(self, x, y): return x - y class FacetRegionFlip(FacetRegionOperator): operator_name = 'flip' def evaluate(self, context): arg, = self.operands return set( (fv, -sign) for fv, sign in arg.evaluate(context)) class FacetRegionBothSides(FacetRegionOperator): operator_name = 'both-sides' def evaluate(self, context): arg, = self.operands signs = (-1, 1) return set( (fv, sign) for fv, _ in arg.evaluate(context) for sign in signs) class FacetRegionUnsigned(FacetRegionOperator): operator_name = 'unsigned' def evaluate(self, context): arg, = self.operands return set( (fv, 1) for fv, _ in arg.evaluate(context)) class OperandsAreCellRegionsMixin(object): def check_operands(self): if not all(isinstance(x, AbstractCellRegion) for x in self.operands): raise TypeError("operands must derive from AbstractCellRegion") class CellBoundaryFacetRegion(OperandsAreCellRegionsMixin, FacetRegionOperator): operator_name = 'boundary' def evaluate(self, context): X, Y = self.operands return context['facets_manager'].boundary( X.evaluate(context), Y.evaluate(context)) class CellRegionInternalFacetRegion(OperandsAreCellRegionsMixin, FacetRegionOperator): operator_name = 'internal' def evaluate(self, context): arg, = self.operands return context['facets_manager'].internal( arg.evaluate(context)) class CellRegionSubdomainInternalFacetRegion(OperandsAreCellRegionsMixin, FacetRegionOperator): operator_name = 'subdomain-internal' def evaluate(self, context): arg, = self.operands return context['facets_manager'].cell_value_internal( arg.evaluate(context))