Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ift
NIFTy
Commits
e196bdc5
Commit
e196bdc5
authored
Jan 10, 2019
by
Martin Reinecke
Browse files
Merge remote-tracking branch 'origin/NIFTy_5' into move_dynamic_prior
parents
268962e3
1f18344d
Changes
8
Hide whitespace changes
Inline
Side-by-side
docs/source/code.rst
View file @
e196bdc5
...
...
@@ -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 a
n
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)``
...
...
nifty5/linearization.py
View file @
e196bdc5
...
...
@@ -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
):
...
...
nifty5/minimization/energy_adapter.py
View file @
e196bdc5
...
...
@@ -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
...
...
nifty5/minimization/iteration_controllers.py
View file @
e196bdc5
...
...
@@ -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
...
...
nifty5/minimization/scipy_minimizer.py
View file @
e196bdc5
...
...
@@ -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
...
...
nifty5/operators/simple_linear_operators.py
View file @
e196bdc5
...
...
@@ -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
...
...
nifty5/probing.py
View file @
e196bdc5
...
...
@@ -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
))
...
...
nifty5/sugar.py
View file @
e196bdc5
...
...
@@ -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
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment