energy_operators.py 14.1 KB
Newer Older
Martin Reinecke's avatar
Martin Reinecke committed
1
2
3
4
5
6
7
8
9
10
11
12
13
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
14
# Copyright(C) 2013-2019 Max-Planck-Society
Martin Reinecke's avatar
Martin Reinecke committed
15
#
16
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik.
Martin Reinecke's avatar
Martin Reinecke committed
17

Philipp Arras's avatar
Philipp Arras committed
18
19
import numpy as np

Philipp Arras's avatar
Philipp Arras committed
20
from .. import utilities
Martin Reinecke's avatar
Martin Reinecke committed
21
from ..domain_tuple import DomainTuple
Philipp Arras's avatar
Philipp Arras committed
22
23
from ..field import Field
from ..linearization import Linearization
Philipp Arras's avatar
Philipp Arras committed
24
25
26
from ..multi_domain import MultiDomain
from ..multi_field import MultiField
from ..sugar import makeDomain, makeOp
Philipp Arras's avatar
Philipp Arras committed
27
from .linear_operator import LinearOperator
Martin Reinecke's avatar
Martin Reinecke committed
28
from .operator import Operator
Martin Reinecke's avatar
fix    
Martin Reinecke committed
29
from .sampling_enabler import SamplingEnabler
30
from .scaling_operator import ScalingOperator
Philipp Arras's avatar
Philipp Arras committed
31
from .simple_linear_operators import VdotOperator
Martin Reinecke's avatar
Martin Reinecke committed
32
33
34


class EnergyOperator(Operator):
Philipp Arras's avatar
Philipp Arras committed
35
    """Operator which has a scalar domain as target domain.
36

Martin Reinecke's avatar
Martin Reinecke committed
37
    It is intended as an objective function for field inference.
38

Philipp Arras's avatar
Philipp Arras committed
39
40
41
    Examples
    --------
     - Information Hamiltonian, i.e. negative-log-probabilities.
Martin Reinecke's avatar
Martin Reinecke committed
42
     - Gibbs free energy, i.e. an averaged Hamiltonian, aka Kullback-Leibler
Philipp Arras's avatar
Philipp Arras committed
43
       divergence.
44
    """
Martin Reinecke's avatar
Martin Reinecke committed
45
46
47
    _target = DomainTuple.scalar_domain()


48
49
class Squared2NormOperator(EnergyOperator):
    """Computes the square of the L2-norm of the output of an operator.
50

Philipp Arras's avatar
Philipp Arras committed
51
52
53
    Parameters
    ----------
    domain : Domain, DomainTuple or tuple of Domain
54
        Domain of the operator in which the L2-norm shall be computed.
Martin Reinecke's avatar
Martin Reinecke committed
55
    """
Philipp Arras's avatar
Philipp Arras committed
56

Martin Reinecke's avatar
Martin Reinecke committed
57
58
59
    def __init__(self, domain):
        self._domain = domain

Philipp Arras's avatar
Philipp Arras committed
60
    def apply(self, x):
61
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
62
63
        if not isinstance(x, Linearization):
            res = x.vdot(x)
Philipp Arras's avatar
Philipp Arras committed
64
            return res
Philipp Arras's avatar
Philipp Arras committed
65
66
        res = x.val.vdot(x.val)
        return x.new(res, VdotOperator(2*x.val))
Martin Reinecke's avatar
Martin Reinecke committed
67

Martin Reinecke's avatar
Martin Reinecke committed
68

Martin Reinecke's avatar
Martin Reinecke committed
69
class QuadraticFormOperator(EnergyOperator):
Philipp Arras's avatar
Philipp Arras committed
70
    """Computes the L2-norm of a Field or MultiField with respect to a
71
    specific kernel given by `endo`.
Philipp Arras's avatar
Philipp Arras committed
72
73
74

    .. math ::
        E(f) = \\frac12 f^\\dagger \\text{endo}(f)
75
76
77

    Parameters
    ----------
Philipp Arras's avatar
Philipp Arras committed
78
    endo : EndomorphicOperator
79
         Kernel of the quadratic form
Martin Reinecke's avatar
Martin Reinecke committed
80
    """
Philipp Arras's avatar
Philipp Arras committed
81
82

    def __init__(self, endo):
Martin Reinecke's avatar
Martin Reinecke committed
83
        from .endomorphic_operator import EndomorphicOperator
Philipp Arras's avatar
Philipp Arras committed
84
        if not isinstance(endo, EndomorphicOperator):
Martin Reinecke's avatar
Martin Reinecke committed
85
            raise TypeError("op must be an EndomorphicOperator")
Philipp Arras's avatar
Philipp Arras committed
86
87
        self._op = endo
        self._domain = endo.domain
Martin Reinecke's avatar
Martin Reinecke committed
88

Philipp Arras's avatar
Philipp Arras committed
89
    def apply(self, x):
90
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
91
92
93
94
        if not isinstance(x, Linearization):
            return 0.5*x.vdot(self._op(x))
        res = 0.5*x.val.vdot(self._op(x.val))
        return x.new(res, VdotOperator(self._op(x.val)))
Martin Reinecke's avatar
Martin Reinecke committed
95

Philipp Arras's avatar
Philipp Arras committed
96

97
class VariableCovarianceGaussianEnergy(EnergyOperator):
Reimar Leike's avatar
Reimar Leike committed
98
    """Computes the negative log pdf of a Gaussian with unknown covariance.
99

Reimar Leike's avatar
Reimar Leike committed
100
    The covariance is assumed to be diagonal.
101
102

    .. math ::
Reimar Leike's avatar
Reimar Leike committed
103
        E(s,D) = - \\log G(s, D) = 0.5 (s)^\\dagger D^{-1} (s) + 0.5 tr log(D),
104
105

    an information energy for a Gaussian distribution with residual s and
106
    diagonal covariance D.
Reimar Leike's avatar
Reimar Leike committed
107
108
    The domain of this energy will be a MultiDomain with two keys,
    the target will be the scalar domain.
109
110
111

    Parameters
    ----------
112
    domain : Domain, DomainTuple, tuple of Domain
Reimar Leike's avatar
Reimar Leike committed
113
        domain of the residual and domain of the covariance diagonal.
114

115
    residual : key
Philipp Arras's avatar
Philipp Arras committed
116
        Residual key of the Gaussian.
117

Philipp Arras's avatar
Philipp Arras committed
118
    inverse_covariance : key
119
        Inverse covariance diagonal key of the Gaussian.
120
121
    """

Philipp Arras's avatar
Philipp Arras committed
122
123
124
125
126
    def __init__(self, domain, residual_key, inverse_covariance_key):
        self._r = str(residual_key)
        self._icov = str(inverse_covariance_key)
        dom = DomainTuple.make(domain)
        self._domain = MultiDomain.make({self._r: dom, self._icov: dom})
127

Philipp Arras's avatar
Philipp Arras committed
128
    def apply(self, x):
129
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
130
        res = 0.5*(x[self._r].vdot(x[self._r]*x[self._icov]).real - x[self._icov].log().sum())
Philipp Arras's avatar
Philipp Arras committed
131
        if not isinstance(x, Linearization) or not x.want_metric:
Philipp Arras's avatar
Philipp Arras committed
132
            return res
Philipp Arras's avatar
Philipp Arras committed
133
        mf = {self._r: x.val[self._icov], self._icov: .5*x.val[self._icov]**(-2)}
Philipp Arras's avatar
Philipp Arras committed
134
        return res.add_metric(makeOp(MultiField.from_dict(mf)))
135

Martin Reinecke's avatar
Martin Reinecke committed
136
137

class GaussianEnergy(EnergyOperator):
Philipp Arras's avatar
Docs    
Philipp Arras committed
138
    """Computes a negative-log Gaussian.
139

Philipp Arras's avatar
Philipp Arras committed
140
    Represents up to constants in :math:`m`:
Martin Reinecke's avatar
Martin Reinecke committed
141

Philipp Arras's avatar
Philipp Arras committed
142
143
    .. math ::
        E(f) = - \\log G(f-m, D) = 0.5 (f-m)^\\dagger D^{-1} (f-m),
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
144

Philipp Arras's avatar
Philipp Arras committed
145
146
    an information energy for a Gaussian distribution with mean m and
    covariance D.
147

Philipp Arras's avatar
Philipp Arras committed
148
149
150
151
    Parameters
    ----------
    mean : Field
        Mean of the Gaussian. Default is 0.
152
153
    inverse_covariance : LinearOperator
        Inverse covariance of the Gaussian. Default is the identity operator.
Philipp Arras's avatar
Fixup    
Philipp Arras committed
154
    domain : Domain, DomainTuple, tuple of Domain or MultiDomain
Philipp Arras's avatar
Philipp Arras committed
155
156
157
158
159
160
        Operator domain. By default it is inferred from `mean` or
        `covariance` if specified

    Note
    ----
    At least one of the arguments has to be provided.
Martin Reinecke's avatar
Martin Reinecke committed
161
    """
Martin Reinecke's avatar
Martin Reinecke committed
162

163
    def __init__(self, mean=None, inverse_covariance=None, domain=None):
Martin Reinecke's avatar
Martin Reinecke committed
164
165
        if mean is not None and not isinstance(mean, (Field, MultiField)):
            raise TypeError
166
        if inverse_covariance is not None and not isinstance(inverse_covariance, LinearOperator):
Philipp Arras's avatar
Philipp Arras committed
167
168
            raise TypeError

Martin Reinecke's avatar
Martin Reinecke committed
169
170
171
        self._domain = None
        if mean is not None:
            self._checkEquivalence(mean.domain)
172
173
        if inverse_covariance is not None:
            self._checkEquivalence(inverse_covariance.domain)
Martin Reinecke's avatar
Martin Reinecke committed
174
175
176
177
178
        if domain is not None:
            self._checkEquivalence(domain)
        if self._domain is None:
            raise ValueError("no domain given")
        self._mean = mean
179
        if inverse_covariance is None:
180
            self._op = Squared2NormOperator(self._domain).scale(0.5)
Philipp Arras's avatar
Philipp Arras committed
181
            self._met = ScalingOperator(self._domain, 1)
Martin Reinecke's avatar
Martin Reinecke committed
182
        else:
183
            self._op = QuadraticFormOperator(inverse_covariance)
Philipp Arras's avatar
Philipp Arras committed
184
            self._met = inverse_covariance
Martin Reinecke's avatar
Martin Reinecke committed
185
186

    def _checkEquivalence(self, newdom):
Martin Reinecke's avatar
fix    
Martin Reinecke committed
187
        newdom = makeDomain(newdom)
Martin Reinecke's avatar
Martin Reinecke committed
188
        if self._domain is None:
Philipp Arras's avatar
Philipp Arras committed
189
            self._domain = newdom
Martin Reinecke's avatar
Martin Reinecke committed
190
        else:
Philipp Arras's avatar
Philipp Arras committed
191
            if self._domain != newdom:
Martin Reinecke's avatar
Martin Reinecke committed
192
193
                raise ValueError("domain mismatch")

Philipp Arras's avatar
Philipp Arras committed
194
    def apply(self, x):
195
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
196
        residual = x if self._mean is None else x - self._mean
Philipp Arras's avatar
Changes    
Philipp Arras committed
197
        res = self._op(residual).real
Philipp Arras's avatar
Philipp Arras committed
198
199
200
        if isinstance(x, Linearization) and x.want_metric:
            return res.add_metric(self._met)
        return res
Martin Reinecke's avatar
Martin Reinecke committed
201
202
203


class PoissonianEnergy(EnergyOperator):
Philipp Arras's avatar
Docs    
Philipp Arras committed
204
205
    """Computes likelihood Hamiltonians of expected count field constrained by
    Poissonian count data.
206

Philipp Arras's avatar
Philipp Arras committed
207
    Represents up to an f-independent term :math:`log(d!)`:
208

Philipp Arras's avatar
Philipp Arras committed
209
210
    .. math ::
        E(f) = -\\log \\text{Poisson}(d|f) = \\sum f - d^\\dagger \\log(f),
211

Philipp Arras's avatar
Philipp Arras committed
212
    where f is a :class:`Field` in data space with the expectation values for
Martin Reinecke's avatar
Martin Reinecke committed
213
    the counts.
Philipp Arras's avatar
Philipp Arras committed
214
215
216
217
218
219

    Parameters
    ----------
    d : Field
        Data field with counts. Needs to have integer dtype and all field
        values need to be non-negative.
Martin Reinecke's avatar
Martin Reinecke committed
220
    """
Philipp Arras's avatar
Philipp Arras committed
221

222
    def __init__(self, d):
Philipp Arras's avatar
Philipp Arras committed
223
224
        if not isinstance(d, Field) or not np.issubdtype(d.dtype, np.integer):
            raise TypeError
Martin Reinecke's avatar
stage2    
Martin Reinecke committed
225
        if np.any(d.val < 0):
Philipp Arras's avatar
Philipp Arras committed
226
            raise ValueError
227
228
        self._d = d
        self._domain = DomainTuple.make(d.domain)
Martin Reinecke's avatar
Martin Reinecke committed
229

Philipp Arras's avatar
Philipp Arras committed
230
    def apply(self, x):
231
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
232
        res = x.sum() - x.log().vdot(self._d)
Philipp Arras's avatar
Philipp Arras committed
233
        if not isinstance(x, Linearization) or not x.want_metric:
234
            return res
Philipp Arras's avatar
Philipp Arras committed
235
        return res.add_metric(makeOp(1./x.val))
Martin Reinecke's avatar
Martin Reinecke committed
236

237

238
class InverseGammaLikelihood(EnergyOperator):
Philipp Arras's avatar
Docs    
Philipp Arras committed
239
    """Computes the negative log-likelihood of the inverse gamma distribution.
240
241
242

    It negative log-pdf(x) is given by

Martin Reinecke's avatar
Martin Reinecke committed
243
244
245
246
247
248
249
    .. math ::

        \\sum_i (\\alpha_i+1)*\\ln(x_i) + \\beta_i/x_i

    This is the likelihood for the variance :math:`x=S_k` given data
    :math:`\\beta = 0.5 |s_k|^2` where the Field :math:`s` is known to have
    the covariance :math:`S_k`.
250
251
252
253
254
255
256

    Parameters
    ----------
    beta : Field
        beta parameter of the inverse gamma distribution
    alpha : Scalar, Field, optional
        alpha parameter of the inverse gamma distribution
257
    """
Philipp Arras's avatar
Philipp Arras committed
258

259
260
    def __init__(self, beta, alpha=-0.5):
        if not isinstance(beta, Field):
Philipp Arras's avatar
Philipp Arras committed
261
            raise TypeError
Philipp Arras's avatar
Philipp Arras committed
262
        self._domain = DomainTuple.make(beta.domain)
263
264
        self._beta = beta
        if np.isscalar(alpha):
Martin Reinecke's avatar
stage2    
Martin Reinecke committed
265
            alpha = Field(beta.domain, np.full(beta.shape, alpha))
266
267
268
        elif not isinstance(alpha, Field):
            raise TypeError
        self._alphap1 = alpha+1
269

Philipp Arras's avatar
Philipp Arras committed
270
    def apply(self, x):
271
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
272
        res = x.log().vdot(self._alphap1) + x.one_over().vdot(self._beta)
Philipp Arras's avatar
Philipp Arras committed
273
        if not isinstance(x, Linearization) or not x.want_metric:
274
            return res
Philipp Arras's avatar
Philipp Arras committed
275
        return res.add_metric(makeOp(self._alphap1/(x.val**2)))
276
277


278
class StudentTEnergy(EnergyOperator):
Lukas Platz's avatar
Lukas Platz committed
279
    """Computes likelihood energy corresponding to Student's t-distribution.
280
281

    .. math ::
Lukas Platz's avatar
Lukas Platz committed
282
283
        E_\\theta(f) = -\\log \\text{StudentT}_\\theta(f)
                     = \\frac{\\theta + 1}{2} \\log(1 + \\frac{f^2}{\\theta}),
284

Lukas Platz's avatar
Lukas Platz committed
285
    where f is a field defined on `domain`.
286
287
288

    Parameters
    ----------
Lukas Platz's avatar
Lukas Platz committed
289
290
    domain : `Domain` or `DomainTuple`
        Domain of the operator
291
292
293
294
295
296
297
298
    theta : Scalar
        Degree of freedom parameter for the student t distribution
    """

    def __init__(self, domain, theta):
        self._domain = DomainTuple.make(domain)
        self._theta = theta

Philipp Arras's avatar
Philipp Arras committed
299
    def apply(self, x):
300
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
301
        res = ((self._theta+1)/2)*(x**2/self._theta).log1p().sum()
Philipp Arras's avatar
Philipp Arras committed
302
        if not isinstance(x, Linearization) or not x.want_metric:
Philipp Arras's avatar
Philipp Arras committed
303
            return res
304
        met = ScalingOperator(self.domain, (self._theta+1) / (self._theta+3))
Philipp Arras's avatar
Philipp Arras committed
305
        return res.add_metric(met)
306
307


Martin Reinecke's avatar
Martin Reinecke committed
308
class BernoulliEnergy(EnergyOperator):
Philipp Arras's avatar
Philipp Arras committed
309
    """Computes likelihood energy of expected event frequency constrained by
310
311
    event data.

Philipp Arras's avatar
Philipp Arras committed
312
313
314
315
316
317
318
    .. math ::
        E(f) = -\\log \\text{Bernoulli}(d|f)
             = -d^\\dagger \\log f  - (1-d)^\\dagger \\log(1-f),

    where f is a field defined on `d.domain` with the expected
    frequencies of events.

319
320
    Parameters
    ----------
Martin Reinecke's avatar
Martin Reinecke committed
321
    d : Field
Philipp Arras's avatar
Philipp Arras committed
322
        Data field with events (1) or non-events (0).
Martin Reinecke's avatar
Martin Reinecke committed
323
    """
Philipp Arras's avatar
Philipp Arras committed
324

325
    def __init__(self, d):
Philipp Arras's avatar
Philipp Arras committed
326
327
        if not isinstance(d, Field) or not np.issubdtype(d.dtype, np.integer):
            raise TypeError
Martin Reinecke's avatar
stage2    
Martin Reinecke committed
328
        if not np.all(np.logical_or(d.val == 0, d.val == 1)):
Philipp Arras's avatar
Philipp Arras committed
329
            raise ValueError
Martin Reinecke's avatar
Martin Reinecke committed
330
        self._d = d
331
        self._domain = DomainTuple.make(d.domain)
Martin Reinecke's avatar
Martin Reinecke committed
332

Philipp Arras's avatar
Philipp Arras committed
333
    def apply(self, x):
334
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
335
        res = -x.log().vdot(self._d) + (1.-x).log().vdot(self._d-1.)
Philipp Arras's avatar
Philipp Arras committed
336
        if not isinstance(x, Linearization) or not x.want_metric:
Philipp Arras's avatar
Philipp Arras committed
337
            return res
Philipp Arras's avatar
Philipp Arras committed
338
        return res.add_metric(makeOp(1./(x.val*(1. - x.val))))
Martin Reinecke's avatar
Martin Reinecke committed
339
340


341
class StandardHamiltonian(EnergyOperator):
Philipp Arras's avatar
Philipp Arras committed
342
343
    """Computes an information Hamiltonian in its standard form, i.e. with the
    prior being a Gaussian with unit covariance.
344

Philipp Arras's avatar
Philipp Arras committed
345
    Let the likelihood energy be :math:`E_{lh}`. Then this operator computes:
346

Philipp Arras's avatar
Philipp Arras committed
347
348
    .. math ::
         H(f) = 0.5 f^\\dagger f + E_{lh}(f):
349

Martin Reinecke's avatar
Martin Reinecke committed
350
    Other field priors can be represented via transformations of a white
351
352
    Gaussian field into a field with the desired prior probability structure.

Martin Reinecke's avatar
Martin Reinecke committed
353
    By implementing prior information this way, the field prior is represented
354
355
356
    by a generative model, from which NIFTy can draw samples and infer a field
    using the Maximum a Posteriori (MAP) or the Variational Bayes (VB) method.

Philipp Arras's avatar
Philipp Arras committed
357
358
359
360
361
362
363
364
    The metric of this operator can be used as covariance for drawing Gaussian
    samples.

    Parameters
    ----------
    lh : EnergyOperator
        The likelihood energy.
    ic_samp : IterationController
365
        Tells an internal :class:`SamplingEnabler` which convergence criterion
Philipp Arras's avatar
Philipp Arras committed
366
367
368
369
370
371
        to use to draw Gaussian samples.

    See also
    --------
    `Encoding prior knowledge in the structure of the likelihood`,
    Jakob Knollmüller, Torsten A. Ensslin,
Martin Reinecke's avatar
Martin Reinecke committed
372
    `<https://arxiv.org/abs/1812.04403>`_
Martin Reinecke's avatar
Martin Reinecke committed
373
    """
Philipp Arras's avatar
Philipp Arras committed
374

375
    def __init__(self, lh, ic_samp=None, _c_inp=None):
Martin Reinecke's avatar
Martin Reinecke committed
376
377
        self._lh = lh
        self._prior = GaussianEnergy(domain=lh.domain)
378
379
        if _c_inp is not None:
            _, self._prior = self._prior.simplify_for_constant_input(_c_inp)
Martin Reinecke's avatar
Martin Reinecke committed
380
        self._ic_samp = ic_samp
Martin Reinecke's avatar
Martin Reinecke committed
381
        self._domain = lh.domain
Martin Reinecke's avatar
Martin Reinecke committed
382

Philipp Arras's avatar
Philipp Arras committed
383
    def apply(self, x):
384
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
385
        if not isinstance(x, Linearization) or not x.want_metric or self._ic_samp is None:
Philipp Arras's avatar
Philipp Arras committed
386
            return (self._lh + self._prior)(x)
Philipp Arras's avatar
Philipp Arras committed
387
388
        lhx, prx = self._lh(x), self._prior(x)
        return (lhx+prx).add_metric(SamplingEnabler(lhx.metric, prx.metric, self._ic_samp))
Martin Reinecke's avatar
Martin Reinecke committed
389

Philipp Arras's avatar
Philipp Arras committed
390
391
    def __repr__(self):
        subs = 'Likelihood:\n{}'.format(utilities.indent(self._lh.__repr__()))
392
        subs += '\nPrior:\n{}'.format(self._prior)
Martin Reinecke's avatar
Martin Reinecke committed
393
        return 'StandardHamiltonian:\n' + utilities.indent(subs)
Philipp Arras's avatar
Philipp Arras committed
394

395
396
397
398
    def _simplify_for_constant_input_nontrivial(self, c_inp):
        out, lh1 = self._lh.simplify_for_constant_input(c_inp)
        return out, StandardHamiltonian(lh1, self._ic_samp, _c_inp=c_inp)

Martin Reinecke's avatar
Martin Reinecke committed
399

Martin Reinecke's avatar
Martin Reinecke committed
400
class AveragedEnergy(EnergyOperator):
Philipp Arras's avatar
Docs    
Philipp Arras committed
401
    """Averages an energy over samples.
Martin Reinecke's avatar
Martin Reinecke committed
402

403
404
405
    Parameters
    ----------
    h: Hamiltonian
Philipp Arras's avatar
Philipp Arras committed
406
       The energy to be averaged.
Martin Reinecke's avatar
Martin Reinecke committed
407
    res_samples : iterable of Fields
Torsten Ensslin's avatar
Torsten Ensslin committed
408
409
       Set of residual sample points to be added to mean field for
       approximate estimation of the KL.
410

Philipp Arras's avatar
Docs    
Philipp Arras committed
411
412
413
414
415
    Notes
    -----
    - Having symmetrized residual samples, with both :math:`v_i` and
      :math:`-v_i` being present, ensures that the distribution mean is
      exactly represented.
Torsten Ensslin's avatar
Fix te    
Torsten Ensslin committed
416

Philipp Arras's avatar
Docs    
Philipp Arras committed
417
418
419
    - :class:`AveragedEnergy(h)` approximates
      :math:`\\left< H(f) \\right>_{G(f-m,D)}` if the residuals :math:`f-m`
      are drawn from a Gaussian distribution with covariance :math:`D`.
Martin Reinecke's avatar
Martin Reinecke committed
420
    """
Martin Reinecke's avatar
Martin Reinecke committed
421
422
423

    def __init__(self, h, res_samples):
        self._h = h
Martin Reinecke's avatar
Martin Reinecke committed
424
        self._domain = h.domain
Martin Reinecke's avatar
Martin Reinecke committed
425
426
        self._res_samples = tuple(res_samples)

Philipp Arras's avatar
Philipp Arras committed
427
    def apply(self, x):
428
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
429
430
        mymap = map(lambda v: self._h(x+v), self._res_samples)
        return utilities.my_sum(mymap)/len(self._res_samples)