Source code for pyqmc.accumulators
import numpy as np
import pyqmc.gpu as gpu
import pyqmc.energy as energy
import pyqmc.ewald as ewald
import pyqmc.eval_ecp as eval_ecp
def gradient_generator(mol, wf, to_opt=None, **ewald_kwargs):
return PGradTransform(
EnergyAccumulator(mol, **ewald_kwargs), LinearTransform(wf.parameters, to_opt)
)
[docs]class EnergyAccumulator:
"""Returns local energy of each configuration in a dictionary."""
def __init__(self, mol, threshold=10, **kwargs):
self.mol = mol
self.threshold = threshold
if hasattr(mol, "a"):
self.coulomb = ewald.Ewald(mol, **kwargs)
else:
self.coulomb = energy.OpenCoulomb(mol, **kwargs)
def __call__(self, configs, wf):
ee, ei, ii = self.coulomb.energy(configs)
ecp_val = eval_ecp.ecp(self.mol, configs, wf, self.threshold)
ke, grad2 = energy.kinetic(configs, wf)
return {
"ke": ke,
"ee": ee,
"ei": ei,
"ecp": ecp_val,
"grad2": grad2,
"total": ke + ee + ei + ecp_val + ii,
}
def avg(self, configs, wf):
return {k: np.mean(it, axis=0) for k, it in self(configs, wf).items()}
def nonlocal_tmoves(self, configs, wf, e, tau):
return eval_ecp.compute_tmoves(self.mol, configs, wf, e, self.threshold, tau)
def has_nonlocal_moves(self):
return self.mol._ecp != {}
def keys(self):
return set(["ke", "ee", "ei", "ecp", "total", "grad2"])
def shapes(self):
return {"ke": (), "ee": (), "ei": (), "ecp": (), "total": (), "grad2": ()}
[docs]class LinearTransform:
"""
Linearize a dictionary of wf parameters.
:parameter dict parameters: the wave function parameters
:parameter dict to_opt: is a dictionary with the keys to optimize, and its values are boolean arrays indicating which specific elements to optimize
Note:
to_opt[k] can't be boolean scalar; it has to be an array with the same dimension as parameters[k].
to_opt doesn't have to have all the keys of parameters, but all keys of to_opt must be keys of parameters.
"""
def __init__(self, parameters, to_opt=None):
parameters = {k: gpu.asnumpy(v) for k, v in parameters.items()}
if to_opt is None:
to_opt = {k: np.ones(p.shape, dtype=bool) for k, p in parameters.items()}
self.to_opt = {k: o for k, o in to_opt.items() if np.any(o)}
self.frozen_parms = {k: parameters[k][~opt] for k, opt in self.to_opt.items()}
self.shapes = {k: parameters[k].shape for k in self.to_opt}
self.slices = {k: np.prod(s) for k, s in self.shapes.items()}
self.dtypes = {k: parameters[k].dtype for k in self.to_opt}
self.complex = {k: d == complex for k, d in self.dtypes.items()}
self.nimag = {k: to_opt[k].sum() if c else 0 for k, c in self.complex.items()}
self.complex_inds = np.concatenate(
[np.ones(to_opt[k].sum(), dtype=bool) * c for k, c in self.complex.items()]
)
self.nparams = np.sum([v.sum() for v in self.to_opt.values()])
[docs] def serialize_parameters(self, parameters):
"""Convert the dictionary to a linear list of gradients"""
params = np.concatenate(
[gpu.asnumpy(parameters[k])[opt] for k, opt in self.to_opt.items()]
)
return np.concatenate((params.real, params[self.complex_inds].imag))
[docs] def serialize_gradients(self, pgrad):
"""Convert the dictionary to a linear list of gradients, mask allows for certain fixed parameters"""
grads = []
for k, opt in self.to_opt.items():
mask = ~np.repeat(opt[np.newaxis, :], pgrad[k].shape[0], axis=0)
mask_grads = np.ma.array(pgrad[k], mask=mask).reshape(pgrad[k].shape[0], -1)
grads.append(np.ma.compress_cols(mask_grads))
grads = np.concatenate(grads, axis=1)
return np.concatenate((grads, grads[:, self.complex_inds] * 1j), axis=1)
[docs] def deserialize(self, parameters):
"""Convert serialized parameters to dictionary"""
n = 0
m = self.nparams
d = {}
for k, opt in self.to_opt.items():
opt_ = opt.flatten()
n_p = np.sum(opt_)
flat_parms = np.zeros(self.slices[k], dtype=self.dtypes[k])
flat_parms[~opt_] = self.frozen_parms[k]
flat_parms[opt_] = parameters[n : n + n_p]
if self.complex[k]:
m_p = self.nimag[k]
flat_parms[opt_] += parameters[m : m + m_p] * 1j
m += m_p
d[k] = gpu.cp.asarray(flat_parms.reshape(self.shapes[k]))
n += n_p
return d
[docs]class PGradTransform:
""" """
def __init__(self, enacc, transform, nodal_cutoff=1e-3):
self.enacc = enacc
self.transform = transform
self.nodal_cutoff = nodal_cutoff
def _node_regr(self, configs, grad2):
"""
Return true if a given configuration is within nodal_cutoff
of the node
Also return the regularization polynomial if true,
f = a * r ** 2 + b * r ** 4 + c * r ** 6
"""
r = 1.0 / grad2
mask = r < self.nodal_cutoff ** 2
c = 7.0 / (self.nodal_cutoff ** 6)
b = -15.0 / (self.nodal_cutoff ** 4)
a = 9.0 / (self.nodal_cutoff ** 2)
f = a * r + b * r ** 2 + c * r ** 3
f[np.logical_not(mask)] = 1.0
return mask, f
def __call__(self, configs, wf):
pgrad = wf.pgradient()
d = self.enacc(configs, wf)
energy = d["total"]
dp = self.transform.serialize_gradients(pgrad)
node_cut, f = self._node_regr(configs, d["grad2"])
dp_regularized = dp * f[:, np.newaxis]
d["dpH"] = np.einsum("i,ij->ij", energy, dp_regularized)
d["dppsi"] = dp_regularized
d["dpidpj"] = np.einsum("ij,ik->ijk", dp, dp_regularized)
return d
[docs] def avg(self, configs, wf, weights=None):
"""
Compute (weighted) average
"""
nconf = configs.configs.shape[0]
if weights is None:
weights = np.ones(nconf)
weights = weights / np.sum(weights)
pgrad = wf.pgradient()
den = self.enacc(configs, wf)
energy = den["total"]
dp = self.transform.serialize_gradients(pgrad)
node_cut, f = self._node_regr(configs, den["grad2"])
dp_regularized = dp * f[:, np.newaxis]
d = {k: np.average(it, weights=weights, axis=0) for k, it in den.items()}
d["dpH"] = np.einsum("i,ij->j", energy, weights[:, np.newaxis] * dp_regularized)
d["dppsi"] = np.average(dp_regularized, weights=weights, axis=0)
d["dpidpj"] = np.einsum(
"ij,ik->jk", dp, weights[:, np.newaxis] * dp_regularized, optimize=True
)
return d
def keys(self):
return self.enacc.keys().union(["dpH", "dppsi", "dpidpj"])
def shapes(self):
nparms = np.sum([np.sum(opt) for opt in self.transform.to_opt.values()])
d = {"dpH": (nparms,), "dppsi": (nparms,), "dpidpj": (nparms, nparms)}
d.update(self.enacc.shapes())
return d
[docs]class SqAccumulator:
r"""
Accumulates structure factor
.. math:: S(\vec{q}) = \langle \rho_{\vec{q}} \rho_{-\vec{q}} \rangle
= \langle \left| \sum_{j=1}^{N_e} e^{i\vec{q}\cdot\vec{r}_j} \right| \rangle
"""
def __init__(self, qlist=None, Lvecs=None, nq=4):
"""
Inputs:
qlist: (n, 3) array-like. If qlist is provided, Lvecs and nq are ignored
Lvecs: (3, 3) array-like of lattice vectors. Required if qlist is None
nq: int, if qlist is nonzero, use a uniform grid of shape (nq, nq, nq)
"""
if qlist is not None:
self.qlist = qlist
else:
assert (
Lvecs is not None
), "need to provide either list of q vectors or lattice vectors"
Gvecs = np.linalg.inv(Lvecs).T * 2 * np.pi
qvecs = list(map(np.ravel, np.meshgrid(*[np.arange(nq)] * 3)))
qvecs = np.stack(qvecs, axis=1)
self.qlist = np.dot(qvecs, Gvecs)
def __call__(self, configs, wf):
nelec = configs.configs.shape[1]
exp_iqr = np.exp(1j * np.inner(configs.configs, self.qlist))
sum_exp_iqr = exp_iqr.sum(axis=1)
return {"Sq": (sum_exp_iqr.real ** 2 + sum_exp_iqr.imag ** 2) / nelec}
def avg(self, configs, wf):
return {k: np.mean(it, axis=0) for k, it in self(configs, wf).items()}
def keys(self):
return set(["Sq"])
def shapes(self):
return {"Sq": (len(self.qlist),)}