Source code for nifty8.probing
# 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/>.
#
# Copyright(C) 2013-2019 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik.
from .multi_field import MultiField
from .operators.endomorphic_operator import EndomorphicOperator
from .operators.operator import Operator
from .sugar import from_random, makeField
[docs]
class StatCalculator:
"""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).
"""
[docs]
def __init__(self):
self._count = 0
[docs]
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
self._M2 = 0.*value
else:
delta = value - self._mean
self._mean = self.mean + delta*(1./self._count)
delta2 = value - self._mean
self._M2 = self._M2 + delta*delta2
@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))
[docs]
def probe_with_posterior_samples(op, post_op, nprobes, dtype):
'''FIXME
Parameters
----------
op : EndomorphicOperator
FIXME
post_op : Operator
FIXME
nprobes : int
Number of samples which shall be drawn.
dtype :
the data type of the samples
Returns
-------
List of :class:`nifty8.field.Field`
List of two fields: the mean and the variance.
'''
if not isinstance(op, EndomorphicOperator):
raise TypeError
if post_op is not None:
if not isinstance(post_op, Operator):
raise TypeError
if post_op.domain is not op.target:
raise ValueError
sc = StatCalculator()
for i in range(nprobes):
if post_op is None:
sc.add(op.draw_sample(from_inverse=True))
else:
sc.add(post_op(op.draw_sample(from_inverse=True)))
if nprobes == 1:
return sc.mean, None
return sc.mean, sc.var
[docs]
def probe_diagonal(op, nprobes, random_type="pm1"):
'''Probes the diagonal of an endomorphic operator.
The operator is called on a user-specified number of randomly generated
input vectors :math:`v_i`, producing :math:`r_i`. The estimated diagonal
is the mean of :math:`r_i^\\dagger v_i`.
Parameters
----------
op: EndomorphicOperator
The operator to be probed.
nprobes: int
The number of probes to be used.
random_type: str
The kind of random number distribution to be used for the probing.
The default value `pm1` causes the probing vector to be randomly
filled with values of +1 and -1.
Returns
-------
:class:`nifty8.field.Field`
The estimated diagonal.
'''
sc = StatCalculator()
for i in range(nprobes):
x = from_random(op.domain, random_type)
sc.add(op(x).conjugate()*x)
return sc.mean
[docs]
def approximation2endo(op, nsamples):
sc = StatCalculator()
for _ in range(nsamples):
sc.add(op.draw_sample())
approx = sc.var
dct = approx.to_dict()
for kk in dct:
foo = dct[kk].val_rw()
foo[foo == 0] = 1
dct[kk] = makeField(dct[kk].domain, foo)
return MultiField.from_dict(dct)