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),)}