Commit c9444176 authored by Martin Reinecke's avatar Martin Reinecke
Browse files

propertification

parent 30f574e0
Pipeline #25048 passed with stages
in 6 minutes and 49 seconds
...@@ -7,7 +7,15 @@ NIFTy's domain classes ...@@ -7,7 +7,15 @@ NIFTy's domain classes
Abstract base class Abstract base class
------------------- -------------------
:class:`Domain` is the abstract ancestor for all of NIFTy's domains. One of the fundamental building blocks of the NIFTy4 framework is the *domain*.
Its required capabilities are expressed by the abstract :class:`Domain` class.
A domain must be able to answer the following queries:
- its total number of data entries (pixels), which is accessible via the
:attr:`~Domain.size` property
- the shape of the array that is supposed to hold these data entries
(obtainable by means of the :attr:`~Domain.shape` property)
- equality comparison to another :class:`Domain` instance
Unstructured domains Unstructured domains
...@@ -25,18 +33,31 @@ Structured domains ...@@ -25,18 +33,31 @@ Structured domains
------------------ ------------------
In contrast to unstructured domains, these domains have an assigned geometry. In contrast to unstructured domains, these domains have an assigned geometry.
NIFTy requires these domains to provide the volume elements of their grid cells. NIFTy requires them to provide the volume elements of their grid cells.
The additional methods are described in the abstract class The additional methods are specified in the abstract class
:class:`StructuredDomain`. :class:`StructuredDomain`:
NIFTy comes with several concrete subclasses of :class:`StructuredDomain`. - Methods :meth:`~StructuredDomain.scalar_dvol`,
:meth:`~StructuredDomain.dvol`, and :meth:`~StructuredDomain.total_volume`
:class:`RGSpace` represents a regular Cartesian grid with an arbitrary provide information about the domain's pixel volume(s) and its total volume.
number of dimensions, which is supposed to be periodic in each dimension. - The property :attr:`~StructuredDomain.harmonic` specifies whether a domain
This domain can be constructed to represent either position or harmonic space. is harmonic (i.e. describes a frequency space) or not
- Iff the domain is harmonic, the methods
:class:`HPSpace` and :class:`GLSpace` describe pixelisations of the :meth:`~StructuredDomain.get_k_length_array`,
2-sphere; their counterpart in harmonic space is :class:`LMSpace`, which :meth:`~StructuredDomain.get_unique_k_lengths`, and
contains spherical harmonic coefficients. :meth:`~StructuredDomain.get_fft_smoothing_kernel_function` provide absolute
distances of the individual grid cells from the origin and assist with
:class:`PowerSpace` is used to describe one-dimensional power spectra. Gaussian convolution.
NIFTy comes with several concrete subclasses of :class:`StructuredDomain`:
- :class:`RGSpace` represents a regular Cartesian grid with an arbitrary
number of dimensions, which is supposed to be periodic in each dimension.
- :class:`HPSpace` and :class:`GLSpace` describe pixelisations of the
2-sphere; their counterpart in harmonic space is :class:`LMSpace`, which
contains spherical harmonic coefficients.
- :class:`PowerSpace` is used to describe one-dimensional power spectra.
Among these, :class:`RGSpace` can be harmonic or not (depending on constructor arguments), :class:`GLSpace`, :class:`HPSpace`, and :class:`PowerSpace` are
pure position domains (i.e. nonharmonic), and :class:`LMSpace` is always
harmonic.
...@@ -42,9 +42,11 @@ One of the fundamental building blocks of the NIFTy4 framework is the *domain*. ...@@ -42,9 +42,11 @@ One of the fundamental building blocks of the NIFTy4 framework is the *domain*.
Its required capabilities are expressed by the abstract :class:`Domain` class. Its required capabilities are expressed by the abstract :class:`Domain` class.
A domain must be able to answer the following queries: A domain must be able to answer the following queries:
- its total number of data entries (pixels) - its total number of data entries (pixels), which is accessible via the
- the shape of the array that is supposed to hold them :attr:`~Domain.size` property
- equality/inequality to another :class:`Domain` instance - the shape of the array that is supposed to hold these data entries
(obtainable by means of the :attr:`~Domain.shape` property)
- equality comparison to another :class:`Domain` instance
Unstructured domains Unstructured domains
......
...@@ -39,9 +39,11 @@ class DOFSpace(StructuredDomain): ...@@ -39,9 +39,11 @@ class DOFSpace(StructuredDomain):
def size(self): def size(self):
return len(self._dvol) return len(self._dvol)
@property
def scalar_dvol(self): def scalar_dvol(self):
return None return None
@property
def dvol(self): def dvol(self):
return np.array(self._dvol) return np.array(self._dvol)
......
...@@ -25,6 +25,12 @@ import numpy as np ...@@ -25,6 +25,12 @@ import numpy as np
class Domain(with_metaclass( class Domain(with_metaclass(
NiftyMeta, type('NewBase', (object,), {}))): NiftyMeta, type('NewBase', (object,), {}))):
"""The abstract class repesenting a (structured or unstructured) domain. """The abstract class repesenting a (structured or unstructured) domain.
Attributes:
-----------
_needed_for_hash : list of str
the names of all members that are relevant for comparison against
other Domain objects.
""" """
def __init__(self): def __init__(self):
...@@ -39,8 +45,8 @@ class Domain(with_metaclass( ...@@ -39,8 +45,8 @@ class Domain(with_metaclass(
Notes Notes
----- -----
Only members that are explicitly added to Only members that are explicitly added to
:py:attr:`._needed_for_hash` will be used for hashing. :attr:`._needed_for_hash` will be used for hashing.
""" """
result_hash = 0 result_hash = 0
for key in self._needed_for_hash: for key in self._needed_for_hash:
...@@ -58,6 +64,15 @@ class Domain(with_metaclass( ...@@ -58,6 +64,15 @@ class Domain(with_metaclass(
Returns Returns
------- -------
bool : True iff `self` and x describe the same domain. bool : True iff `self` and x describe the same domain.
Notes
-----
Only members that are explicitly added to
:attr:`._needed_for_hash` will be used for comparisom.
Subclasses of Domain should not re-define :meth:`__eq__`,
:meth:`__ne__`, or :meth:`__hash__`; they should instead add their
relevant attributes' names to :attr:`._needed_for_hash`.
""" """
if self is x: # shortcut for simple case if self is x: # shortcut for simple case
return True return True
...@@ -69,12 +84,12 @@ class Domain(with_metaclass( ...@@ -69,12 +84,12 @@ class Domain(with_metaclass(
return True return True
def __ne__(self, x): def __ne__(self, x):
"""Returns the opposite of :py:meth:`.__eq__()`""" """Returns the opposite of :meth:`.__eq__()`"""
return not self.__eq__(x) return not self.__eq__(x)
@abc.abstractproperty @abc.abstractproperty
def shape(self): def shape(self):
"""tuple of ints: number of pixels along each axis """tuple of int: number of pixels along each axis
The shape of the array-like object required to store information The shape of the array-like object required to store information
living on the domain. living on the domain.
......
...@@ -67,17 +67,20 @@ class GLSpace(StructuredDomain): ...@@ -67,17 +67,20 @@ class GLSpace(StructuredDomain):
def size(self): def size(self):
return np.int((self.nlat * self.nlon)) return np.int((self.nlat * self.nlon))
@property
def scalar_dvol(self): def scalar_dvol(self):
return None return None
# MR FIXME: this is potentially wasteful, since the return array is # MR FIXME: this is potentially wasteful, since the return array is
# blown up by a factor of self.nlon # blown up by a factor of self.nlon
@property
def dvol(self): def dvol(self):
from pyHealpix import GL_weights from pyHealpix import GL_weights
if self._dvol is None: if self._dvol is None:
self._dvol = GL_weights(self.nlat, self.nlon) self._dvol = GL_weights(self.nlat, self.nlon)
return np.repeat(self._dvol, self.nlon) return np.repeat(self._dvol, self.nlon)
@property
def total_volume(self): def total_volume(self):
return 4*np.pi return 4*np.pi
......
...@@ -56,6 +56,7 @@ class HPSpace(StructuredDomain): ...@@ -56,6 +56,7 @@ class HPSpace(StructuredDomain):
def size(self): def size(self):
return np.int(12 * self.nside * self.nside) return np.int(12 * self.nside * self.nside)
@property
def scalar_dvol(self): def scalar_dvol(self):
return np.pi / (3*self._nside*self._nside) return np.pi / (3*self._nside*self._nside)
......
...@@ -74,6 +74,7 @@ class LMSpace(StructuredDomain): ...@@ -74,6 +74,7 @@ class LMSpace(StructuredDomain):
# minus two little triangles if mmax < lmax # minus two little triangles if mmax < lmax
return (l+1)**2 - (l-m)*(l-m+1) return (l+1)**2 - (l-m)*(l-m+1)
@property
def scalar_dvol(self): def scalar_dvol(self):
return 1. return 1.
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# Copyright(C) 2013-2017 Max-Planck-Society # Copyright(C) 2013-2018 Max-Planck-Society
# #
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik # NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes. # and financially supported by the Studienstiftung des deutschen Volkes.
...@@ -143,11 +143,11 @@ class PowerSpace(StructuredDomain): ...@@ -143,11 +143,11 @@ class PowerSpace(StructuredDomain):
if not (isinstance(harmonic_partner, StructuredDomain) and if not (isinstance(harmonic_partner, StructuredDomain) and
harmonic_partner.harmonic): harmonic_partner.harmonic):
raise ValueError("harmonic_partner must be a harmonic space.") raise ValueError("harmonic_partner must be a harmonic space.")
if harmonic_partner.scalar_dvol() is None: if harmonic_partner.scalar_dvol is None:
raise ValueError("harmonic partner must have " raise ValueError("harmonic partner must have "
"scalar volume factors") "scalar volume factors")
self._harmonic_partner = harmonic_partner self._harmonic_partner = harmonic_partner
pdvol = harmonic_partner.scalar_dvol() pdvol = harmonic_partner.scalar_dvol
if binbounds is not None: if binbounds is not None:
binbounds = tuple(binbounds) binbounds = tuple(binbounds)
...@@ -178,7 +178,10 @@ class PowerSpace(StructuredDomain): ...@@ -178,7 +178,10 @@ class PowerSpace(StructuredDomain):
weights=dobj.local_data(k_length_array.val).ravel(), weights=dobj.local_data(k_length_array.val).ravel(),
minlength=nbin).astype(np.float64, copy=False) minlength=nbin).astype(np.float64, copy=False)
temp_k_lengths = dobj.np_allreduce_sum(temp_k_lengths) / temp_rho temp_k_lengths = dobj.np_allreduce_sum(temp_k_lengths) / temp_rho
temp_k_lengths.flags.writeable = False
dobj.lock(temp_pindex)
temp_dvol = temp_rho*pdvol temp_dvol = temp_rho*pdvol
temp_dvol.flags.writeable = False
self._powerIndexCache[key] = (binbounds, temp_pindex, self._powerIndexCache[key] = (binbounds, temp_pindex,
temp_k_lengths, temp_dvol) temp_k_lengths, temp_dvol)
...@@ -202,9 +205,11 @@ class PowerSpace(StructuredDomain): ...@@ -202,9 +205,11 @@ class PowerSpace(StructuredDomain):
def size(self): def size(self):
return self.shape[0] return self.shape[0]
@property
def scalar_dvol(self): def scalar_dvol(self):
return None return None
@property
def dvol(self): def dvol(self):
return self._dvol return self._dvol
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# Copyright(C) 2013-2017 Max-Planck-Society # Copyright(C) 2013-2018 Max-Planck-Society
# #
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik # NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes. # and financially supported by the Studienstiftung des deutschen Volkes.
...@@ -35,10 +35,14 @@ class RGSpace(StructuredDomain): ...@@ -35,10 +35,14 @@ class RGSpace(StructuredDomain):
distances : None or float or tuple of float, optional distances : None or float or tuple of float, optional
Distance between two grid points along each axis Distance between two grid points along each axis
(default: None). (default: None).
If distances==None:
if harmonic==True, all distances will be set to 1 If distances is None:
if harmonic==False, the distance along each axis will be
set to the inverse of the number of points along that axis. - if harmonic==True, all distances will be set to 1
- if harmonic==False, the distance along each axis will be
set to the inverse of the number of points along that axis.
harmonic : bool, optional harmonic : bool, optional
Whether the space represents a grid in position or harmonic space. Whether the space represents a grid in position or harmonic space.
(default: False). (default: False).
...@@ -84,6 +88,7 @@ class RGSpace(StructuredDomain): ...@@ -84,6 +88,7 @@ class RGSpace(StructuredDomain):
def size(self): def size(self):
return self._size return self._size
@property
def scalar_dvol(self): def scalar_dvol(self):
return self._dvol return self._dvol
...@@ -186,8 +191,8 @@ class RGSpace(StructuredDomain): ...@@ -186,8 +191,8 @@ class RGSpace(StructuredDomain):
@property @property
def distances(self): def distances(self):
"""Distance between grid points along each axis. It is a tuple """tuple of float : Distance between grid points along each axis.
of positive floating point numbers with the n-th entry giving the The n-th entry of the tuple is the distance between neighboring
distance between neighboring grid points along the n-th dimension. grid points along the n-th dimension.
""" """
return self._distances return self._distances
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# Copyright(C) 2013-2017 Max-Planck-Society # Copyright(C) 2013-2018 Max-Planck-Society
# #
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik # NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes. # and financially supported by the Studienstiftung des deutschen Volkes.
...@@ -32,37 +32,33 @@ class StructuredDomain(Domain): ...@@ -32,37 +32,33 @@ class StructuredDomain(Domain):
def __init__(self): def __init__(self):
super(StructuredDomain, self).__init__() super(StructuredDomain, self).__init__()
@abc.abstractmethod @abc.abstractproperty
def scalar_dvol(self): def scalar_dvol(self):
"""Returns the volume factors of this domain as a floating """float or None : uniform cell volume, if applicable
Returns the volume factors of this domain as a floating
point scalar, if the volume factors are all identical, otherwise point scalar, if the volume factors are all identical, otherwise
returns None. returns None.
Returns
-------
float or None: Volume factor
""" """
raise NotImplementedError raise NotImplementedError
@property
def dvol(self): def dvol(self):
"""Returns the volume factors of this domain, either as a floating """float or numpy.ndarray(dtype=float): Volume factors
Returns the volume factors of this domain, either as a floating
point scalar (if the volume factors are all identical) or as a point scalar (if the volume factors are all identical) or as a
floating point array with a shape of `self.shape`. floating point array with a shape of `self.shape`.
Returns
-------
float or numpy.ndarray(dtype=float): Volume factors
""" """
return self.scalar_dvol() return self.scalar_dvol
@property
def total_volume(self): def total_volume(self):
"""Returns the sum over all the domain's pixel volumes. """float : Total domain volume
Returns Returns the sum over all the domain's pixel volumes.
-------
float : sum of all pixel volume elements
""" """
tmp = self.dvol() tmp = self.dvol
return self.size * tmp if np.isscalar(tmp) else np.sum(tmp) return self.size * tmp if np.isscalar(tmp) else np.sum(tmp)
@abc.abstractproperty @abc.abstractproperty
...@@ -71,7 +67,9 @@ class StructuredDomain(Domain): ...@@ -71,7 +67,9 @@ class StructuredDomain(Domain):
raise NotImplementedError raise NotImplementedError
def get_k_length_array(self): def get_k_length_array(self):
"""The length of the k vector for every pixel. """k vector lengths, if applicable,
Returns the length of the k vector for every pixel.
This method is only implemented for harmonic domains. This method is only implemented for harmonic domains.
Returns Returns
...@@ -82,14 +80,16 @@ class StructuredDomain(Domain): ...@@ -82,14 +80,16 @@ class StructuredDomain(Domain):
raise NotImplementedError raise NotImplementedError
def get_unique_k_lengths(self): def get_unique_k_lengths(self):
""" Returns an array of floats containing the unique k vector lengths """Sorted unique k-vector lengths, if applicable.
Returns an array of floats containing the unique k vector lengths
for this domain. for this domain.
This method is only implemented for harmonic domains. This method is only implemented for harmonic domains.
""" """
raise NotImplementedError raise NotImplementedError
def get_fft_smoothing_kernel_function(self, sigma): def get_fft_smoothing_kernel_function(self, sigma):
"""This method returns a smoothing kernel function. """Helper for Gaussian smoothing.
This method, which is only implemented for harmonic domains, helps This method, which is only implemented for harmonic domains, helps
smoothing fields that live on a domain that has this domain as smoothing fields that live on a domain that has this domain as
......
...@@ -166,6 +166,13 @@ class Field(object): ...@@ -166,6 +166,13 @@ class Field(object):
def fill(self, fill_value): def fill(self, fill_value):
self._val.fill(fill_value) self._val.fill(fill_value)
def lock(self):
dobj.lock(self._val)
@property
def locked(self):
return dobj.locked(self._val)
@property @property
def val(self): def val(self):
""" Returns the data object associated with this Field. """ Returns the data object associated with this Field.
...@@ -227,19 +234,36 @@ class Field(object): ...@@ -227,19 +234,36 @@ class Field(object):
Returns Returns
------- -------
Field Field
The output object. An identical copy of 'self'. An identical copy of 'self'.
""" """
return Field(val=self, copy=True) return Field(val=self, copy=True)
def locked_copy(self):
""" Returns a read-only version of the Field.
If `self` is locked, returns `self`. Otherwise returns a locked copy
of `self`.
Returns
-------
Field
A read-only version of 'self'.
"""
if self.locked:
return self
res = Field(val=self, copy=True)
res.lock()
return res
def scalar_weight(self, spaces=None): def scalar_weight(self, spaces=None):
if np.isscalar(spaces): if np.isscalar(spaces):
return self._domain[spaces].scalar_dvol() return self._domain[spaces].scalar_dvol
if spaces is None: if spaces is None:
spaces = range(len(self._domain)) spaces = range(len(self._domain))
res = 1. res = 1.
for i in spaces: for i in spaces:
tmp = self._domain[i].scalar_dvol() tmp = self._domain[i].scalar_dvol
if tmp is None: if tmp is None:
return None return None
res *= tmp res *= tmp
...@@ -247,13 +271,13 @@ class Field(object): ...@@ -247,13 +271,13 @@ class Field(object):
def total_volume(self, spaces=None): def total_volume(self, spaces=None):
if np.isscalar(spaces): if np.isscalar(spaces):
return self._domain[spaces].total_volume() return self._domain[spaces].total_volume
if spaces is None: if spaces is None:
spaces = range(len(self._domain)) spaces = range(len(self._domain))
res = 1. res = 1.
for i in spaces: for i in spaces:
res *= self._domain[i].total_volume() res *= self._domain[i].total_volume
return res return res
def weight(self, power=1, spaces=None, out=None): def weight(self, power=1, spaces=None, out=None):
...@@ -287,7 +311,7 @@ class Field(object): ...@@ -287,7 +311,7 @@ class Field(object):
fct = 1. fct = 1.
for ind in spaces: for ind in spaces:
wgt = self._domain[ind].dvol() wgt = self._domain[ind].dvol
if np.isscalar(wgt): if np.isscalar(wgt):
fct *= wgt fct *= wgt
else: else:
......
...@@ -60,7 +60,7 @@ class ChainOperator(LinearOperator): ...@@ -60,7 +60,7 @@ class ChainOperator(LinearOperator):
# try to absorb the factor into a DiagonalOperator # try to absorb the factor into a DiagonalOperator
for i in range(len(opsnew)): for i in range(len(opsnew)):
if isinstance(opsnew[i], DiagonalOperator): if isinstance(opsnew[i], DiagonalOperator):
opsnew[i] = DiagonalOperator(opsnew[i].diagonal()*fct, opsnew[i] = DiagonalOperator(opsnew[i].diagonal*fct,
domain=opsnew[i].domain,