Commit 1f18344d authored by Martin Reinecke's avatar Martin Reinecke
Browse files

Merge branch 'mr_docs' into 'NIFTy_5'

mtr documentation

See merge request ift/nifty-dev!160
parents 928858f4 735fc6e8
...@@ -184,17 +184,29 @@ a :class:`DomainTuple` or :class:`MultiDomain` object specifying the structure o ...@@ -184,17 +184,29 @@ a :class:`DomainTuple` or :class:`MultiDomain` object specifying the structure o
describing its output, and finally an overloaded `apply` method, which can describing its output, and finally an overloaded `apply` method, which can
take take
- a :class:`Field`/:class:`MultiField`object, in which case it returns the transformed - a :class:`Field`/:class:`MultiField` object, in which case it returns the transformed
:class:`Field`/:class:`MultiField` :class:`Field`/:class:`MultiField`
- a :class:`Linearization` object, in which case it returns the transformed - a :class:`Linearization` object, in which case it returns the transformed
:class:`Linearization` :class:`Linearization`
This is the interface that all objects derived from :class:`Operator` must implement. This is the interface that all objects derived from :class:`Operator` must implement.
In addition, :class:`Operator` objects can be added/subtracted, multiplied, chained In addition, :class:`Operator` objects can be added/subtracted, multiplied, chained
(via the :class:`__call__` method) and support pointwise application of functions like (via the :class:`__call__` method and the `@` operator) and support pointwise
application of functions like
:class:`exp()`, :class:`log()`, :class:`sqrt()`, :class:`conjugate()` etc. :class:`exp()`, :class:`log()`, :class:`sqrt()`, :class:`conjugate()` etc.
Advanced operators
------------------
NIFTy provides a library of more sophisticated operators which are used for more
specific inference problems. Currently these are:
- :class:`AmplitudeOperator`, which returns a smooth power spectrum.
- :class:`InverseGammaOperator`, which models point sources which follow a inverse gamma distribution.
- :class:`CorrelatedField`, which models a diffuse log-normal field. It takes an amplitude operator to specify the correlation structure of the field.
Linear Operators Linear Operators
================ ================
...@@ -205,8 +217,8 @@ additional functionality which is not available for the more generic :class:`Ope ...@@ -205,8 +217,8 @@ additional functionality which is not available for the more generic :class:`Ope
class. class.
Operator basics Linear Operator basics
--------------- ----------------------
There are four basic ways of applying an operator :math:`A` to a field :math:`f`: There are four basic ways of applying an operator :math:`A` to a field :math:`f`:
...@@ -218,8 +230,8 @@ There are four basic ways of applying an operator :math:`A` to a field :math:`f` ...@@ -218,8 +230,8 @@ There are four basic ways of applying an operator :math:`A` to a field :math:`f`
(Because of the linearity, inverse adjoint and adjoint inverse application (Because of the linearity, inverse adjoint and adjoint inverse application
are equivalent.) are equivalent.)
These different actions of an operator ``Op`` on a field ``f`` can be invoked These different actions of a linear operator ``Op`` on a field ``f`` can be
in various ways: invoked in various ways:
- direct multiplication: ``Op(f)`` or ``Op.times(f)`` or ``Op.apply(f, Op.TIMES)`` - direct multiplication: ``Op(f)`` or ``Op.times(f)`` or ``Op.apply(f, Op.TIMES)``
- adjoint multiplication: ``Op.adjoint_times(f)`` or ``Op.apply(f, Op.ADJOINT_TIMES)`` - adjoint multiplication: ``Op.adjoint_times(f)`` or ``Op.apply(f, Op.ADJOINT_TIMES)``
......
...@@ -24,6 +24,22 @@ from .operators.scaling_operator import ScalingOperator ...@@ -24,6 +24,22 @@ from .operators.scaling_operator import ScalingOperator
class Linearization(object): class Linearization(object):
"""Let `A` be an operator and `x` a field. `Linearization` stores the value
of the operator application (i.e. `A(x)`), the local Jacobian
(i.e. `dA(x)/dx`) and, optionally, the local metric.
Parameters
----------
val : Field/MultiField
the value of the operator application
jac : LinearOperator
the Jacobian
metric : LinearOperator or None (default: None)
the metric
want_metric : bool (default: False)
if True, the metric will be computed for other Linearizations derived
from this one.
"""
def __init__(self, val, jac, metric=None, want_metric=False): def __init__(self, val, jac, metric=None, want_metric=False):
self._val = val self._val = val
self._jac = jac self._jac = jac
...@@ -33,36 +49,63 @@ class Linearization(object): ...@@ -33,36 +49,63 @@ class Linearization(object):
self._metric = metric self._metric = metric
def new(self, val, jac, metric=None): def new(self, val, jac, metric=None):
"""Create a new Linearization, taking the `want_metric` property from
this one.
Parameters
----------
val : Field/MultiField
the value of the operator application
jac : LinearOperator
the Jacobian
metric : LinearOperator or None (default: None)
the metric
"""
return Linearization(val, jac, metric, self._want_metric) return Linearization(val, jac, metric, self._want_metric)
@property @property
def domain(self): def domain(self):
"""DomainTuple/MultiDomain : the Jacobian's domain"""
return self._jac.domain return self._jac.domain
@property @property
def target(self): def target(self):
"""DomainTuple/MultiDomain : the Jacobian's target (i.e. the value's domain)"""
return self._jac.target return self._jac.target
@property @property
def val(self): def val(self):
"""Field/MultiField : the value"""
return self._val return self._val
@property @property
def jac(self): def jac(self):
"""LinearOperator : the Jacobian"""
return self._jac return self._jac
@property @property
def gradient(self): def gradient(self):
"""Only available if target is a scalar""" """Field/MultiField : the gradient
Notes
-----
Only available if target is a scalar
"""
return self._jac.adjoint_times(Field.scalar(1.)) return self._jac.adjoint_times(Field.scalar(1.))
@property @property
def want_metric(self): def want_metric(self):
"""bool : the value of `want_metric`"""
return self._want_metric return self._want_metric
@property @property
def metric(self): def metric(self):
"""Only available if target is a scalar""" """LinearOperator : the metric
Notes
-----
Only available if target is a scalar
"""
return self._metric return self._metric
def __getitem__(self, name): def __getitem__(self, name):
......
...@@ -20,6 +20,25 @@ from ..minimization.energy import Energy ...@@ -20,6 +20,25 @@ from ..minimization.energy import Energy
class EnergyAdapter(Energy): class EnergyAdapter(Energy):
"""Helper class which provides the traditional Nifty Energy interface to
Nifty operators with a scalar target domain.
Parameters
-----------
position: Field or MultiField living on the operator's input domain.
The position where the minimization process is started
op: Operator with a scalar target domain
The expression computing the energy from the input data
constants: list of strings (default: [])
The component names of the operator's input domain which are assumed
to be constant during the minimization process.
If the operator's input domain is not a MultiField, this must be empty.
want_metric: bool (default: False)
if True, the class will provide a `metric` property. This should only
be enabled if it is required, because it will most likely consume
additional resources.
"""
def __init__(self, position, op, constants=[], want_metric=False): def __init__(self, position, op, constants=[], want_metric=False):
super(EnergyAdapter, self).__init__(position) super(EnergyAdapter, self).__init__(position)
self._op = op self._op = op
......
...@@ -143,7 +143,24 @@ class GradientNormController(IterationController): ...@@ -143,7 +143,24 @@ class GradientNormController(IterationController):
class GradInfNormController(IterationController): class GradInfNormController(IterationController):
def __init__(self, tol=None, convergence_level=1, iteration_limit=None, """An iteration controller checking (mainly) the L_infinity gradient norm.
Parameters
----------
tol : float
If the L_infinity norm of the energy gradient is below this value, the
convergence counter will be increased in this iteration.
convergence_level : int, default=1
The number which the convergence counter must reach before the
iteration is considered to be converged
iteration_limit : int, optional
The maximum number of iterations that will be carried out.
name : str, optional
if supplied, this string and some diagnostic information will be
printed after every iteration
"""
def __init__(self, tol, convergence_level=1, iteration_limit=None,
name=None): name=None):
self._tol = tol self._tol = tol
self._convergence_level = convergence_level self._convergence_level = convergence_level
...@@ -185,6 +202,25 @@ class GradInfNormController(IterationController): ...@@ -185,6 +202,25 @@ class GradInfNormController(IterationController):
class DeltaEnergyController(IterationController): class DeltaEnergyController(IterationController):
"""An iteration controller checking (mainly) the energy change from one
iteration to the next.
Parameters
----------
tol_rel_deltaE : float
If the difference between the last and current energies divided by
the current energy is below this value, the convergence counter will
be increased in this iteration.
convergence_level : int, default=1
The number which the convergence counter must reach before the
iteration is considered to be converged
iteration_limit : int, optional
The maximum number of iterations that will be carried out.
name : str, optional
if supplied, this string and some diagnostic information will be
printed after every iteration
"""
def __init__(self, tol_rel_deltaE, convergence_level=1, def __init__(self, tol_rel_deltaE, convergence_level=1,
iteration_limit=None, name=None): iteration_limit=None, name=None):
self._tol_rel_deltaE = tol_rel_deltaE self._tol_rel_deltaE = tol_rel_deltaE
......
...@@ -148,6 +148,12 @@ def L_BFGS_B(ftol, gtol, maxiter, maxcor=10, disp=False, bounds=None): ...@@ -148,6 +148,12 @@ def L_BFGS_B(ftol, gtol, maxiter, maxcor=10, disp=False, bounds=None):
class ScipyCG(Minimizer): class ScipyCG(Minimizer):
"""Returns a ScipyMinimizer object carrying out the conjugate gradient
algorithm as implemented by SciPy.
This class is only intended for double-checking NIFTy's own conjugate
gradient implementation and should not be used otherwise.
"""
def __init__(self, tol, maxiter): def __init__(self, tol, maxiter):
if not dobj.is_numpy(): if not dobj.is_numpy():
raise NotImplementedError raise NotImplementedError
......
...@@ -25,6 +25,13 @@ from .linear_operator import LinearOperator ...@@ -25,6 +25,13 @@ from .linear_operator import LinearOperator
class VdotOperator(LinearOperator): class VdotOperator(LinearOperator):
"""Operator computing the scalar product of its input with a given Field.
Parameters
----------
field : Field/MultiField
The field used to build the scalar product with the operator input
"""
def __init__(self, field): def __init__(self, field):
self._field = field self._field = field
self._domain = field.domain self._domain = field.domain
...@@ -39,6 +46,7 @@ class VdotOperator(LinearOperator): ...@@ -39,6 +46,7 @@ class VdotOperator(LinearOperator):
class ConjugationOperator(EndomorphicOperator): class ConjugationOperator(EndomorphicOperator):
"""Operator computing the complex conjugate of its input."""
def __init__(self, domain): def __init__(self, domain):
self._domain = DomainTuple.make(domain) self._domain = DomainTuple.make(domain)
self._capability = self._all_ops self._capability = self._all_ops
...@@ -49,6 +57,7 @@ class ConjugationOperator(EndomorphicOperator): ...@@ -49,6 +57,7 @@ class ConjugationOperator(EndomorphicOperator):
class Realizer(EndomorphicOperator): class Realizer(EndomorphicOperator):
"""Operator returning the real component of its input."""
def __init__(self, domain): def __init__(self, domain):
self._domain = DomainTuple.make(domain) self._domain = DomainTuple.make(domain)
self._capability = self.TIMES | self.ADJOINT_TIMES self._capability = self.TIMES | self.ADJOINT_TIMES
...@@ -100,6 +109,41 @@ class FieldAdapter(LinearOperator): ...@@ -100,6 +109,41 @@ class FieldAdapter(LinearOperator):
def ducktape(left, right, name): def ducktape(left, right, name):
"""Convenience function for computing an adapter between two operators.
Parameters
----------
left : None, Operator, or Domainoid
Something describing the input domain of the left operator.
If `left` is an `Operator`, its domain is used as `left`.
right : None, Operator, or Domainoid
Something describing the target domain of the right operator.
If `right` is an `Operator`, its target is used as `right`.
name : string
The component of the `MultiDomain` that will be extracted/inserted
Notes
-----
- one of the involved domains must be a `DomainTuple`, the other a
`MultiDomain`.
- `left` and `right` must not be both `None`, but one of them can (and
probably should) be `None`. In this case, the missing information is
inferred.
- the returned operator's domains are
- a `DomainTuple` and
- a `MultiDomain` with exactly one entry called `name` and the same
`DomainTuple`
Which of these is the domain and which is the target depends on the
input.
Returns
-------
FieldAdapter : an adapter operator converting between the two (possibly
partially inferred) domains.
"""
from ..sugar import makeDomain from ..sugar import makeDomain
from .operator import Operator from .operator import Operator
if left is None: # need to infer left from right if left is None: # need to infer left from right
......
...@@ -19,10 +19,26 @@ from .field import Field ...@@ -19,10 +19,26 @@ from .field import Field
class StatCalculator(object): class StatCalculator(object):
"""Helper class to compute mean and variance of a set of inputs.
Notes
-----
- the memory usage of this object is constant, i.e. it does not increase
with the number of samples added
- the code computes the unbiased variance (which contains a `1./(n-1)`
term for `n` samples).
"""
def __init__(self): def __init__(self):
self._count = 0 self._count = 0
def add(self, value): def add(self, value):
"""Adds a sample.
Parameters
----------
value: any type that supports multiplication by a scalar and
element-wise addition/subtraction/multiplication.
"""
self._count += 1 self._count += 1
if self._count == 1: if self._count == 1:
self._mean = 1.*value self._mean = 1.*value
...@@ -35,12 +51,18 @@ class StatCalculator(object): ...@@ -35,12 +51,18 @@ class StatCalculator(object):
@property @property
def mean(self): def mean(self):
"""
value type : the mean of all samples added so far.
"""
if self._count == 0: if self._count == 0:
raise RuntimeError raise RuntimeError
return 1.*self._mean return 1.*self._mean
@property @property
def var(self): def var(self):
"""
value type : the unbiased variance of all samples added so far.
"""
if self._count < 2: if self._count < 2:
raise RuntimeError raise RuntimeError
return self._M2 * (1./(self._count-1)) return self._M2 * (1./(self._count-1))
......
...@@ -41,6 +41,19 @@ __all__ = ['PS_field', 'power_analyze', 'create_power_operator', ...@@ -41,6 +41,19 @@ __all__ = ['PS_field', 'power_analyze', 'create_power_operator',
def PS_field(pspace, func): def PS_field(pspace, func):
"""Convenience function sampling a power spectrum
Parameters
----------
pspace : PowerSpace
space at whose `k_lengths` the power spectrum function is evaluated
func : function taking and returning a numpy.ndarray(float)
the power spectrum function
Returns
-------
Field : a field living on (pspace,) containing the computed function values
"""
if not isinstance(pspace, PowerSpace): if not isinstance(pspace, PowerSpace):
raise TypeError raise TypeError
data = dobj.from_global_data(func(pspace.k_lengths)) data = dobj.from_global_data(func(pspace.k_lengths))
...@@ -202,30 +215,104 @@ def create_harmonic_smoothing_operator(domain, space, sigma): ...@@ -202,30 +215,104 @@ def create_harmonic_smoothing_operator(domain, space, sigma):
def full(domain, val): def full(domain, val):
"""Convenience function creating Fields/MultiFields with uniform values.
Parameters
----------
domain : Domainoid
the intended domain of the output field
val : scalar value
the uniform value to be placed into all entries of the result
Returns
-------
Field / MultiField : the newly created uniform field
"""
if isinstance(domain, (dict, MultiDomain)): if isinstance(domain, (dict, MultiDomain)):
return MultiField.full(domain, val) return MultiField.full(domain, val)
return Field.full(domain, val) return Field.full(domain, val)
def from_random(random_type, domain, dtype=np.float64, **kwargs): def from_random(random_type, domain, dtype=np.float64, **kwargs):
"""Convenience function creating Fields/MultiFields with random values.
Parameters
----------
random_type : 'pm1', 'normal', or 'uniform'
The random distribution to use.
domain : Domainoid
the intended domain of the output field
dtype : type
data type of the output field (e.g. numpy.float64)
**kwargs : additional parameters for the random distribution
('mean' and 'std' for 'normal', 'low' and 'high' for 'uniform')
Returns
-------
Field / MultiField : the newly created random field
"""
if isinstance(domain, (dict, MultiDomain)): if isinstance(domain, (dict, MultiDomain)):
return MultiField.from_random(random_type, domain, dtype, **kwargs) return MultiField.from_random(random_type, domain, dtype, **kwargs)
return Field.from_random(random_type, domain, dtype, **kwargs) return Field.from_random(random_type, domain, dtype, **kwargs)
def from_global_data(domain, arr, sum_up=False): def from_global_data(domain, arr, sum_up=False):
"""Convenience function creating Fields/MultiFields from Numpy arrays or
dicts of Numpy arrays.
Parameters
----------
domain : Domainoid
the intended domain of the output field
arr : Numpy array if `domain` corresponds to a `DomainTuple`,
dictionary of Numpy arrays if `domain` corresponds to a `MultiDomain`
sum_up : bool
Only meaningful if MPI is enabled
If `True`, the contents of the arrays on all tasks are added together,
otherwise it is assumed that the array on each task holds the correct
field values.
Returns
-------
Field / MultiField : the newly created random field
"""
if isinstance(domain, (dict, MultiDomain)): if isinstance(domain, (dict, MultiDomain)):
return MultiField.from_global_data(domain, arr, sum_up) return MultiField.from_global_data(domain, arr, sum_up)
return Field.from_global_data(domain, arr, sum_up) return Field.from_global_data(domain, arr, sum_up)
def from_local_data(domain, arr): def from_local_data(domain, arr):
"""Convenience function creating Fields/MultiFields from Numpy arrays or
dicts of Numpy arrays.
Parameters
----------
domain : Domainoid
the intended domain of the output field
arr : Numpy array if `domain` corresponds to a `DomainTuple`,
dictionary of Numpy arrays if `domain` corresponds to a `MultiDomain`
Returns
-------
Field / MultiField : the newly created field
"""
if isinstance(domain, (dict, MultiDomain)): if isinstance(domain, (dict, MultiDomain)):
return MultiField.from_local_data(domain, arr) return MultiField.from_local_data(domain, arr)
return Field.from_local_data(domain, arr) return Field.from_local_data(domain, arr)
def makeDomain(domain): def makeDomain(domain):
"""Convenience function creating DomainTuples/MultiDomains Domainoids.
Parameters
----------
domain : Domainoid (can be DomainTuple, MultiDomain, dict, Domain or list of Domains)
the description of the requested (multi-)domain
Returns
-------
DomainTuple / MultiDomain : the newly created domain object
"""
if isinstance(domain, (MultiDomain, dict)): if isinstance(domain, (MultiDomain, dict)):
return MultiDomain.make(domain) return MultiDomain.make(domain)
return DomainTuple.make(domain) return DomainTuple.make(domain)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment