energy_operators.py 15 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
Philipp Arras's avatar
Philipp Arras committed
30
from .sandwich_operator import SandwichOperator
31
from .scaling_operator import ScalingOperator
Philipp Arras's avatar
Philipp Arras committed
32
from .simple_linear_operators import FieldAdapter, VdotOperator
Martin Reinecke's avatar
Martin Reinecke committed
33
34
35


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

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

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


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

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

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

Philipp Arras's avatar
Philipp Arras committed
61
    def apply(self, x, difforder):
62
        self._check_input(x)
Martin Reinecke's avatar
Martin Reinecke committed
63
        res = x.vdot(x)
Philipp Arras's avatar
Philipp Arras committed
64
65
66
67
        if difforder == self.VALUE_ONLY:
            return res
        jac = VdotOperator(2*x)
        return Linearization(res, jac, want_metric=difforder == self.WITH_METRIC)
Martin Reinecke's avatar
Martin Reinecke committed
68

Martin Reinecke's avatar
Martin Reinecke committed
69

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

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

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

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

Philipp Arras's avatar
Philipp Arras committed
90
    def apply(self, x, difforder):
91
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
92
        t1 = self._op(x)
Martin Reinecke's avatar
Martin Reinecke committed
93
        res = 0.5*x.vdot(t1)
Philipp Arras's avatar
Philipp Arras committed
94
95
96
        if difforder == self.VALUE_ONLY:
            return res
        return Linearization(res, VdotOperator(t1))
Martin Reinecke's avatar
Martin Reinecke committed
97

Philipp Arras's avatar
Philipp Arras committed
98

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

Reimar Leike's avatar
Reimar Leike committed
102
    The covariance is assumed to be diagonal.
103
104

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

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

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

117
    residual : key
Philipp Arras's avatar
Philipp Arras committed
118
        Residual key of the Gaussian.
119

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

Philipp Arras's avatar
Philipp Arras committed
124
125
126
127
128
    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})
129

Philipp Arras's avatar
Philipp Arras committed
130
    def apply(self, x, difforder):
131
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
132
133
134
        if difforder >= self.WITH_JAC:
            x = Linearization.make_var(x, difforder == self.WITH_METRIC)
        res = 0.5*(x[self._r].vdot(x[self._r]*x[self._icov]).real - x[self._icov].log().sum())
Martin Reinecke's avatar
Martin Reinecke committed
135
        if difforder <= self.WITH_JAC:
Philipp Arras's avatar
Philipp Arras committed
136
            return res
Philipp Arras's avatar
Philipp Arras committed
137
        mf = {self._r: x.val[self._icov], self._icov: .5*x.val[self._icov]**(-2)}
Philipp Arras's avatar
Philipp Arras committed
138
        return res.add_metric(makeOp(MultiField.from_dict(mf)))
139

Martin Reinecke's avatar
Martin Reinecke committed
140
141

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

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

Philipp Arras's avatar
Philipp Arras committed
146
147
    .. 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
148

Philipp Arras's avatar
Philipp Arras committed
149
150
    an information energy for a Gaussian distribution with mean m and
    covariance D.
151

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

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

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

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

Philipp Arras's avatar
Philipp Arras committed
198
    def apply(self, x, difforder):
199
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
200
201
        if difforder >= self.WITH_JAC:
            x = Linearization.make_var(x, difforder == self.WITH_METRIC)
Philipp Arras's avatar
Philipp Arras committed
202
        residual = x if self._mean is None else x - self._mean
Philipp Arras's avatar
Changes    
Philipp Arras committed
203
        res = self._op(residual).real
Philipp Arras's avatar
Philipp Arras committed
204
        if difforder < self.WITH_METRIC:
Martin Reinecke's avatar
Martin Reinecke committed
205
            return res
Philipp Arras's avatar
Philipp Arras committed
206
        return res.add_metric(self._met)
Martin Reinecke's avatar
Martin Reinecke committed
207
208
209


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

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

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

Philipp Arras's avatar
Philipp Arras committed
218
    where f is a :class:`Field` in data space with the expectation values for
Martin Reinecke's avatar
Martin Reinecke committed
219
    the counts.
Philipp Arras's avatar
Philipp Arras committed
220
221
222
223
224
225

    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
226
    """
Philipp Arras's avatar
Philipp Arras committed
227

228
    def __init__(self, d):
Philipp Arras's avatar
Philipp Arras committed
229
230
        if not isinstance(d, Field) or not np.issubdtype(d.dtype, np.integer):
            raise TypeError
Martin Reinecke's avatar
stage2    
Martin Reinecke committed
231
        if np.any(d.val < 0):
Philipp Arras's avatar
Philipp Arras committed
232
            raise ValueError
233
234
        self._d = d
        self._domain = DomainTuple.make(d.domain)
Martin Reinecke's avatar
Martin Reinecke committed
235

Philipp Arras's avatar
Philipp Arras committed
236
    def apply(self, x, difforder):
237
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
238
239
240
        if difforder >= self.WITH_JAC:
            x = Linearization.make_var(x, difforder == self.WITH_METRIC)
        res = x.sum() - x.log().vdot(self._d)
Martin Reinecke's avatar
Martin Reinecke committed
241
        if difforder <= self.WITH_JAC:
242
            return res
Philipp Arras's avatar
Philipp Arras committed
243
        return res.add_metric(makeOp(1./x.val))
Martin Reinecke's avatar
Martin Reinecke committed
244

245

246
class InverseGammaLikelihood(EnergyOperator):
Philipp Arras's avatar
Docs    
Philipp Arras committed
247
    """Computes the negative log-likelihood of the inverse gamma distribution.
248
249
250

    It negative log-pdf(x) is given by

Martin Reinecke's avatar
Martin Reinecke committed
251
252
253
254
255
256
257
    .. 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`.
258
259
260
261
262
263
264

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

267
268
    def __init__(self, beta, alpha=-0.5):
        if not isinstance(beta, Field):
Philipp Arras's avatar
Philipp Arras committed
269
            raise TypeError
Philipp Arras's avatar
Philipp Arras committed
270
        self._domain = DomainTuple.make(beta.domain)
271
272
        self._beta = beta
        if np.isscalar(alpha):
Martin Reinecke's avatar
stage2    
Martin Reinecke committed
273
            alpha = Field(beta.domain, np.full(beta.shape, alpha))
274
275
276
        elif not isinstance(alpha, Field):
            raise TypeError
        self._alphap1 = alpha+1
277

Philipp Arras's avatar
Philipp Arras committed
278
    def apply(self, x, difforder):
279
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
280
281
282
        if difforder >= self.WITH_JAC:
            x = Linearization.make_var(x, difforder == self.WITH_METRIC)
        res = x.log().vdot(self._alphap1) + x.one_over().vdot(self._beta)
Martin Reinecke's avatar
Martin Reinecke committed
283
        if difforder <= self.WITH_JAC:
284
            return res
Philipp Arras's avatar
Philipp Arras committed
285
        return res.add_metric(makeOp(self._alphap1/(x.val**2)))
286
287


288
class StudentTEnergy(EnergyOperator):
Lukas Platz's avatar
Lukas Platz committed
289
    """Computes likelihood energy corresponding to Student's t-distribution.
290
291

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

Lukas Platz's avatar
Lukas Platz committed
295
    where f is a field defined on `domain`.
296
297
298

    Parameters
    ----------
Lukas Platz's avatar
Lukas Platz committed
299
300
    domain : `Domain` or `DomainTuple`
        Domain of the operator
301
302
303
304
305
306
307
308
    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
309
    def apply(self, x, difforder):
310
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
311
312
313
        if difforder >= self.WITH_JAC:
            x = Linearization.make_var(x, difforder == self.WITH_METRIC)
        res = ((self._theta+1)/2)*(x**2/self._theta).log1p().sum()
Martin Reinecke's avatar
Martin Reinecke committed
314
        if difforder <= self.WITH_JAC:
Philipp Arras's avatar
Philipp Arras committed
315
            return res
316
        met = ScalingOperator(self.domain, (self._theta+1) / (self._theta+3))
Philipp Arras's avatar
Philipp Arras committed
317
        return res.add_metric(met)
318
319


Martin Reinecke's avatar
Martin Reinecke committed
320
class BernoulliEnergy(EnergyOperator):
Philipp Arras's avatar
Philipp Arras committed
321
    """Computes likelihood energy of expected event frequency constrained by
322
323
    event data.

Philipp Arras's avatar
Philipp Arras committed
324
325
326
327
328
329
330
    .. 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.

331
332
    Parameters
    ----------
Martin Reinecke's avatar
Martin Reinecke committed
333
    d : Field
Philipp Arras's avatar
Philipp Arras committed
334
        Data field with events (1) or non-events (0).
Martin Reinecke's avatar
Martin Reinecke committed
335
    """
Philipp Arras's avatar
Philipp Arras committed
336

337
    def __init__(self, d):
Philipp Arras's avatar
Philipp Arras committed
338
339
        if not isinstance(d, Field) or not np.issubdtype(d.dtype, np.integer):
            raise TypeError
Martin Reinecke's avatar
stage2    
Martin Reinecke committed
340
        if not np.all(np.logical_or(d.val == 0, d.val == 1)):
Philipp Arras's avatar
Philipp Arras committed
341
            raise ValueError
Martin Reinecke's avatar
Martin Reinecke committed
342
        self._d = d
343
        self._domain = DomainTuple.make(d.domain)
Martin Reinecke's avatar
Martin Reinecke committed
344

Philipp Arras's avatar
Philipp Arras committed
345
    def apply(self, x, difforder):
346
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
347
348
349
        if difforder >= self.WITH_JAC:
            x = Linearization.make_var(x, difforder == self.WITH_METRIC)
        res = -x.log().vdot(self._d) + (1.-x).log().vdot(self._d-1.)
Martin Reinecke's avatar
Martin Reinecke committed
350
        if difforder <= self.WITH_JAC:
Philipp Arras's avatar
Philipp Arras committed
351
            return res
Philipp Arras's avatar
Philipp Arras committed
352
        met = makeOp(1./(x.val*(1. - x.val)))
Martin Reinecke's avatar
Martin Reinecke committed
353
        met = SandwichOperator.make(x.jac, met)
Philipp Arras's avatar
Philipp Arras committed
354
        return res.add_metric(met)
Martin Reinecke's avatar
Martin Reinecke committed
355
356


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

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

Philipp Arras's avatar
Philipp Arras committed
363
364
    .. math ::
         H(f) = 0.5 f^\\dagger f + E_{lh}(f):
365

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

Martin Reinecke's avatar
Martin Reinecke committed
369
    By implementing prior information this way, the field prior is represented
370
371
372
    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
373
374
375
376
377
378
379
380
    The metric of this operator can be used as covariance for drawing Gaussian
    samples.

    Parameters
    ----------
    lh : EnergyOperator
        The likelihood energy.
    ic_samp : IterationController
381
        Tells an internal :class:`SamplingEnabler` which convergence criterion
Philipp Arras's avatar
Philipp Arras committed
382
383
384
385
386
387
        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
388
    `<https://arxiv.org/abs/1812.04403>`_
Martin Reinecke's avatar
Martin Reinecke committed
389
    """
Philipp Arras's avatar
Philipp Arras committed
390

391
    def __init__(self, lh, ic_samp=None, _c_inp=None):
Martin Reinecke's avatar
Martin Reinecke committed
392
393
        self._lh = lh
        self._prior = GaussianEnergy(domain=lh.domain)
394
395
        if _c_inp is not None:
            _, self._prior = self._prior.simplify_for_constant_input(_c_inp)
Martin Reinecke's avatar
Martin Reinecke committed
396
        self._ic_samp = ic_samp
Martin Reinecke's avatar
Martin Reinecke committed
397
        self._domain = lh.domain
Martin Reinecke's avatar
Martin Reinecke committed
398

Philipp Arras's avatar
Philipp Arras committed
399
    def apply(self, x, difforder):
400
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
401
402
403
        if difforder >= self.WITH_JAC:
            x = Linearization.make_var(x, difforder == self.WITH_METRIC)
        if difforder <= self.WITH_JAC or self._ic_samp is None:
Philipp Arras's avatar
Philipp Arras committed
404
            return (self._lh + self._prior)(x)
Philipp Arras's avatar
Philipp Arras committed
405
406
        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
407

Philipp Arras's avatar
Philipp Arras committed
408
409
    def __repr__(self):
        subs = 'Likelihood:\n{}'.format(utilities.indent(self._lh.__repr__()))
410
        subs += '\nPrior:\n{}'.format(self._prior)
Martin Reinecke's avatar
Martin Reinecke committed
411
        return 'StandardHamiltonian:\n' + utilities.indent(subs)
Philipp Arras's avatar
Philipp Arras committed
412

413
414
415
416
    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
417

Martin Reinecke's avatar
Martin Reinecke committed
418
class AveragedEnergy(EnergyOperator):
Philipp Arras's avatar
Docs    
Philipp Arras committed
419
    """Averages an energy over samples.
Martin Reinecke's avatar
Martin Reinecke committed
420

421
422
423
    Parameters
    ----------
    h: Hamiltonian
Philipp Arras's avatar
Philipp Arras committed
424
       The energy to be averaged.
Martin Reinecke's avatar
Martin Reinecke committed
425
    res_samples : iterable of Fields
Torsten Ensslin's avatar
Torsten Ensslin committed
426
427
       Set of residual sample points to be added to mean field for
       approximate estimation of the KL.
428

Philipp Arras's avatar
Docs    
Philipp Arras committed
429
430
431
432
433
    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
434

Philipp Arras's avatar
Docs    
Philipp Arras committed
435
436
437
    - :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
438
    """
Martin Reinecke's avatar
Martin Reinecke committed
439
440
441

    def __init__(self, h, res_samples):
        self._h = h
Martin Reinecke's avatar
Martin Reinecke committed
442
        self._domain = h.domain
Martin Reinecke's avatar
Martin Reinecke committed
443
444
        self._res_samples = tuple(res_samples)

Philipp Arras's avatar
Philipp Arras committed
445
    def apply(self, x, difforder):
446
        self._check_input(x)
Philipp Arras's avatar
Philipp Arras committed
447
448
449
450
        if difforder >= self.WITH_JAC:
            x = Linearization.make_var(x, difforder == self.WITH_METRIC)
        mymap = map(lambda v: self._h(x+v), self._res_samples)
        return utilities.my_sum(mymap)/len(self._res_samples)