energy_operators.py 13.8 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
from ..field import Field
Philipp Arras's avatar
Philipp Arras committed
23
24
25
from ..multi_domain import MultiDomain
from ..multi_field import MultiField
from ..sugar import makeDomain, makeOp
Philipp Arras's avatar
Philipp Arras committed
26
from .linear_operator import LinearOperator
Martin Reinecke's avatar
Martin Reinecke committed
27
from .operator import Operator
Martin Reinecke's avatar
fix    
Martin Reinecke committed
28
from .sampling_enabler import SamplingEnabler
29
from .scaling_operator import ScalingOperator
Philipp Arras's avatar
Philipp Arras committed
30
from .simple_linear_operators import VdotOperator
Martin Reinecke's avatar
Martin Reinecke committed
31
32
33


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

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

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


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

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

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

Philipp Arras's avatar
Philipp Arras committed
59
    def apply(self, x):
60
        self._check_input(x)
Martin Reinecke's avatar
more    
Martin Reinecke committed
61
        res = x.fld.vdot(x.fld)
Martin Reinecke's avatar
more    
Martin Reinecke committed
62
        if x.jac is None:
Martin Reinecke's avatar
more    
Martin Reinecke committed
63
64
            return res
        return x.new(res, VdotOperator(2*x.fld))
Martin Reinecke's avatar
Martin Reinecke committed
65

Martin Reinecke's avatar
Martin Reinecke committed
66

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

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

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

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

Philipp Arras's avatar
Philipp Arras committed
87
    def apply(self, x):
88
        self._check_input(x)
Martin Reinecke's avatar
more    
Martin Reinecke committed
89
        res = 0.5*x.fld.vdot(self._op(x.fld))
Martin Reinecke's avatar
more    
Martin Reinecke committed
90
        if x.jac is None:
Martin Reinecke's avatar
more    
Martin Reinecke committed
91
92
            return res
        return x.new(res, VdotOperator(self._op(x.fld)))
Martin Reinecke's avatar
Martin Reinecke committed
93

Philipp Arras's avatar
Philipp Arras committed
94

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

Reimar Leike's avatar
Reimar Leike committed
98
    The covariance is assumed to be diagonal.
99
100

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

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

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

113
    residual : key
Philipp Arras's avatar
Philipp Arras committed
114
        Residual key of the Gaussian.
115

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

Philipp Arras's avatar
Philipp Arras committed
120
121
122
123
124
    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})
125

Philipp Arras's avatar
Philipp Arras committed
126
    def apply(self, x):
127
        self._check_input(x)
Martin Reinecke's avatar
Martin Reinecke committed
128
        res = 0.5*(x[self._r].vdot(x[self._r]*x[self._icov]).real - x[self._icov].ptw("log").sum())
Martin Reinecke's avatar
more    
Martin Reinecke committed
129
        if not x.want_metric:
Philipp Arras's avatar
Philipp Arras committed
130
            return res
Martin Reinecke's avatar
more    
Martin Reinecke committed
131
        mf = {self._r: x.fld[self._icov], self._icov: .5*x.fld[self._icov]**(-2)}
Philipp Arras's avatar
Philipp Arras committed
132
        return res.add_metric(makeOp(MultiField.from_dict(mf)))
133

Martin Reinecke's avatar
Martin Reinecke committed
134
135

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

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

Philipp Arras's avatar
Philipp Arras committed
140
141
    .. 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
142

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

Philipp Arras's avatar
Philipp Arras committed
146
147
148
149
    Parameters
    ----------
    mean : Field
        Mean of the Gaussian. Default is 0.
150
151
    inverse_covariance : LinearOperator
        Inverse covariance of the Gaussian. Default is the identity operator.
Philipp Arras's avatar
Fixup    
Philipp Arras committed
152
    domain : Domain, DomainTuple, tuple of Domain or MultiDomain
Philipp Arras's avatar
Philipp Arras committed
153
154
155
156
157
158
        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
159
    """
Martin Reinecke's avatar
Martin Reinecke committed
160

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

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

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

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


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

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

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

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

    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
218
    """
Philipp Arras's avatar
Philipp Arras committed
219

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

Philipp Arras's avatar
Philipp Arras committed
228
    def apply(self, x):
229
        self._check_input(x)
Martin Reinecke's avatar
Martin Reinecke committed
230
        res = x.sum() - x.ptw("log").vdot(self._d)
Martin Reinecke's avatar
more    
Martin Reinecke committed
231
        if not x.want_metric:
232
            return res
Martin Reinecke's avatar
more    
Martin Reinecke committed
233
        return res.add_metric(makeOp(x.fld.ptw("reciprocal")))
Martin Reinecke's avatar
Martin Reinecke committed
234

235

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

    It negative log-pdf(x) is given by

Martin Reinecke's avatar
Martin Reinecke committed
241
242
243
244
245
246
247
    .. 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`.
248
249
250
251
252
253
254

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

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

Philipp Arras's avatar
Philipp Arras committed
268
    def apply(self, x):
269
        self._check_input(x)
Martin Reinecke's avatar
Martin Reinecke committed
270
        res = x.ptw("log").vdot(self._alphap1) + x.ptw("reciprocal").vdot(self._beta)
Martin Reinecke's avatar
more    
Martin Reinecke committed
271
        if not x.want_metric:
272
            return res
Martin Reinecke's avatar
more    
Martin Reinecke committed
273
        return res.add_metric(makeOp(self._alphap1/(x.fld**2)))
274
275


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

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

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

    Parameters
    ----------
Lukas Platz's avatar
Lukas Platz committed
287
288
    domain : `Domain` or `DomainTuple`
        Domain of the operator
289
290
291
292
293
294
295
296
    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
297
    def apply(self, x):
298
        self._check_input(x)
Martin Reinecke's avatar
Martin Reinecke committed
299
        res = ((self._theta+1)/2)*(x**2/self._theta).ptw("log1p").sum()
Martin Reinecke's avatar
more    
Martin Reinecke committed
300
        if not x.want_metric:
Philipp Arras's avatar
Philipp Arras committed
301
            return res
302
        met = ScalingOperator(self.domain, (self._theta+1) / (self._theta+3))
Philipp Arras's avatar
Philipp Arras committed
303
        return res.add_metric(met)
304
305


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

Philipp Arras's avatar
Philipp Arras committed
310
311
312
313
314
315
316
    .. 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.

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

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

Philipp Arras's avatar
Philipp Arras committed
331
    def apply(self, x):
332
        self._check_input(x)
Martin Reinecke's avatar
Martin Reinecke committed
333
        res = -x.ptw("log").vdot(self._d) + (1.-x).ptw("log").vdot(self._d-1.)
Martin Reinecke's avatar
more    
Martin Reinecke committed
334
        if not x.want_metric:
Philipp Arras's avatar
Philipp Arras committed
335
            return res
Martin Reinecke's avatar
more    
Martin Reinecke committed
336
        return res.add_metric(makeOp(1./(x.fld*(1. - x.fld))))
Martin Reinecke's avatar
Martin Reinecke committed
337
338


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

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

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

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

Martin Reinecke's avatar
Martin Reinecke committed
351
    By implementing prior information this way, the field prior is represented
352
353
354
    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
355
356
357
358
359
360
361
362
    The metric of this operator can be used as covariance for drawing Gaussian
    samples.

    Parameters
    ----------
    lh : EnergyOperator
        The likelihood energy.
    ic_samp : IterationController
363
        Tells an internal :class:`SamplingEnabler` which convergence criterion
Philipp Arras's avatar
Philipp Arras committed
364
365
366
367
368
369
        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
370
    `<https://arxiv.org/abs/1812.04403>`_
Martin Reinecke's avatar
Martin Reinecke committed
371
    """
Philipp Arras's avatar
Philipp Arras committed
372

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

Philipp Arras's avatar
Philipp Arras committed
381
    def apply(self, x):
382
        self._check_input(x)
Martin Reinecke's avatar
more    
Martin Reinecke committed
383
        if not x.want_metric or self._ic_samp is None:
Philipp Arras's avatar
Philipp Arras committed
384
            return (self._lh + self._prior)(x)
Philipp Arras's avatar
Philipp Arras committed
385
386
        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
387

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

393
394
395
396
    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
397

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

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

Philipp Arras's avatar
Docs    
Philipp Arras committed
409
410
411
412
413
    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
414

Philipp Arras's avatar
Docs    
Philipp Arras committed
415
416
417
    - :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
418
    """
Martin Reinecke's avatar
Martin Reinecke committed
419
420
421

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

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