Source code for nifty8.domain_tuple
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright(C) 2013-2019 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik.
from functools import reduce
import numpy as np
from . import utilities
from .domains.domain import Domain
[docs]
class DomainTuple:
"""Ordered sequence of Domain objects.
This class holds a tuple of :class:`Domain` objects, which together form
the space on which a :class:`Field` is defined.
This corresponds to a tensor product of the corresponding vector
fields.
Notes
-----
DomainTuples should never be created using the constructor, but rather
via the factory function :attr:`make`!
"""
_tupleCache = {}
_scalarDomain = None
[docs]
def __init__(self, domain, _callingfrommake=False):
if not _callingfrommake:
raise NotImplementedError
self._dom = self._parse_domain(domain)
self._axtuple = self._get_axes_tuple()
self._shape = reduce(lambda x, y: x+y, (sp.shape for sp in self._dom),
())
self._size = reduce(lambda x, y: x*y, self._shape, 1)
def _get_axes_tuple(self):
i = 0
res = [None]*len(self._dom)
for idx, thing in enumerate(self._dom):
nax = len(thing.shape)
res[idx] = tuple(range(i, i+nax))
i += nax
return tuple(res)
[docs]
@staticmethod
def make(domain):
"""Returns a DomainTuple matching `domain`.
This function checks whether a matching DomainTuple already exists.
If yes, this object is returned, otherwise a new DomainTuple object
is created and returned.
Parameters
----------
domain : Domain or tuple of Domain or DomainTuple
The geometrical structure for which the DomainTuple shall be
obtained.
"""
if isinstance(domain, DomainTuple):
return domain
from .multi_domain import MultiDomain
if isinstance(domain, MultiDomain):
raise TypeError("Cannot create DomainTuple from MultiDomain")
domain = DomainTuple._parse_domain(domain)
obj = DomainTuple._tupleCache.get(domain)
if obj is not None:
return obj
obj = DomainTuple(domain, _callingfrommake=True)
DomainTuple._tupleCache[domain] = obj
return obj
@staticmethod
def _parse_domain(domain):
if domain is None:
return ()
if isinstance(domain, Domain):
return (domain,)
if not isinstance(domain, tuple):
domain = tuple(domain)
for d in domain:
if not isinstance(d, Domain):
raise TypeError(
"Given object contains something that is not an "
"instance of Domain class.")
return domain
def __getitem__(self, i):
return self._dom[i]
@property
def shape(self):
"""tuple of int: number of pixels along each axis
The shape of the array-like object required to store information
defined on the DomainTuple.
"""
return self._shape
@property
def size(self):
"""int : total number of pixels.
Equivalent to the products over all entries in the object's shape.
"""
return self._size
[docs]
def scalar_weight(self, spaces=None):
"""Returns the uniform volume element for a sub-domain of `self`.
Parameters
----------
spaces : int, tuple of int or None
Indices of the sub-domains to be considered.
If `None`, the entire domain is used.
Returns
-------
float or None
If the requested sub-domain has a uniform volume element, it is
returned. Otherwise, `None` is returned.
"""
if np.isscalar(spaces):
return self._dom[spaces].scalar_dvol
if spaces is None:
spaces = range(len(self._dom))
res = 1.
for i in spaces:
tmp = self._dom[i].scalar_dvol
if tmp is None:
return None
res *= tmp
return res
[docs]
def total_volume(self, spaces=None):
"""Returns the total volume of `self` or of a subspace of it.
Parameters
----------
spaces : int, tuple of int or None
Indices of the sub-domains of the domain to be considered.
If `None`, the total volume of the whole domain is returned.
Returns
-------
float
the total volume of the requested (sub-)domain.
"""
if np.isscalar(spaces):
return self._dom[spaces].total_volume
if spaces is None:
spaces = range(len(self._dom))
res = 1.
for i in spaces:
res *= self._dom[i].total_volume
return res
@property
def axes(self):
"""tuple of tuple of int : axis indices of the underlying domains"""
return self._axtuple
def __len__(self):
return len(self._dom)
def __hash__(self):
return self._dom.__hash__()
def __eq__(self, x):
return (self is x) or (isinstance(x, DomainTuple) and self._dom == x._dom)
def __ne__(self, x):
return not self.__eq__(x)
def __str__(self):
if len(self) == 0:
return "DomainTuple, len: 0"
return ("DomainTuple, len: {}\n".format(len(self)) +
"\n".join("* " + str(i) for i in self))
def __reduce__(self):
return (_unpickleDomainTuple, (self._dom,))
[docs]
@staticmethod
def scalar_domain():
if DomainTuple._scalarDomain is None:
DomainTuple._scalarDomain = DomainTuple.make(())
return DomainTuple._scalarDomain
def __repr__(self):
return f"DomainTuple.make({tuple(self)})"
def _unpickleDomainTuple(*args):
return DomainTuple.make(*args)