Commit 691535a1 authored by Reimar H Leike's avatar Reimar H Leike
Browse files

Felix added docstrings to all methods of field (yes, all!). Some optional...

Felix added docstrings to all methods of field (yes, all!). Some optional keywords are not correctly documented yet
parent a8914c33
Pipeline #12367 passed with stage
in 6 minutes and 35 seconds
......@@ -36,6 +36,75 @@ from nifty.random import Random
class Field(Loggable, Versionable, object):
""" Combines D2O-objects with meta-information NIFTY needs for computations.
In NIFTY, Fields are used to store dataarrays and carry all the needed
metainformation (i.e. the domain) for operators to be able to work on them.
In addition Field has functions to analyse the power spectrum of it's
content or create a random field of it.
Parameters
----------
domain : DomainObject
One of the space types NIFTY supports. RGSpace, GLSpace, HPSpace,
LMSpace or PowerSpace. It might also be a FieldArray, which is
an unstructured domain.
val : scalar, numpy.ndarray, distributed_data_object, Field
The values the array should contain after init. A scalar input will
fill the whole array with this scalar. If an array is provided the
array's dimensions must match the domain's.
dtype : type
A numpy.type. Most common are int, float and complex.
distribution_strategy: optional[{'fftw', 'equal', 'not', 'freeform'}]
Specifies which distributor will be created and used.
'fftw' uses the distribution strategy of pyfftw,
'equal' tries to distribute the data as uniform as possible
'not' does not distribute the data at all
'freeform' distribute the data according to the given local data/shape
copy: boolean
Attributes
----------
val : distributed_data_object
domain : DomainObject
See Parameters.
domain_axes : tuple of tuples
Enumerates the axes of the Field
dtype : type
Contains the datatype stored in the Field.
distribution_strategy : string
Name of the used distribution_strategy.
Raise
-----
TypeError
Raised if
*the given domain contains something that is not a DomainObject
instance
*val is an array that has a different dimension than the domain
Examples
--------
>>> a = Field(RGSpace([4,5]),val=2)
>>> a.val
<distributed_data_object>
array([[2, 2, 2, 2, 2],
[2, 2, 2, 2, 2],
[2, 2, 2, 2, 2],
[2, 2, 2, 2, 2]])
>>> a.dtype
dtype('int64')
See Also
--------
distributed_data_object
"""
# ---Initialization methods---
def __init__(self, domain=None, val=None, dtype=None,
......@@ -57,6 +126,21 @@ class Field(Loggable, Versionable, object):
self.set_val(new_val=val, copy=copy)
def _parse_domain(self, domain, val=None):
""" Returns a tuple of DomainObjects for nomenclature unification.
Parameters
----------
domain : all supported NIFTY spaces
The domain over which the Field lives.
val : a NIFTY Field instance
Can be used to make Field infere it's domain by adopting val's
domain.
Returns
-------
out : tuple
The output object. A tuple with one or multiple DomainObjects.
"""
if domain is None:
if isinstance(val, Field):
domain = val.domain
......@@ -75,6 +159,29 @@ class Field(Loggable, Versionable, object):
return domain
def _get_axes_tuple(self, things_with_shape, start=0):
""" Enumerates all axes of the domain.
This function is used in the greater context of the 'spaces' keyword.
Parameters
----------
things_with_shape : indexable list of objects with .shape property
Normal input is a domain/ tuple of domains.
start : int
Sets the integer number for the first axis
Returns
-------
out : tuple
Incremental numeration of all axes.
Note
----
The 'spaces' keyword is used in operators in order to carry out
operations only on a certain subspace if the domain of the Field is
a product space.
"""
i = start
axes_list = []
for thing in things_with_shape:
......@@ -86,6 +193,20 @@ class Field(Loggable, Versionable, object):
return tuple(axes_list)
def _infer_dtype(self, dtype, val):
""" Inferes the datatype of the Field
Parameters
----------
dtype : type
Can be None
val : list of arrays
If the dtype is None, Fields tries to infere the datatype from the
values given to it at initialization.
Returns
-------
out : np.dtype
"""
if dtype is None:
try:
dtype = val.dtype
......@@ -102,6 +223,19 @@ class Field(Loggable, Versionable, object):
return dtype
def _parse_distribution_strategy(self, distribution_strategy, val):
""" Sets a distribution strategy which the underlying D2O then uses.
Parameters
----------
distribution_strategy : all supported distribution strategies
The distribution strategy the new d2o should have.
val : Field or D2O
Can infere the distribution strategy from another given object.
Returns
-------
out : distribution_strategy
"""
if distribution_strategy is None:
if isinstance(val, distributed_data_object):
distribution_strategy = val.distribution_strategy
......@@ -121,6 +255,35 @@ class Field(Loggable, Versionable, object):
@classmethod
def from_random(cls, random_type, domain=None, dtype=None,
distribution_strategy=None, **kwargs):
""" Draws a random field with the given parameters.
Parameters
----------
cls : class
random_type : String
'pm1', 'normal', 'uniform' are the supported arguments for this
method.
domain : DomainObject
The domain of the output random field
dtype : type
The datatype of the output random field
distribution_strategy : all supported distribution strategies
The distribution strategy of the output random field
Returns
-------
out : Field
The output object.
See Also
--------
_parse_random_arguments, power_synthesise
"""
# create a initially empty field
f = cls(domain=domain, dtype=dtype,
distribution_strategy=distribution_strategy)
......@@ -143,7 +306,30 @@ class Field(Loggable, Versionable, object):
@staticmethod
def _parse_random_arguments(random_type, f, **kwargs):
""" Processes the arguments given to a unified nomenclature.
Parameters
----------
random_type : String
'pm1', 'normal', 'uniform'
f : not used! FIXME: delete or use!
Raise
-----
KeyError
Raised if
*the given randomtype is not 'pm1', 'normal' or 'uniform'
Returns
-------
out : The generated random arguments.
See Also
--------
from_random
"""
if random_type == "pm1":
random_arguments = {}
......@@ -167,8 +353,58 @@ class Field(Loggable, Versionable, object):
# ---Powerspectral methods---
def power_analyze(self, spaces=None, logarithmic=False, nbin=None, binbounds=None,
decompose_power=False):
def power_analyze(self, spaces=None, log=False, nbin=None, binbounds=None,
real_signal=True):
""" Computes the powerspectrum of the Field
Creates a PowerSpace with the given attributes and computes the
power spectrum as a field over this PowerSpace.
It's important to note that this can only be done if the subspace to
be analyzed is in harmonic space.
Parameters
----------
spaces : int, *optional*
The subspace which you want to have the powerspectrum of.
{default : None}
if spaces==None : Tries to synthesize for the whole domain
log : boolean, *optional*
True if the output PowerSpace should have log binning.
{default : False}
nbin : int, None, *optional*
The number of bins the resulting PowerSpace shall have.
{default : None}
if nbin==None : maximum number of bins is used
binbounds : array-like, None, *optional*
Inner bounds of the bins, if specifield
{default : None}
if binbounds==None : bins are inferred. Overwrites nbins and log
real_signal : boolean, *optional*
Whether the analysed signal-space Field is real or complex.
For a real field a complex power spectrum comes out.
For a compex field all power is put in a real power spectrum.
{default : True}
Raise
-----
ValueError
Raised if
*len(spaces) is either 0 or >1
*len(domain) is not 1 with spaces=None
*the analyzed space is not harmonic
Returns
-------
out : Field
The output object. It's domain is a PowerSpace and it contains
the power spectrum of 'self's field.
See Also
--------
power_synthesize, PowerSpace
"""
# check if all spaces in `self.domain` are either harmonic or
# power_space instances
for sp in self.domain:
......@@ -210,18 +446,18 @@ class Field(Loggable, Versionable, object):
self.val.get_axes_local_distribution_strategy(
self.domain_axes[space_index])
harmonic_partner = self.domain[space_index]
power_domain = PowerSpace(harmonic_partner=harmonic_partner,
harmonic_domain = self.domain[space_index]
power_domain = PowerSpace(harmonic_domain=harmonic_domain,
distribution_strategy=distribution_strategy,
logarithmic=logarithmic, nbin=nbin, binbounds=binbounds)
log=log, nbin=nbin, binbounds=binbounds)
# extract pindex and rho from power_domain
pindex = power_domain.pindex
rho = power_domain.rho
if decompose_power:
if real_signal:
hermitian_part, anti_hermitian_part = \
harmonic_partner.hermitian_decomposition(
harmonic_domain.hermitian_decomposition(
self.val,
axes=self.domain_axes[space_index])
......@@ -245,7 +481,7 @@ class Field(Loggable, Versionable, object):
result_domain = list(self.domain)
result_domain[space_index] = power_domain
if decompose_power:
if real_signal:
result_dtype = np.complex
else:
result_dtype = np.float
......@@ -259,6 +495,31 @@ class Field(Loggable, Versionable, object):
return result_field
def _calculate_power_spectrum(self, x, pindex, rho, axes=None):
""" Calculates the powerspectrum of the given Field x.
Parameters
----------
x : Field
The Field of which the power spectrum shall be calculated
pindex : distributed_data_object
rho : numpy.array
axes : tuple
Used if only a subspace of the whole field is analysed.
Coresponds to the 'spaces' keyword in power_analyse.
Returns
-------
out : distributed_data_object
The output object. Contains the power spectrum.
See Also
--------
power_analyze
"""
fieldabs = abs(x)
fieldabs **= 2
......@@ -280,6 +541,40 @@ class Field(Loggable, Versionable, object):
return power_spectrum
def _shape_up_pindex(self, pindex, target_shape, target_strategy, axes):
""" Makes the pindex array have the right size.
If the 'spaces' keyword is used in power_analyze this method also
shapes the pindex array the right way.
Parameters
----------
pindex : distributed_data_object
target_shape : tuple
target_strategy : a global distribution_strategy
axes : tuple
Raise
-----
ValueError
Raised if
*A wrong distribution strategy of the pindex is provided.
Returns
-------
out : distributed_data_object
The output object.
See Also
--------
_calculate_power_spectrum
"""
if pindex.distribution_strategy not in \
DISTRIBUTION_STRATEGIES['global']:
raise ValueError("pindex's distribution strategy must be "
......@@ -303,9 +598,55 @@ class Field(Loggable, Versionable, object):
return result_obj
def power_synthesize(self, spaces=None, real_power=True,
real_signal=False, mean=None, std=None):
def power_synthesize(self, spaces=None, real_power=True, real_signal=True,
mean=None, std=None):
"""Yields a random field in harmonic space with this power spectrum.
This method draws a Gaussian random field in the harmic partner domain.
The drawn field has this field as its power spectrum.
Notes
-----
For this the domain must be a PowerSpace.
Parameters
----------
spaces : {tuple, int, None} *optional*
Specifies the subspace in which the power will be synthesized in
case of a product space.
{default : None}
if spaces==None : Tries to synthesize for the whole domain
real_power : boolean *optional*
Determines whether the power spectrum is real or complex
{default : True}
real_signal : boolean *optional*
True will result in a purely real signal-space field.
This means that the created field is symmetric wrt. the origin
after complex conjugation.
{default : True}
mean : {float, None} *optional*
The mean of the noise field the powerspectrum will be multiplied on.
{default : None}
if mean==None : mean will be set to 0
std : float *optional*
The standard deviation of the noise field the powerspectrum will be
multiplied on.
{default : None}
if std==None : std will be set to 1
Returns
-------
out : Field
The output object. A random field created with the power spectrum
stored in 'self'
See Also
--------
power_analyze
"""
# check if the `spaces` input is valid
spaces = utilities.cast_axis_to_tuple(spaces, len(self.domain))
......@@ -322,8 +663,8 @@ class Field(Loggable, Versionable, object):
result_domain = list(self.domain)
for power_space_index in spaces:
power_space = self.domain[power_space_index]
harmonic_partner = power_space.harmonic_partner
result_domain[power_space_index] = harmonic_partner
harmonic_domain = power_space.harmonic_domain
result_domain[power_space_index] = harmonic_domain
# create random samples: one or two, depending on whether the
# power spectrum is real or complex
......@@ -365,8 +706,8 @@ class Field(Loggable, Versionable, object):
if real_signal:
for power_space_index in spaces:
harmonic_partner = result_domain[power_space_index]
result_val_list = [harmonic_partner.hermitian_decomposition(
harmonic_domain = result_domain[power_space_index]
result_val_list = [harmonic_domain.hermitian_decomposition(
result_val,
axes=result.domain_axes[power_space_index],
preserve_gaussian_variance=True)[0]
......@@ -385,6 +726,32 @@ class Field(Loggable, Versionable, object):
return result
def _spec_to_rescaler(self, spec, result_list, power_space_index):
""" Transforms a 1D power spectrum to a 2D Field.
This happens in order to make it applicable to a random Field by mere
pointwise multiplication.
Parameters
----------
spec : numpy.array
Has the power spectrum stored in it.
result_list : Field
Used to infere the local_distribution_strategy
power_space_index :
Basically the 'spaces' keyword
Returns
-------
out : Field
The output object. The power spectrum in 2D.
See Also
--------
power_synthesize
"""
power_space = self.domain[power_space_index]
# weight the random fields with the power spectrum
......@@ -415,6 +782,25 @@ class Field(Loggable, Versionable, object):
# ---Properties---
def set_val(self, new_val=None, copy=False):
""" Let's one set the values of the.
Parameters
----------
new_val : number, numpy.array, distributed_data_object,
Field, None, *optional*
The values to be stored in the field.
{default : None}
if new_val==None : sets the values to 0.
copy : boolean, *optional*
True if this field holds a copy of new_val, False if
it holds the same object
{default : False}
See Also
--------
val
"""
new_val = self.cast(new_val)
if copy:
new_val = new_val.copy()
......@@ -422,6 +808,24 @@ class Field(Loggable, Versionable, object):
return self
def get_val(self, copy=False):
""" Acceses the values stored in the Field.
Parameters
----------
copy : boolean
True makes the method retrun a COPY of the Field's underlying
distributed_data_object.
Returns
-------
out : distributed_data_object
The output object.
See Also
--------
val
"""
if self._val is None:
self.set_val(None)
......@@ -432,14 +836,54 @@ class Field(Loggable, Versionable, object):
@property
def val(self):
""" Retruns actual distributed_data_object associated with this Field.
Returns
-------
out : distributed_data_object
The output object.
See Also
--------
get_val
"""
return self.get_val(copy=False)
@val.setter
def val(self, new_val):
""" Sets the values in the d2o of the Field.
Parameters
----------
new_val : number, numpy.array, distributed_data_object, Field
If an array is provided it needs to have the same shape as the
domain of the Field.
See Also
--------
get_val
"""
self.set_val(new_val=new_val, copy=False)
@property
def shape(self):
""" Returns the shape of the Field/ it's domain.
All axes lengths written down seperately in a tuple.
Returns
-------
out : tuple
The output object. The tuple contains the dimansions of the spaces
in domain.
See Also
--------
dim
"""
shape_tuple = tuple(sp.shape for sp in self.domain)
try:
global_shape = reduce(lambda x, y: x + y, shape_tuple)
......@@ -450,6 +894,20 @@ class Field(Loggable, Versionable, object):
@property
def dim(self):
""" Returns the dimension of the Field/it's domain.
Multiplies all values from shape.
Returns
-------
out : int
The dimension of the Field.
See Also
--------
shape
"""
dim_tuple = tuple(sp.dim for sp in self.domain)
try:
return reduce(lambda x, y: x * y, dim_tuple)
......@@ -474,6 +932,27 @@ class Field(Loggable, Versionable, object):
# ---Special unary/binary operations---
def cast(self, x=None, dtype=None):
""" Transforms x to a d2o with the same shape as the domain of 'self'
Parameters
----------
x : number, d2o, Field, array_like
The input that shall be casted on a d2o of the same shape like the
domain.
dtype : type
The datatype the output shall have.
Returns
-------
out : distributed_data_object
The output object.
See Also