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

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
describing its output, and finally an overloaded `apply` method, which can
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`
- a :class:`Linearization` object, in which case it returns the transformed
:class:`Linearization`
This is the interface that all objects derived from :class:`Operator` must implement.
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.
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
================
......@@ -205,8 +217,8 @@ additional functionality which is not available for the more generic :class:`Ope
class.
Operator basics
---------------
Linear Operator basics
----------------------
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
are equivalent.)
These different actions of an operator ``Op`` on a field ``f`` can be invoked
in various ways:
These different actions of a linear operator ``Op`` on a field ``f`` can be
invoked in various ways:
- 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)``
......
......@@ -24,6 +24,22 @@ from .operators.scaling_operator import ScalingOperator
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):
self._val = val
self._jac = jac
......@@ -33,36 +49,63 @@ class Linearization(object):
self._metric = metric
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)
@property
def domain(self):
"""DomainTuple/MultiDomain : the Jacobian's domain"""
return self._jac.domain
@property
def target(self):
"""DomainTuple/MultiDomain : the Jacobian's target (i.e. the value's domain)"""
return self._jac.target
@property
def val(self):
"""Field/MultiField : the value"""
return self._val
@property
def jac(self):
"""LinearOperator : the Jacobian"""
return self._jac
@property
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.))
@property
def want_metric(self):
"""bool : the value of `want_metric`"""
return self._want_metric
@property
def metric(self):
"""Only available if target is a scalar"""
"""LinearOperator : the metric
Notes
-----
Only available if target is a scalar
"""
return self._metric
def __getitem__(self, name):
......
......@@ -20,6 +20,25 @@ from ..minimization.energy import 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):
super(EnergyAdapter, self).__init__(position)
self._op = op
......
......@@ -143,7 +143,24 @@ class GradientNormController(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):
self._tol = tol
self._convergence_level = convergence_level
......@@ -185,6 +202,25 @@ class GradInfNormController(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,
iteration_limit=None, name=None):
self._tol_rel_deltaE = tol_rel_deltaE
......
......@@ -148,6 +148,12 @@ def L_BFGS_B(ftol, gtol, maxiter, maxcor=10, disp=False, bounds=None):
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):
if not dobj.is_numpy():
raise NotImplementedError
......
......@@ -25,6 +25,13 @@ from .linear_operator import 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):
self._field = field
self._domain = field.domain
......@@ -39,6 +46,7 @@ class VdotOperator(LinearOperator):
class ConjugationOperator(EndomorphicOperator):
"""Operator computing the complex conjugate of its input."""
def __init__(self, domain):
self._domain = DomainTuple.make(domain)
self._capability = self._all_ops
......@@ -49,6 +57,7 @@ class ConjugationOperator(EndomorphicOperator):
class Realizer(EndomorphicOperator):
"""Operator returning the real component of its input."""
def __init__(self, domain):
self._domain = DomainTuple.make(domain)
self._capability = self.TIMES | self.ADJOINT_TIMES
......@@ -100,6 +109,41 @@ class FieldAdapter(LinearOperator):
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 .operator import Operator
if left is None: # need to infer left from right
......
......@@ -19,10 +19,26 @@ from .field import Field
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):
self._count = 0
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
if self._count == 1:
self._mean = 1.*value
......@@ -35,12 +51,18 @@ class StatCalculator(object):
@property
def mean(self):
"""
value type : the mean of all samples added so far.
"""
if self._count == 0:
raise RuntimeError
return 1.*self._mean
@property
def var(self):
"""
value type : the unbiased variance of all samples added so far.
"""
if self._count < 2:
raise RuntimeError
return self._M2 * (1./(self._count-1))
......
......@@ -41,6 +41,19 @@ __all__ = ['PS_field', 'power_analyze', 'create_power_operator',
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):
raise TypeError
data = dobj.from_global_data(func(pspace.k_lengths))
......@@ -202,30 +215,104 @@ def create_harmonic_smoothing_operator(domain, space, sigma):
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)):
return MultiField.full(domain, val)
return Field.full(domain, val)
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)):
return MultiField.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):
"""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)):
return MultiField.from_global_data(domain, arr, sum_up)
return Field.from_global_data(domain, arr, sum_up)
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)):
return MultiField.from_local_data(domain, arr)
return Field.from_local_data(domain, arr)
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)):
return MultiDomain.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