Commit c9444176 authored by Martin Reinecke's avatar Martin Reinecke

propertification

parent 30f574e0
Pipeline #25048 passed with stages
in 6 minutes and 49 seconds
......@@ -7,7 +7,15 @@ NIFTy's domain classes
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
......@@ -25,18 +33,31 @@ Structured domains
------------------
In contrast to unstructured domains, these domains have an assigned geometry.
NIFTy requires these domains to provide the volume elements of their grid cells.
The additional methods are described in the abstract class
:class:`StructuredDomain`.
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.
This domain can be constructed to represent either position or harmonic space.
: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.
NIFTy requires them to provide the volume elements of their grid cells.
The additional methods are specified in the abstract class
:class:`StructuredDomain`:
- Methods :meth:`~StructuredDomain.scalar_dvol`,
:meth:`~StructuredDomain.dvol`, and :meth:`~StructuredDomain.total_volume`
provide information about the domain's pixel volume(s) and its total volume.
- The property :attr:`~StructuredDomain.harmonic` specifies whether a domain
is harmonic (i.e. describes a frequency space) or not
- Iff the domain is harmonic, the methods
:meth:`~StructuredDomain.get_k_length_array`,
:meth:`~StructuredDomain.get_unique_k_lengths`, and
:meth:`~StructuredDomain.get_fft_smoothing_kernel_function` provide absolute
distances of the individual grid cells from the origin and assist with
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*.
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)
- the shape of the array that is supposed to hold them
- equality/inequality to another :class:`Domain` instance
- 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
......
......@@ -39,9 +39,11 @@ class DOFSpace(StructuredDomain):
def size(self):
return len(self._dvol)
@property
def scalar_dvol(self):
return None
@property
def dvol(self):
return np.array(self._dvol)
......
......@@ -25,6 +25,12 @@ import numpy as np
class Domain(with_metaclass(
NiftyMeta, type('NewBase', (object,), {}))):
"""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):
......@@ -39,8 +45,8 @@ class Domain(with_metaclass(
Notes
-----
Only members that are explicitly added to
:py:attr:`._needed_for_hash` will be used for hashing.
Only members that are explicitly added to
:attr:`._needed_for_hash` will be used for hashing.
"""
result_hash = 0
for key in self._needed_for_hash:
......@@ -58,6 +64,15 @@ class Domain(with_metaclass(
Returns
-------
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
return True
......@@ -69,12 +84,12 @@ class Domain(with_metaclass(
return True
def __ne__(self, x):
"""Returns the opposite of :py:meth:`.__eq__()`"""
"""Returns the opposite of :meth:`.__eq__()`"""
return not self.__eq__(x)
@abc.abstractproperty
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
living on the domain.
......
......@@ -67,17 +67,20 @@ class GLSpace(StructuredDomain):
def size(self):
return np.int((self.nlat * self.nlon))
@property
def scalar_dvol(self):
return None
# MR FIXME: this is potentially wasteful, since the return array is
# blown up by a factor of self.nlon
@property
def dvol(self):
from pyHealpix import GL_weights
if self._dvol is None:
self._dvol = GL_weights(self.nlat, self.nlon)
return np.repeat(self._dvol, self.nlon)
@property
def total_volume(self):
return 4*np.pi
......
......@@ -56,6 +56,7 @@ class HPSpace(StructuredDomain):
def size(self):
return np.int(12 * self.nside * self.nside)
@property
def scalar_dvol(self):
return np.pi / (3*self._nside*self._nside)
......
......@@ -74,6 +74,7 @@ class LMSpace(StructuredDomain):
# minus two little triangles if mmax < lmax
return (l+1)**2 - (l-m)*(l-m+1)
@property
def scalar_dvol(self):
return 1.
......
......@@ -11,7 +11,7 @@
# 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-2017 Max-Planck-Society
# Copyright(C) 2013-2018 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
......@@ -143,11 +143,11 @@ class PowerSpace(StructuredDomain):
if not (isinstance(harmonic_partner, StructuredDomain) and
harmonic_partner.harmonic):
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 "
"scalar volume factors")
self._harmonic_partner = harmonic_partner
pdvol = harmonic_partner.scalar_dvol()
pdvol = harmonic_partner.scalar_dvol
if binbounds is not None:
binbounds = tuple(binbounds)
......@@ -178,7 +178,10 @@ class PowerSpace(StructuredDomain):
weights=dobj.local_data(k_length_array.val).ravel(),
minlength=nbin).astype(np.float64, copy=False)
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.flags.writeable = False
self._powerIndexCache[key] = (binbounds, temp_pindex,
temp_k_lengths, temp_dvol)
......@@ -202,9 +205,11 @@ class PowerSpace(StructuredDomain):
def size(self):
return self.shape[0]
@property
def scalar_dvol(self):
return None
@property
def dvol(self):
return self._dvol
......
......@@ -11,7 +11,7 @@
# 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-2017 Max-Planck-Society
# Copyright(C) 2013-2018 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
......@@ -35,10 +35,14 @@ class RGSpace(StructuredDomain):
distances : None or float or tuple of float, optional
Distance between two grid points along each axis
(default: None).
If distances==None:
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.
If distances is None:
- 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
Whether the space represents a grid in position or harmonic space.
(default: False).
......@@ -84,6 +88,7 @@ class RGSpace(StructuredDomain):
def size(self):
return self._size
@property
def scalar_dvol(self):
return self._dvol
......@@ -186,8 +191,8 @@ class RGSpace(StructuredDomain):
@property
def distances(self):
"""Distance between grid points along each axis. It is a tuple
of positive floating point numbers with the n-th entry giving the
distance between neighboring grid points along the n-th dimension.
"""tuple of float : Distance between grid points along each axis.
The n-th entry of the tuple is the distance between neighboring
grid points along the n-th dimension.
"""
return self._distances
......@@ -11,7 +11,7 @@
# 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-2017 Max-Planck-Society
# Copyright(C) 2013-2018 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
......@@ -32,37 +32,33 @@ class StructuredDomain(Domain):
def __init__(self):
super(StructuredDomain, self).__init__()
@abc.abstractmethod
@abc.abstractproperty
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
returns None.
Returns
-------
float or None: Volume factor
"""
raise NotImplementedError
@property
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
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):
"""Returns the sum over all the domain's pixel volumes.
"""float : Total domain volume
Returns
-------
float : sum of all pixel volume elements
Returns the sum over all the domain's pixel volumes.
"""
tmp = self.dvol()
tmp = self.dvol
return self.size * tmp if np.isscalar(tmp) else np.sum(tmp)
@abc.abstractproperty
......@@ -71,7 +67,9 @@ class StructuredDomain(Domain):
raise NotImplementedError
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.
Returns
......@@ -82,14 +80,16 @@ class StructuredDomain(Domain):
raise NotImplementedError
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.
This method is only implemented for harmonic domains.
"""
raise NotImplementedError
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
smoothing fields that live on a domain that has this domain as
......
......@@ -166,6 +166,13 @@ class Field(object):
def fill(self, fill_value):
self._val.fill(fill_value)
def lock(self):
dobj.lock(self._val)
@property
def locked(self):
return dobj.locked(self._val)
@property
def val(self):
""" Returns the data object associated with this Field.
......@@ -227,19 +234,36 @@ class Field(object):
Returns
-------
Field
The output object. An identical copy of 'self'.
An identical copy of 'self'.
"""
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):
if np.isscalar(spaces):
return self._domain[spaces].scalar_dvol()
return self._domain[spaces].scalar_dvol
if spaces is None:
spaces = range(len(self._domain))
res = 1.
for i in spaces:
tmp = self._domain[i].scalar_dvol()
tmp = self._domain[i].scalar_dvol
if tmp is None:
return None
res *= tmp
......@@ -247,13 +271,13 @@ class Field(object):
def total_volume(self, spaces=None):
if np.isscalar(spaces):
return self._domain[spaces].total_volume()
return self._domain[spaces].total_volume
if spaces is None:
spaces = range(len(self._domain))
res = 1.
for i in spaces:
res *= self._domain[i].total_volume()
res *= self._domain[i].total_volume
return res
def weight(self, power=1, spaces=None, out=None):
......@@ -287,7 +311,7 @@ class Field(object):
fct = 1.
for ind in spaces:
wgt = self._domain[ind].dvol()
wgt = self._domain[ind].dvol
if np.isscalar(wgt):
fct *= wgt
else:
......
......@@ -60,7 +60,7 @@ class ChainOperator(LinearOperator):
# try to absorb the factor into a DiagonalOperator
for i in range(len(opsnew)):
if isinstance(opsnew[i], DiagonalOperator):
opsnew[i] = DiagonalOperator(opsnew[i].diagonal()*fct,
opsnew[i] = DiagonalOperator(opsnew[i].diagonal*fct,
domain=opsnew[i].domain,
spaces=opsnew[i]._spaces)
fct = 1.
......@@ -76,8 +76,8 @@ class ChainOperator(LinearOperator):
isinstance(opsnew[-1], DiagonalOperator) and
isinstance(op, DiagonalOperator) and
op._spaces == opsnew[-1]._spaces):
opsnew[-1] = DiagonalOperator(opsnew[-1].diagonal() *
op.diagonal(),
opsnew[-1] = DiagonalOperator(opsnew[-1].diagonal *
op.diagonal,
domain=opsnew[-1].domain,
spaces=opsnew[-1]._spaces)
else:
......
......@@ -44,7 +44,7 @@ class DiagonalOperator(EndomorphicOperator):
The elements of "domain" on which the operator acts.
If None, it acts on all elements.
NOTE: the fields given to __init__ and returned from .diagonal() are
NOTE: the fields given to __init__ and returned from .diagonal are
considered to be non-bare, i.e. during operator application, no additional
volume factors are applied!
"""
......@@ -74,7 +74,7 @@ class DiagonalOperator(EndomorphicOperator):
if self._spaces == tuple(range(len(self._domain))):
self._spaces = None # shortcut
self._diagonal = diagonal.copy()
self._diagonal = diagonal.locked_copy()
if self._spaces is not None:
active_axes = []
......@@ -111,9 +111,10 @@ class DiagonalOperator(EndomorphicOperator):
else:
return Field(x.domain, val=x.val/self._ldiag.conj())
@property
def diagonal(self):
""" Returns the diagonal of the Operator."""
return self._diagonal.copy()
return self._diagonal
@property
def domain(self):
......
......@@ -47,12 +47,12 @@ class DOFDistributor(LinearOperator):
raise ValueError("incorrect dofdex domain")
nbin = dofdex.max()
if partner.scalar_dvol() is not None:
if partner.scalar_dvol is not None:
wgt = np.bincount(dobj.local_data(dofdex.val).ravel(),
minlength=nbin)
wgt = wgt*partner.scalar_dvol()
wgt = wgt*partner.scalar_dvol
else:
dvol = dobj.local_data(partner.dvol())
dvol = dobj.local_data(partner.dvol)
wgt = np.bincount(dobj.local_data(dofdex.val).ravel(),
minlength=nbin, weights=dvol)
# The explicit conversion to float64 is necessary because bincount
......
......@@ -116,9 +116,9 @@ class FFTOperator(LinearOperator):
tmp = dobj.from_local_data(x.val.shape, ldat2, distaxis=0)
Tval = Field(tdom, tmp)
if mode & (LinearOperator.TIMES | LinearOperator.ADJOINT_TIMES):
fct = self._domain[self._space].scalar_dvol()
fct = self._domain[self._space].scalar_dvol
else:
fct = self._target[self._space].scalar_dvol()
fct = self._target[self._space].scalar_dvol
if fct != 1:
Tval *= fct
......
......@@ -142,7 +142,7 @@ class HarmonicTransformOperator(LinearOperator):
ldat2 = dobj.local_data(tmp).reshape(ldat.shape)
tmp = dobj.from_local_data(x.val.shape, ldat2, distaxis=0)
Tval = Field(tdom, tmp)
fct = self._domain[self._space].scalar_dvol()
fct = self._domain[self._space].scalar_dvol
if fct != 1:
Tval *= fct
......
......@@ -148,12 +148,11 @@ class LinearOperator(with_metaclass(
depending on mode.
mode : int
LinearOperator.TIMES: normal application
LinearOperator.ADJOINT_TIMES: adjoint application
LinearOperator.INVERSE_TIMES: inverse application
LinearOperator.ADJOINT_INVERSE_TIMES or
LinearOperator.INVERSE_ADJOINT_TIMES:
adjoint inverse application
- :attr:`TIMES`: normal application
- :attr:`ADJOINT_TIMES`: adjoint application
- :attr:`INVERSE_TIMES`: inverse application
- :attr:`ADJOINT_INVERSE_TIMES` or
:attr:`INVERSE_ADJOINT_TIMES`: adjoint inverse application
Returns
-------
......
......@@ -71,7 +71,7 @@ class SumOperator(LinearOperator):
for i in range(len(opsnew)):
if isinstance(opsnew[i], DiagonalOperator):
sum *= (-1 if negnew[i] else 1)
opsnew[i] = DiagonalOperator(opsnew[i].diagonal()+sum,
opsnew[i] = DiagonalOperator(opsnew[i].diagonal+sum,
domain=opsnew[i].domain,
spaces=opsnew[i]._spaces)
sum = 0.
......@@ -89,11 +89,11 @@ class SumOperator(LinearOperator):
for i in range(len(ops)):
if not processed[i]:
if isinstance(ops[i], DiagonalOperator):
diag = ops[i].diagonal()*(-1 if neg[i] else 1)
diag = ops[i].diagonal*(-1 if neg[i] else 1)
for j in range(i+1, len(ops)):
if (isinstance(ops[j], DiagonalOperator) and
ops[i]._spaces == ops[j]._spaces):
diag += ops[j].diagonal()*(-1 if neg[j] else 1)
diag += ops[j].diagonal*(-1 if neg[j] else 1)
processed[j] = True
opsnew.append(DiagonalOperator(diag, ops[i].domain,
ops[i]._spaces))
......
......@@ -11,7 +11,7 @@
# 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-2017 Max-Planck-Society
# Copyright(C) 2013-2018 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
......@@ -90,6 +90,6 @@ class DiagonalOperator_Tests(unittest.TestCase):
def test_diagonal(self, space):
diag = ift.Field.from_random('normal', domain=space)
D = ift.DiagonalOperator(diag)
diag_op = D.diagonal()
diag_op = D.diagonal
assert_allclose(ift.dobj.to_global_data(diag.val),
ift.dobj.to_global_data(diag_op.val))
......@@ -11,7 +11,7 @@
# 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-2017 Max-Planck-Society
# Copyright(C) 2013-2018 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
......@@ -73,4 +73,4 @@ class GLSpaceFunctionalityTests(unittest.TestCase):
@expand(get_dvol_configs())
def test_dvol(self, power, expected):
assert_almost_equal(GLSpace(2).dvol(), expected)
assert_almost_equal(GLSpace(2).dvol, expected)
......@@ -11,7 +11,7 @@
# 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-2017 Max-Planck-Society
# Copyright(C) 2013-2018 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
......@@ -69,4 +69,4 @@ class HPSpaceFunctionalityTests(unittest.TestCase):
assert_equal(getattr(h, key), value)
def test_dvol(self):
assert_almost_equal(HPSpace(2).dvol(), np.pi/12)
assert_almost_equal(HPSpace(2).dvol, np.pi/12)
......@@ -11,7 +11,7 @@
# 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-2017 Max-Planck-Society
# Copyright(C) 2013-2018 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
......@@ -87,7 +87,7 @@ class LMSpaceFunctionalityTests(unittest.TestCase):
assert_equal(getattr(l, key), value)
def test_dvol(self):
assert_allclose(ift.LMSpace(5).dvol(), 1.)
assert_allclose(ift.LMSpace(5).dvol, 1.)
@expand(get_k_length_array_configs())
def test_k_length_array(self, lmax, expected):
......
......@@ -11,7 +11,7 @@
# 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-2017 Max-Planck-Society
# Copyright(C) 2013-2018 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik