Merge branch 'develop' into documentation

This commit is contained in:
fjosw 2021-11-11 10:51:05 +00:00
commit ad1ac2b4d1
3 changed files with 53 additions and 39 deletions

View file

@ -137,6 +137,7 @@ from .obs import *
from .correlators import * from .correlators import *
from .fits import * from .fits import *
from . import dirac from . import dirac
from . import input
from . import linalg from . import linalg
from . import misc from . import misc
from . import mpm from . import mpm

View file

@ -1,23 +1,25 @@
import numpy as np import numpy as np
from autograd import jacobian from autograd import jacobian
import autograd.numpy as anp # Thinly-wrapped numpy import autograd.numpy as anp # Thinly-wrapped numpy
from .obs import derived_observable, CObs, Obs from .obs import derived_observable, CObs, Obs, _merge_idx, _expand_deltas_for_merge, _filter_zeroes
from functools import partial from functools import partial
from autograd.extend import defvjp from autograd.extend import defvjp
def derived_array(func, data, **kwargs): def derived_array(func, data, **kwargs):
"""Construct a derived Obs according to func(data, **kwargs) of matrix value data """Construct a derived Obs for a matrix valued function according to func(data, **kwargs) using automatic differentiation.
using automatic differentiation.
Parameters Parameters
---------- ----------
func -- arbitrary function of the form func(data, **kwargs). For the func : object
arbitrary function of the form func(data, **kwargs). For the
automatic differentiation to work, all numpy functions have to have automatic differentiation to work, all numpy functions have to have
the autograd wrapper (use 'import autograd.numpy as anp'). the autograd wrapper (use 'import autograd.numpy as anp').
data -- list of Obs, e.g. [obs1, obs2, obs3]. data : list
man_grad -- manually supply a list or an array which contains the jacobian list of Obs, e.g. [obs1, obs2, obs3].
man_grad : list
manually supply a list or an array which contains the jacobian
of func. Use cautiously, supplying the wrong derivative will of func. Use cautiously, supplying the wrong derivative will
not be intercepted. not be intercepted.
""" """
@ -30,25 +32,29 @@ def derived_array(func, data, **kwargs):
if isinstance(i_data, Obs): if isinstance(i_data, Obs):
first_name = i_data.names[0] first_name = i_data.names[0]
first_shape = i_data.shape[first_name] first_shape = i_data.shape[first_name]
first_idl = i_data.idl[first_name]
break break
for i in range(len(raveled_data)): for i in range(len(raveled_data)):
if isinstance(raveled_data[i], (int, float)): if isinstance(raveled_data[i], (int, float)):
raveled_data[i] = Obs([raveled_data[i] + np.zeros(first_shape)], [first_name]) raveled_data[i] = Obs([raveled_data[i] + np.zeros(first_shape)], [first_name], idl=[first_idl])
n_obs = len(raveled_data) n_obs = len(raveled_data)
new_names = sorted(set([y for x in [o.names for o in raveled_data] for y in x])) new_names = sorted(set([y for x in [o.names for o in raveled_data] for y in x]))
new_shape = {} is_merged = len(list(filter(lambda o: o.is_merged is True, raveled_data))) > 0
for i_data in raveled_data: reweighted = len(list(filter(lambda o: o.reweighted is True, raveled_data))) > 0
new_idl_d = {}
for name in new_names: for name in new_names:
tmp = i_data.shape.get(name) idl = []
for i_data in raveled_data:
tmp = i_data.idl.get(name)
if tmp is not None: if tmp is not None:
if new_shape.get(name) is None: idl.append(tmp)
new_shape[name] = tmp new_idl_d[name] = _merge_idx(idl)
else: if not is_merged:
if new_shape[name] != tmp: is_merged = (1 != len(set([len(idx) for idx in [*idl, new_idl_d[name]]])))
raise Exception('Shapes of ensemble', name, 'do not match.')
if data.ndim == 1: if data.ndim == 1:
values = np.array([o.value for o in data]) values = np.array([o.value for o in data])
else: else:
@ -71,8 +77,6 @@ def derived_array(func, data, **kwargs):
deriv = np.asarray(kwargs.get('man_grad')) deriv = np.asarray(kwargs.get('man_grad'))
if new_values.shape + data.shape != deriv.shape: if new_values.shape + data.shape != deriv.shape:
raise Exception('Manual derivative does not have correct shape.') raise Exception('Manual derivative does not have correct shape.')
elif kwargs.get('num_grad') is True:
raise Exception('Multi mode currently not supported for numerical derivative')
else: else:
deriv = jacobian(func)(values, **kwargs) deriv = jacobian(func)(values, **kwargs)
@ -83,7 +87,7 @@ def derived_array(func, data, **kwargs):
d_extracted[name] = [] d_extracted[name] = []
for i_dat, dat in enumerate(data): for i_dat, dat in enumerate(data):
ens_length = dat.ravel()[0].shape[name] ens_length = dat.ravel()[0].shape[name]
d_extracted[name].append(np.array([o.deltas[name] for o in dat.reshape(np.prod(dat.shape))]).reshape(dat.shape + (ens_length, ))) d_extracted[name].append(np.array([_expand_deltas_for_merge(o.deltas[name], o.idl[name], o.shape[name], new_idl_d[name]) for o in dat.reshape(np.prod(dat.shape))]).reshape(dat.shape + (ens_length, )))
for i_val, new_val in np.ndenumerate(new_values): for i_val, new_val in np.ndenumerate(new_values):
new_deltas = {} new_deltas = {}
@ -95,12 +99,21 @@ def derived_array(func, data, **kwargs):
new_samples = [] new_samples = []
new_means = [] new_means = []
for name in new_names: new_idl = []
new_samples.append(new_deltas[name]) if is_merged:
filtered_names, filtered_deltas, filtered_idl_d = _filter_zeroes(new_names, new_deltas, new_idl_d)
else:
filtered_names = new_names
filtered_deltas = new_deltas
filtered_idl_d = new_idl_d
for name in filtered_names:
new_samples.append(filtered_deltas[name])
new_means.append(new_r_values[name][i_val]) new_means.append(new_r_values[name][i_val])
new_idl.append(filtered_idl_d[name])
final_result[i_val] = Obs(new_samples, new_names, means=new_means) final_result[i_val] = Obs(new_samples, filtered_names, means=new_means, idl=new_idl)
final_result[i_val]._value = new_val final_result[i_val]._value = new_val
final_result[i_val].is_merged = is_merged
final_result[i_val].reweighted = reweighted
return final_result return final_result

View file

@ -829,7 +829,7 @@ class CObs:
return 'CObs[' + str(self) + ']' return 'CObs[' + str(self) + ']'
def merge_idx(idl): def _merge_idx(idl):
"""Returns the union of all lists in idl """Returns the union of all lists in idl
Parameters Parameters
@ -856,7 +856,7 @@ def merge_idx(idl):
return list(set().union(*idl)) return list(set().union(*idl))
def expand_deltas_for_merge(deltas, idx, shape, new_idx): def _expand_deltas_for_merge(deltas, idx, shape, new_idx):
"""Expand deltas defined on idx to the list of configs that is defined by new_idx. """Expand deltas defined on idx to the list of configs that is defined by new_idx.
New, empy entries are filled by 0. If idx and new_idx are of type range, the smallest New, empy entries are filled by 0. If idx and new_idx are of type range, the smallest
common divisor of the step sizes is used as new step size. common divisor of the step sizes is used as new step size.
@ -883,7 +883,7 @@ def expand_deltas_for_merge(deltas, idx, shape, new_idx):
return np.array([ret[new_idx[i] - new_idx[0]] for i in range(len(new_idx))]) return np.array([ret[new_idx[i] - new_idx[0]] for i in range(len(new_idx))])
def filter_zeroes(names, deltas, idl, eps=Obs.filter_eps): def _filter_zeroes(names, deltas, idl, eps=Obs.filter_eps):
"""Filter out all configurations with vanishing fluctuation such that they do not """Filter out all configurations with vanishing fluctuation such that they do not
contribute to the error estimate anymore. Returns the new names, deltas and contribute to the error estimate anymore. Returns the new names, deltas and
idl according to the filtering. idl according to the filtering.
@ -976,7 +976,7 @@ def derived_observable(func, data, **kwargs):
tmp = i_data.idl.get(name) tmp = i_data.idl.get(name)
if tmp is not None: if tmp is not None:
idl.append(tmp) idl.append(tmp)
new_idl_d[name] = merge_idx(idl) new_idl_d[name] = _merge_idx(idl)
if not is_merged: if not is_merged:
is_merged = (1 != len(set([len(idx) for idx in [*idl, new_idl_d[name]]]))) is_merged = (1 != len(set([len(idx) for idx in [*idl, new_idl_d[name]]])))
@ -1038,13 +1038,13 @@ def derived_observable(func, data, **kwargs):
new_deltas = {} new_deltas = {}
for j_obs, obs in np.ndenumerate(data): for j_obs, obs in np.ndenumerate(data):
for name in obs.names: for name in obs.names:
new_deltas[name] = new_deltas.get(name, 0) + deriv[i_val + j_obs] * expand_deltas_for_merge(obs.deltas[name], obs.idl[name], obs.shape[name], new_idl_d[name]) new_deltas[name] = new_deltas.get(name, 0) + deriv[i_val + j_obs] * _expand_deltas_for_merge(obs.deltas[name], obs.idl[name], obs.shape[name], new_idl_d[name])
new_samples = [] new_samples = []
new_means = [] new_means = []
new_idl = [] new_idl = []
if is_merged: if is_merged:
filtered_names, filtered_deltas, filtered_idl_d = filter_zeroes(new_names, new_deltas, new_idl_d) filtered_names, filtered_deltas, filtered_idl_d = _filter_zeroes(new_names, new_deltas, new_idl_d)
else: else:
filtered_names = new_names filtered_names = new_names
filtered_deltas = new_deltas filtered_deltas = new_deltas
@ -1064,7 +1064,7 @@ def derived_observable(func, data, **kwargs):
return final_result return final_result
def reduce_deltas(deltas, idx_old, idx_new): def _reduce_deltas(deltas, idx_old, idx_new):
"""Extract deltas defined on idx_old on all configs of idx_new. """Extract deltas defined on idx_old on all configs of idx_new.
Parameters Parameters
@ -1094,7 +1094,7 @@ def reduce_deltas(deltas, idx_old, idx_new):
pos = j pos = j
break break
if pos < 0: if pos < 0:
raise Exception('Error in reduce_deltas: Config %d not in idx_old' % (idx_new[i])) raise Exception('Error in _reduce_deltas: Config %d not in idx_old' % (idx_new[i]))
ret[i] = deltas[j] ret[i] = deltas[j]
return np.array(ret) return np.array(ret)
@ -1124,7 +1124,7 @@ def reweight(weight, obs, **kwargs):
new_samples = [] new_samples = []
w_deltas = {} w_deltas = {}
for name in sorted(weight.names): for name in sorted(weight.names):
w_deltas[name] = reduce_deltas(weight.deltas[name], weight.idl[name], obs[i].idl[name]) w_deltas[name] = _reduce_deltas(weight.deltas[name], weight.idl[name], obs[i].idl[name])
new_samples.append((w_deltas[name] + weight.r_values[name]) * (obs[i].deltas[name] + obs[i].r_values[name])) new_samples.append((w_deltas[name] + weight.r_values[name]) * (obs[i].deltas[name] + obs[i].r_values[name]))
tmp_obs = Obs(new_samples, sorted(weight.names), idl=[obs[i].idl[name] for name in sorted(weight.names)]) tmp_obs = Obs(new_samples, sorted(weight.names), idl=[obs[i].idl[name] for name in sorted(weight.names)])
@ -1200,7 +1200,7 @@ def covariance(obs1, obs2, correlation=False, **kwargs):
for name in sorted(set(obs1.names + obs2.names)): for name in sorted(set(obs1.names + obs2.names)):
if (obs1.shape.get(name) != obs2.shape.get(name)) and (obs1.shape.get(name) is not None) and (obs2.shape.get(name) is not None): if (obs1.shape.get(name) != obs2.shape.get(name)) and (obs1.shape.get(name) is not None) and (obs2.shape.get(name) is not None):
raise Exception('Shapes of ensemble', name, 'do not fit') raise Exception('Shapes of ensemble', name, 'do not fit')
if (1 != len(set([len(idx) for idx in [obs1.idl[name], obs2.idl[name], merge_idx([obs1.idl[name], obs2.idl[name]])]]))): if (1 != len(set([len(idx) for idx in [obs1.idl[name], obs2.idl[name], _merge_idx([obs1.idl[name], obs2.idl[name]])]]))):
raise Exception('Shapes of ensemble', name, 'do not fit') raise Exception('Shapes of ensemble', name, 'do not fit')
if not hasattr(obs1, 'e_names') or not hasattr(obs2, 'e_names'): if not hasattr(obs1, 'e_names') or not hasattr(obs2, 'e_names'):
@ -1306,7 +1306,7 @@ def covariance2(obs1, obs2, correlation=False, **kwargs):
for r_name in obs1.e_content[e_name]: for r_name in obs1.e_content[e_name]:
if r_name not in obs2.e_content[e_name]: if r_name not in obs2.e_content[e_name]:
continue continue
idl_d[r_name] = merge_idx([obs1.idl[r_name], obs2.idl[r_name]]) idl_d[r_name] = _merge_idx([obs1.idl[r_name], obs2.idl[r_name]])
if idl_d[r_name] is range: if idl_d[r_name] is range:
r_length.append(len(idl_d[r_name])) r_length.append(len(idl_d[r_name]))
else: else:
@ -1380,7 +1380,7 @@ def covariance3(obs1, obs2, correlation=False, **kwargs):
for name in sorted(set(obs1.names + obs2.names)): for name in sorted(set(obs1.names + obs2.names)):
if (obs1.shape.get(name) != obs2.shape.get(name)) and (obs1.shape.get(name) is not None) and (obs2.shape.get(name) is not None): if (obs1.shape.get(name) != obs2.shape.get(name)) and (obs1.shape.get(name) is not None) and (obs2.shape.get(name) is not None):
raise Exception('Shapes of ensemble', name, 'do not fit') raise Exception('Shapes of ensemble', name, 'do not fit')
if (1 != len(set([len(idx) for idx in [obs1.idl[name], obs2.idl[name], merge_idx([obs1.idl[name], obs2.idl[name]])]]))): if (1 != len(set([len(idx) for idx in [obs1.idl[name], obs2.idl[name], _merge_idx([obs1.idl[name], obs2.idl[name]])]]))):
raise Exception('Shapes of ensemble', name, 'do not fit') raise Exception('Shapes of ensemble', name, 'do not fit')
if not hasattr(obs1, 'e_names') or not hasattr(obs2, 'e_names'): if not hasattr(obs1, 'e_names') or not hasattr(obs2, 'e_names'):