Source code for pyscf.mcpdft.tfnal_derivs

#!/usr/bin/env python
# Copyright 2014-2022 The PySCF Developers. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
from scipy import linalg
from pyscf.lib import logger


def _reshape_vxc_sigma(vxc0, dens_deriv):
    # d/drho, d/dsigma -> d/drho, d/drho'
    vrho = vxc0[0]
    vxc1 = list(vrho.T)
    if dens_deriv:
        vsigma = vxc0[1]
        vxc1 = vxc1 + list(vsigma.T)
    else:
        vxc1 = [vxc1[0][None, :], vxc1[1][None, :]]
    return vxc1


def _unpack_vxc_sigma(vxc0, rho, dens_deriv):
    # d/drho, d/dsigma -> d/drho, d/drho'
    vrho = vxc0[0]
    vxc1 = list(vrho.T)
    if dens_deriv:
        vsigma = vxc0[1]
        vxc1 = vxc1 + list(vsigma.T)
        vxc1 = _unpack_sigma_vector(vxc1, rho[0][1:4], rho[1][1:4])
    else:
        vxc1 = [vxc1[0][None, :], vxc1[1][None, :]]
    return vxc1


def _pack_fxc_ltri(fxc0, dens_deriv):
    # d2/drho2, d2/drhodsigma, d2/dsigma2
    # -> lower-triangular Hessian matrix
    frho = fxc0[0].T
    fxc1 = [frho[0], ]
    fxc1 += [frho[1], frho[2], ]
    if dens_deriv:
        frhosigma, fsigma = fxc0[1].T, fxc0[2].T
        fxc1 += [frhosigma[0], frhosigma[3], fsigma[0], ]
        fxc1 += [frhosigma[1], frhosigma[4], fsigma[1], fsigma[3], ]
        fxc1 += [frhosigma[2], frhosigma[5], fsigma[2], fsigma[4], fsigma[5]]
    return fxc1


[docs] def eval_ot(otfnal, rho, Pi, dderiv=1, weights=None, _unpack_vot=True): r'''get the integrand of the on-top xc energy and its functional derivatives wrt rho and Pi Args: rho : ndarray of shape (2,*,ngrids) containing spin-density [and derivatives] Pi : ndarray with shape (*,ngrids) containing on-top pair density [and derivatives] Kwargs: dderiv : integer Order of derivatives to return weights : ndarray of shape (ngrids) used ONLY for debugging the total number of ``translated'' electrons in the calculation of rho_t Not multiplied into anything! _unpack_vot : logical If True, derivatives with respect to density gradients are reported as de/drho' and de/dPi'; otherwise, they are reported as de/d|rho'|^2, de/d(rho'.Pi'), and de/d|Pi'|^2 Returns: eot : ndarray of shape (ngrids) integrand of the on-top exchange-correlation energy vot : ndarrays of shape (*,ngrids) or None first functional derivative of Eot wrt (density, pair density) and their derivatives. If _unpack_vot = True, shape and format is ([a, ngrids], [b, ngrids]) : (vrho, vPi); otherwise, [c, ngrids] : [rho,Pi,|rho'|^2,tau,rho'.Pi',|Pi'|^2] ftGGA: a=4, b=4, c=5 (drop tau) tmGGA: a=5, b=1, c=4 (drop Pi') tGGA: a=4, b=1, c=3 (drop Pi', tau) *tLDA: a=1, b=1, c=2 (drop rho', tau) fot : ndarray of shape (*,ngrids) or None second functional derivative of Eot wrt density, pair density, and derivatives; first dimension is lower- triangular matrix elements corresponding to the basis (rho, Pi, |rho'|^2, rho'.Pi', |Pi'|^2) stopping at Pi (3 elements) for *tLDA and |rho'|^2 (6 elements) for tGGA. ''' if dderiv > 2: raise NotImplementedError("Translation of density derivatives of " "higher order than 2") if rho.ndim == 2: rho = rho[:, None, :] if Pi.ndim == 1: Pi = Pi[None, :] assert (rho.shape[0] == 2) assert (rho.shape[1] <= 6), "Undefined behavior for this function" nderiv = rho.shape[1] nderiv_Pi = Pi.shape[0] rho_t = otfnal.get_rho_translated(Pi, rho) # LDA in libxc has a special numerical problem with zero-valued densities # in one spin if nderiv == 0: idx = (rho_t[0, 0] > 1e-15) & (rho_t[1, 0] < 1e-15) rho_t[1, 0, idx] = 1e-15 idx = (rho_t[0, 0] < 1e-15) & (rho_t[1, 0] > 1e-15) rho_t[0, 0, idx] = 1e-15 # mGGA in libxc has special numerical problem with zero-valued densities in # one spin! if nderiv == 5: idx = (rho_t[0, 4] > 1e-15) & (rho_t[1, 4] < 1e-15) rho_t[1, 4, idx] = 1e-15 idx = (rho_t[0, 4] < 1e-15) & (rho_t[1, 4] > 1e-15) rho_t[0, 4, idx] = 1e-15 rho_tot = rho.sum(0) if nderiv > 4 and dderiv > 1: raise NotImplementedError("Meta-GGA functional Hessians") if 1 < nderiv <= 4: rho_deriv = rho_tot[1:4, :] elif 4 < nderiv <= 5: rho_deriv = rho_tot[1:5, :] else: rho_deriv = None Pi_deriv = Pi[1:4, :] if nderiv_Pi > 1 else None xc_grid = otfnal._numint.eval_xc(otfnal.otxc, (rho_t[0, :, :], rho_t[1, :, :]), spin=1, relativity=0, deriv=dderiv, verbose=otfnal.verbose)[:dderiv + 1] eot = xc_grid[0] * rho_t[:, 0, :].sum(0) if (weights is not None) and otfnal.verbose >= logger.DEBUG: nelec = rho_t[0, 0].dot(weights) + rho_t[1, 0].dot(weights) logger.debug(otfnal, ('MC-PDFT: Total number of electrons in (this ' 'chunk of) the total density = %s'), nelec) ms = (rho_t[0, 0].dot(weights) - rho_t[1, 0].dot(weights)) / 2.0 logger.debug(otfnal, ('MC-PDFT: Total ms = (neleca - nelecb) / 2 in ' '(this chunk of) the translated density = %s'), ms) vot = fot = None if dderiv > 0: # vrho, vsigma = xc_grid[1][:2] vxc = list(xc_grid[1][0].T) if otfnal.dens_deriv > 0: vxc = vxc + list(xc_grid[1][1].T) # vrho, vsigma, vlapl, vtau = xc_grid[1][:4] if otfnal.dens_deriv > 1: # we might get a None for one of the derivatives.. # we get None for the laplacian derivative if xc_grid[1][2] is not None: raise NotImplementedError("laplacian translated meta-GGA functionals") # Here is the tau term vxc = vxc + list(xc_grid[1][3].T) vot = otfnal.jT_op(vxc, rho, Pi) if _unpack_vot: vot = _unpack_sigma_vector(vot, deriv1=rho_deriv, deriv2=Pi_deriv) if dderiv > 1: # I should implement this entirely in terms of the gradient norm, since # that reduces the number of grid columns from 25 to 9 for t-GGA and # from 64 to 25 for ft-GGA (and steps around the need to "unpack" # fsigma and frhosigma entirely). fxc = _pack_fxc_ltri(xc_grid[2], otfnal.dens_deriv) # First pass: fxc fot = _jT_f_j(fxc, otfnal.jT_op, rho, Pi, rec=otfnal) # Second pass: translation derivatives fot_d_jT = otfnal.d_jT_op(vxc, rho, Pi) fot[:fot_d_jT.shape[0]] += fot_d_jT return eot, vot, fot
[docs] def unpack_vot(packed, rho, Pi): if rho.ndim == 2: rho = rho[:, None, :] if Pi.ndim == 1: Pi = Pi[None, :] assert (rho.shape[0] == 2) nderiv = rho.shape[1] nderiv_Pi = Pi.shape[0] rho_tot = rho.sum(0) rho_deriv = rho_tot[1:4, :] if nderiv > 1 else None Pi_deriv = Pi[1:4, :] if nderiv_Pi > 1 else None return _unpack_sigma_vector(packed, deriv1=rho_deriv, deriv2=Pi_deriv)
def _unpack_sigma_vector(packed, deriv1=None, deriv2=None): # For GGAs, libxc differentiates with respect to # sigma[0] = nabla^2 rhoa # sigma[1] = nabla rhoa . nabla rhob # sigma[2] = nabla^2 rhob # So we have to multiply the Jacobian to obtain the requested derivatives: # J[0,nabla rhoa] = 2 * nabla rhoa # J[0,nabla rhob] = 0 # J[1,nabla rhoa] = nabla rhob # J[1,nabla rhob] = nabla rhoa # J[2,nabla rhoa] = 0 # J[2,nabla rhob] = 2 * nabla rhob if len(packed) > 5: raise RuntimeError("{} {}".format(len(packed), [p.shape for p in packed[:5]])) ncol1 = 1 if deriv1 is not None and len(packed) > 2: ncol1 += deriv1.shape[0] ncol2 = 1 + 3 * int((deriv2 is not None) and len(packed) > 3) ngrid = packed[0].shape[-1] # Don't assume it's an ndarray unp1 = np.empty((ncol1, ngrid), dtype=packed[0].dtype) unp2 = np.empty((ncol2, ngrid), dtype=packed[0].dtype) unp1[0] = packed[0] unp2[0] = packed[1] if ncol1 > 1: unp1[1:4] = 2 * deriv1[:3] * packed[2] if ncol1 > 4: # Deal with the tau term unp1[4:5] = packed[3:4] if ncol2 > 1: unp1[1:4] += deriv2 * packed[-2] unp2[1:4] = (2 * deriv2 * packed[-1]) + (deriv1[:3] * packed[-2]) return unp1, unp2
[docs] def contract_vot(vot, rho, Pi): '''Evalute the product of unpacked vot with perturbed density, pair density, and derivatives. Args: vot : (ndarray of shape (*,ngrids), ndarray of shape (*, ngrids)) format is ([a, ngrids], [b, ngrids]) : (vrho, vPi) ftGGA: a=4, b=4 tGGA: a=4, b=1 *tLDA: a=1, b=1 rho : ndarray of shape (*,ngrids) containing density [and derivatives] the density contracted with vot Pi : ndarray with shape (*,ngrids) containing on-top pair density [and derivatives] the density contracted with vot Returns: cvot : ndarray of shape (ngrids) product of vot wrt (density, pair density) and their derivatives ''' vrho, vPi = vot if rho.shape[0] == 2: rho = rho.sum(0) if rho.ndim == 1: rho = rho[None, :] if Pi.ndim == 1: Pi = Pi[None, :] cvot = vrho[0] * rho[0] + vPi[0] * Pi[0] if len(vrho) > 1: cvot += (vrho[1:4,:] * rho[1:4, :]).sum(0) if len(vPi) > 1: cvot += (vPi[1:4, :] * Pi[1:4, :]).sum(0) return cvot
[docs] def contract_fot(otfnal, fot, rho0, Pi0, rho1, Pi1, unpack=True, vot_packed=None): r''' Evaluate the product of a packed lower-triangular matrix with perturbed density, pair density, and derivatives. Args: fot : ndarray of shape (*,ngrids) Lower-triangular matrix elements corresponding to the basis (rho, Pi, |drho|^2, drho'.dPi, |dPi|^2) stopping at Pi (3 elements) for *tLDA and |drho|^2 (6 elements) for tGGA. rho0 : ndarray of shape (2,*,ngrids) containing density [and derivatives] the density at which fot was evaluated Pi0 : ndarray with shape (*,ngrids) containing on-top pair density [and derivatives] the density at which fot was evaluated rho1 : ndarray of shape (2,*,ngrids) containing density [and derivatives] the density contracted with fot Pi1 : ndarray with shape (*,ngrids) containing on-top pair density [and derivatives] the density contracted with fot Kwargs: unpack : logical If True, returns vot1 in unpacked shape: (ndarray of shape (*,ngrids), ndarray of shape (*,ngrids)) corresponding to (density, pair density) and their derivatives. This requires vot_packed for *tGGA functionals Otherwise, returns vot1 in packed shape: (rho, Pi, |rho'|^2, rho'.Pi', |Pi'|^2) stopping at Pi (3 elements) for *tLDA and |rho'|^2 (6 elements) for tGGA. vot_packed : ndarray of shape (*,ngrids) Vector elements corresponding to the basis (rho, Pi, |drho|^2, drho'.dPi, |dPi|^2) stopping at Pi (2 elements) for *tLDA and |drho|^2 (3 elements) for tGGA. Required if unpack == True for *tGGA functionals (because vot_|drho|^2 contributes to fot_rho',rho', etc.) Returns: vot1 : (ndarray of shape (*,ngrids), ndarray of shape (*,ngrids)) product of fot wrt (density, pair density) and their derivatives ''' if rho0.shape[0] == 2: rho0 = rho0.sum(0) # Never has exactly 1 derivative if rho0.ndim == 1: rho0 = rho0[None, :] if Pi0.ndim == 1: Pi0 = Pi0[None, :] if rho1.shape[0] == 2: rho1 = rho1.sum(0) # Never has exactly 1 derivative if rho1.ndim == 1: rho1 = rho1[None, :] if Pi1.ndim == 1: Pi1 = Pi1[None, :] ngrids = fot[0].shape[-1] vrho1 = np.zeros(ngrids, dtype=fot[0].dtype) vPi1 = np.zeros(ngrids, dtype=fot[2].dtype) vrho1, vPi1 = np.zeros_like(rho1), np.zeros_like(Pi1) # TODO: dspmv implementation vrho1 = fot[0] * rho1[0] + fot[1] * Pi1[0] vPi1 = fot[2] * Pi1[0] + fot[1] * rho1[0] rho0p = Pi0p = rho1p = Pi1p = None v1 = [vrho1, vPi1] if len(fot) > 3: srr = 2 * (rho0[1:4, :] * rho1[1:4, :]).sum(0) vrho1 += fot[3] * srr vPi1 += fot[4] * srr vrr = fot[3] * rho1[0] + fot[4] * Pi1[0] + fot[5] * srr rho0p = rho0[1:4] rho1p = rho1[1:4] v1 = [vrho1, vPi1, vrr] if len(fot) > 6: srP = ((rho0[1:4, :] * Pi1[1:4, :]).sum(0) + (rho1[1:4, :] * Pi0[1:4, :]).sum(0)) sPP = 2 * (Pi0[1:4, :] * Pi1[1:4, :]).sum(0) vrho1 += fot[6] * srP + fot[10] * sPP vPi1 += fot[7] * srP + fot[11] * sPP vrr += fot[8] * srP + fot[12] * sPP vrP = (fot[6] * rho1[0] + fot[7] * Pi1[0] + fot[8] * srr + fot[9] * srP + fot[13] * sPP) vPP = (fot[10] * rho1[0] + fot[11] * Pi1[0] + fot[12] * srr + fot[13] * srP + fot[14] * sPP) Pi0p = Pi0[1:4] Pi1p = Pi1[1:4] v1 = [vrho1, vPi1, vrr, vrP, vPP] ggrad = (rho0p is not None) or (Pi0p is not None) if unpack: vrho1, vPi1 = _unpack_sigma_vector(v1, rho0p, Pi0p) if ggrad: if vot_packed is None: raise RuntimeError("Cannot evaluate fot.x in terms of " "unpacked density gradients without vot_packed") vrho2, vPi2 = _unpack_sigma_vector(vot_packed, rho1p, Pi1p) if vrho1.shape[0] > 1: vrho1[1:4, :] += vrho2[1:4, :] if vPi1.shape[0] > 1: vPi1[1:4, :] += vPi2[1:4, :] v1 = (vrho1, vPi1) return v1
def _jT_f_j(frr, jT_op, *args, **kwargs): r''' Apply a jacobian function taking *args to the lower-triangular second-derivative array frr''' nel = len(frr) nr = int(round(np.sqrt(1 + 8 * nel) - 1)) // 2 rec = kwargs.get('rec', None) ngrids = frr[0].shape[-1] # build square-matrix index array to address packed matrix frr w/o copying ltri_ix = np.tril_indices(nr) idx_arr = np.zeros((nr, nr), dtype=np.int32) idx_arr[ltri_ix] = range(nel) idx_arr += idx_arr.T diag_ix = np.diag_indices(nr) idx_arr[diag_ix] = idx_arr[diag_ix] // 2 # first pass: jT . frr -> fcr fcr = np.stack([jT_op([frr[i] for i in ix_row], *args) for ix_row in idx_arr], axis=1) # second pass. fcr is a rectangular matrix (unavoidably) nc = fcr.shape[0] if getattr(rec, 'verbose', 0) < logger.DEBUG: fcc = np.empty((nc * (nc + 1) // 2, ngrids), dtype=fcr.dtype) i = 0 for ix_row, fc_row in enumerate(fcr): di = ix_row + 1 j = i + di fcc[i:j] = jT_op(fc_row, *args)[:di] i = j else: fcc = np.empty((nc, nc, ngrids), dtype=fcr.dtype) for fcc_row, fcr_row in zip(fcc, fcr): fcc_row[:] = jT_op(fcr_row, *args) for i in range(1, nc): for j in range(i): scale = (fcc[i, j] + fcc[j, i]) / 2 scale[scale == 0] = 1 logger.debug(rec, 'MC-PDFT jT_f_j symmetry check %d,%d: %e', i, j, linalg.norm((fcc[i, j] - fcc[j, i]) / scale)) ltri_ix = np.tril_indices(nc) fcc = fcc[ltri_ix] return fcc def _gentLDA_jT_op(x, rho, Pi, R, zeta): # On a grid, multiply the transpose of the Jacobian # d(trhoa,trhob) [fictitous densities] # J = ______________ # d(rho,Pi) [real densities] # by a vector x_(trhoa,trhob) ngrid = rho.shape[-1] if R.ndim > 1: R = R[0] # ab -> cs coordinate transformation xc = (x[0] + x[1]) / 2.0 xm = (x[0] - x[1]) / 2.0 # Charge sector has no explicit rho denominator # and so does not require indexing to avoid # division by zero jTx = np.zeros((2, ngrid), dtype=x[0].dtype) jTx[0] = xc + xm * (zeta[0] - (2 * R * zeta[1])) # Spin sector has a rho denominator idx = (rho[0] > 1e-15) zeta = zeta[1, idx] rho = rho[0, idx] xm = xm[idx] jTx[1, idx] = 4 * xm * zeta / rho return jTx def _tGGA_jT_op(x, rho, Pi, R, zeta): # On a grid, multiply the transpose of the Jacobian # d(trho?'.trho?') [fictitous density gradients] # J = ______________ # d(rho,Pi,|rho'|) [real densities and gradients] # by a vector x_(|trho*'|^2) in the context of tGGAs ngrid = rho.shape[-1] jTx = np.zeros((3, ngrid), dtype=x[0].dtype) if R.ndim > 1: R = R[0] # ab -> cs coordinate transformation xcc = (x[2] + x[4] + x[3]) / 4.0 xcm = (x[2] - x[4]) / 2.0 xmm = (x[2] + x[4] - x[3]) / 4.0 # Gradient-gradient sector idx = (zeta[0]!=1) jTx[2] = x[2] jTx[2,idx] = (xcc + xcm * zeta[0] + xmm * zeta[0] * zeta[0])[idx] # Finite-precision safety # Density-gradient sector idx = (rho[0] > 1e-15) sigma_fac = ((rho[1:4].conj() * rho[1:4]).sum(0) * zeta[1]) sigma_fac = ((xcm + 2 * zeta[0] * xmm) * sigma_fac)[idx] rho = rho[0, idx] R = R[idx] sigma_fac = -2 * sigma_fac / rho jTx[0, idx] = R * sigma_fac jTx[1, idx] = -2 * sigma_fac / rho return jTx def _tmetaGGA_jT_op(x, rho, Pi, R, zeta): # output ordering is # ordering: rho, Pi, |rho'|^2, tau ngrid = rho.shape[-1] jTx = np.zeros((4, ngrid), dtype=x[0].dtype) if R.ndim > 1: R = R[0] # ab -> cs coordinate transformation xc = (x[5] + x[6]) / 2.0 xm = (x[5] - x[6]) / 2.0 # easy part jTx[3] = xc + zeta[0] * xm tau_lapl_factor = zeta[1] * rho[4] * xm idx = rho[0] > 1e-15 rho = rho[0, idx] R = R[idx] tau_lapl_factor = 2 * tau_lapl_factor[idx] / rho jTx[0, idx] = -R * tau_lapl_factor jTx[1, idx] = 2 * tau_lapl_factor / rho return jTx def _tGGA_jT_op_m2z(x, rho, zeta, srr): # cs -> rho,zeta step of _tGGA_jT_op above # unused; for contemplative purposes only jTx = np.empty_like(x) jTx[0] = x[0] + zeta[0] * x[1] jTx[1] = x[1] * rho + (x[3] + 2 * zeta[0] * x[4]) * srr jTx[2] = x[2] + x[3] * zeta[0] + x[4] * zeta[0] * zeta[0] jTx[3] = 0 jTx[4] = 0 return jTx def _ftGGA_jT_op_m2z(x, rho, zeta, srz, szz): # cs -> rho,zeta step of _ftGGA_jT_op below jTx = np.empty_like(x) jTx[0] = 2 * x[4] * (zeta[0] * srz + rho * szz) + x[3] * srz jTx[1] = 2 * x[4] * rho * srz jTx[2] = 0 jTx[3] = (x[3] + 2 * x[4] * zeta[0]) * rho jTx[4] = x[4] * rho * rho return jTx def _ftGGA_jT_op_z2R(x, zeta, srP, sPP): # rho,zeta -> rho,R step of _ftGGA_jT_op below jTx = np.empty_like(x) jTx[0] = x[0] jTx[1] = (x[1] * zeta[1] + x[3] * srP * zeta[2] + 2 * x[4] * sPP * zeta[1] * zeta[2]) jTx[2] = x[2] jTx[3] = x[3] * zeta[1] jTx[4] = x[4] * zeta[1] * zeta[1] return jTx def _ftGGA_jT_op_R2Pi(x, rho, R, srr, srP, sPP): # rho,R -> rho,Pi step of _ftGGA_jT_op below if rho.ndim > 1: rho = rho[0] if R.ndim > 1: R = R[0] jTx = np.empty_like(x) ri = np.empty_like(x) ri[0, :] = 0.0 idx = rho > 1e-15 ri[0, idx] = 1.0 / rho[idx] for i in range(4): ri[i + 1] = ri[i] * ri[0] jTx[0] = (x[0] - 2 * R * x[1] * ri[0] + x[3] * (6 * R * ri[1] * srr - 8 * srP * ri[2]) + x[4] * (-24 * R * R * ri[2] * srr + 80 * R * ri[3] * srP - 64 * ri[4] * sPP)) jTx[1] = (4 * x[1] * ri[1] - 8 * x[3] * ri[2] * srr + x[4] * (32 * R * ri[3] * srr - 64 * ri[4] * srP)) jTx[2] = x[2] - 2 * R * x[3] * ri[0] + 4 * x[4] * R * R * ri[1] jTx[3] = 4 * x[3] * ri[1] - 16 * x[4] * R * ri[2] jTx[4] = 16 * x[4] * ri[3] return jTx def _ftGGA_jT_op(x, rho, Pi, R, zeta): # On a grid, evaluate the contribution to the matrix-vector product # of the transpose of the Jacobian # d(trho?'.trho?') [fictitous density gradients] # J = ______________ # d(rho,Pi,|rho'|) [real densities and gradients] # with a vector x_(|trho*'|^2), which is present in ftGGAs and # missing in tGGAs ngrid = rho.shape[-1] jTx = np.zeros((5, ngrid), dtype=x[0].dtype) # ab -> cs step jTx[2] = (x[2] + x[4] + x[3]) / 4.0 jTx[3] = (x[2] - x[4]) / 2.0 jTx[4] = (x[2] + x[4] - x[3]) / 4.0 x = jTx # Intermediates srr = (rho[1:4, :] * rho[1:4, :]).sum(0) srP = (rho[1:4, :] * R[1:4, :]).sum(0) sPP = (R[1:4, :] * R[1:4, :]).sum(0) srz = srP * zeta[1] szz = sPP * zeta[1] * zeta[1] # cs -> rho,zeta step x = _ftGGA_jT_op_m2z(x, rho[0], zeta, srz, szz) # rho,zeta -> rho,R step x = _ftGGA_jT_op_z2R(x, zeta, srP, sPP) # rho,R -> rho,Pi step srP = (rho[1:4, :] * Pi[1:4, :]).sum(0) sPP = (Pi[1:4, :] * Pi[1:4, :]).sum(0) jTx = _ftGGA_jT_op_R2Pi(x, rho, R, srr, srP, sPP) return jTx def _gentLDA_d_jT_op(x, rho, Pi, R, zeta): # On a grid, differentiate once the Jacobian # d(trhoa,trhob) [fictitous densities] # J = ______________ # d(rho,Pi) [real densities] # and multiply by x_(trhoa,trhob) so as to compute the nonlinear- # translation contribution to the second functional derivatives of # the on-top energy in tLDA and ftLDA. rho = rho[0] Pi = Pi[0] R = R[0] ngrid = rho.shape[-1] f = np.zeros((3, ngrid), dtype=x[0].dtype) # ab -> cs xm = (x[0] - x[1]) / 2.0 # Indexing idx = rho > 1e-15 rho = rho[idx] Pi = Pi[idx] xm = xm[idx] R = R[idx] zeta = zeta[:, idx] # Intermediates # R = otfnal.get_ratio (Pi, rho/2) # zeta = otfnal.get_zeta (R, fn_deriv=2)[1:] xmw = 2 * xm / rho z1 = xmw * (zeta[1] + 2 * R * zeta[2]) # without further ceremony f[0, idx] = R * z1 f[1, idx] = -2 * z1 / rho f[2, idx] = xmw * 8 * zeta[2] / rho / rho return f def _tGGA_d_jT_op(x, rho, Pi, R, zeta): # On a grid, differentiate once the Jacobian # d(trho?'.trho?') [fictitous density gradients] # J = ______________ # d(rho,Pi,|rho'|) [real densities and gradients] # and multiply by x_(trho?'.trho?') so as to compute the nonlinear- # translation contribution to the second functional derivatives of # the on-top energy in tGGAs. # Generates contributions to the first five elements # of the lower-triangular packed Hessian ngrid = rho.shape[-1] f = np.zeros((5, ngrid), dtype=x[0].dtype) # Indexing idx = rho[0] > 1e-15 rho = rho[0:4, idx] Pi = Pi[0:1, idx] x = [xi[idx] for xi in x] R = R[0, idx] zeta = zeta[:, idx] # ab -> cs xcm = (x[2] - x[4]) / 2.0 xmm = (x[2] + x[4] - x[3]) / 4.0 # Intermediates sigma = (rho[1:4] * rho[1:4]).sum(0) rho = rho[0] rho2 = rho * rho rho3 = rho2 * rho rho4 = rho3 * rho # coefficient of dsigma dz xcm += 2 * zeta[0] * xmm f[3, idx] = -2 * xcm * R * zeta[1] / rho f[4, idx] = 4 * xcm * zeta[1] / rho2 # coefficient of d^2 z xcm *= sigma f[0, idx] = 2 * xcm * R * (3 * zeta[1] + 2 * R * zeta[2]) / rho2 f[1, idx] = -8 * xcm * (zeta[1] + R * zeta[2]) / rho3 f[2, idx] = 16 * xcm * zeta[2] / rho4 # coefficient of dz dz xmm *= 8 * sigma * zeta[1] * zeta[1] / rho2 f[0, idx] += xmm * R * R f[1, idx] -= 2 * xmm * R / rho f[2, idx] += 4 * xmm / rho2 return f # r,r # 1,r 1,1 # srr,r srr,1 srr,srr # sr1,r sr1,1 sr1,srr sr1,sr1 # s11,r s11,1 s11,srr s11,sr1 s11,s11 def _ftGGA_d_jT_op_m2z(v, rho, zeta, srz, szz): # srm += srz*r # smm += 2srz*r*z + szz*r*r # 0 : r, r # 1 : r, z # 6 : srz, r # 7 : srz, z # 10 : szz, r ngrids = v.shape[1] f = np.zeros((15, ngrids), dtype=v.dtype) f[0] = 2 * v[4] * szz f[1] = 2 * v[4] * srz f[6] = v[3] + 2 * v[4] * zeta[0] f[7] = 2 * v[4] * rho f[10] = 2 * v[4] * rho assert (tuple(f[0].shape) == tuple(f[1].shape)) assert (tuple(f[0].shape) == tuple(f[6].shape)) assert (tuple(f[0].shape) == tuple(f[10].shape)) return f def _ftGGA_d_jT_op_z2R(v, zeta, srP, sPP): # z = z[0] # srz = srP*z[1] # szz = sPP*z[1]**2 # 2 : P, P # 7 : srP, P # 11 : sPP, P ngrids = v.shape[1] f = np.zeros((15, ngrids), dtype=v.dtype) f[2] = 2 * v[4] * sPP * (zeta[3] * zeta[1] + zeta[2] * zeta[2]) f[2] += v[1] * zeta[2] + v[3] * srP * zeta[3] f[7] = v[3] * zeta[2] f[11] = 2 * v[4] * zeta[1] * zeta[2] assert (tuple(f[2].shape) == tuple(f[7].shape)) assert (tuple(f[2].shape) == tuple(f[11].shape)) return f def _ftGGA_d_jT_op_R2Pi(v, rho, Pi, srr, srP, sPP): # R = 4Pi/(r**2) = Pi*d[0] # srR = srP*d[0] + srr*Pi*d[1] # sRR = sPP*d[0]**2 + 2*Pi*d[1]*srP*d[0] + srr*(Pi*d[1])**2 # d^n(d[0]) # d[n] = --------- # dr^n ngrids = v.shape[-1] f = np.zeros((15, ngrids), dtype=v.dtype) d = np.zeros((4, ngrids), dtype=v.dtype) idx = np.abs(rho) > 1e-15 d[0, idx] = 4 / rho[idx] / rho[idx] d[1, idx] = -2 * d[0, idx] / rho[idx] d[2, idx] = -3 * d[1, idx] / rho[idx] d[3, idx] = -4 * d[2, idx] / rho[idx] # rho, rho f[0] = v[1] * Pi * d[2] f[0] += v[3] * (srP * d[2] + srr * Pi * d[3]) f[0] += 2 * v[4] * sPP * (d[2] * d[0] + d[1] * d[1]) f[0] += 2 * v[4] * srP * Pi * (3 * d[2] * d[1] + d[3] * d[0]) f[0] += 2 * v[4] * srr * Pi * Pi * (d[3] * d[1] + d[2] * d[2]) # rho, Pi f[1] = v[1] * d[1] + v[3] * srr * d[2] f[1] += 2 * v[4] * srP * (d[2] * d[0] + d[1] * d[1]) f[1] += 4 * v[4] * srr * Pi * d[2] * d[1] # Pi, Pi f[2] = 2 * v[4] * srr * d[1] * d[1] # rho, rr f[3] = v[3] * Pi * d[2] + 2 * v[4] * Pi * Pi * d[2] * d[1] # Pi, rr f[4] = v[3] * d[1] + 2 * v[4] * Pi * d[1] * d[1] # rho, rP f[6] = v[3] * d[1] + 2 * v[4] * Pi * (d[2] * d[0] + d[1] * d[1]) # Pi, rP f[7] = 2 * v[4] * d[0] * d[1] # rho, PP f[10] = 2 * v[4] * d[0] * d[1] for row in f[1:]: if hasattr(row, 'shape'): assert (tuple(row.shape) == tuple(f[0].shape)) return f def _ftGGA_d_jT_op(v, rho, Pi, R, zeta): # raise NotImplementedError ("Second density derivatives for fully-" # "translated GGA functionals") # Generates contributions to the first five elements, # then 6,7, then 10,11 # of the lower-triangular packed Hessian # (I.E., no double gradient derivatives) # for the terms added in the fully-translated extension of tGGA # ab -> cs vcm = (v[2] - v[4]) / 2.0 vmm = (v[2] + v[4] - v[3]) / 4.0 v[3] = vcm v[4] = vmm v = np.asarray(v) # Intermediates srr = (rho[1:4, :] * rho[1:4, :]).sum(0) srP = (rho[1:4, :] * R[1:4, :]).sum(0) sPP = (R[1:4, :] * R[1:4, :]).sum(0) srz = srP * zeta[1] szz = sPP * zeta[1] * zeta[1] # cs -> rho, zeta f = _ftGGA_d_jT_op_m2z(v, rho[0], zeta, srz, szz) v = _ftGGA_jT_op_m2z(v, rho[0], zeta, srz, szz) # rho, zeta -> rho, R # The for loops here are because I'm guessing that repeated # initialization of large arrays to zero is slower than a short, # shallow Python loop f = _jT_f_j(f, _ftGGA_jT_op_z2R, zeta, srP, sPP) f += _ftGGA_d_jT_op_z2R(v, zeta, srP, sPP) v = _ftGGA_jT_op_z2R(v, zeta, srP, sPP) # rho, R -> rho, Pi srP = (rho[1:4, :] * Pi[1:4, :]).sum(0) sPP = (Pi[1:4, :] * Pi[1:4, :]).sum(0) f = _jT_f_j(f, _ftGGA_jT_op_R2Pi, rho, R, srr, srP, sPP) f += _ftGGA_d_jT_op_R2Pi(v, rho[0], Pi[0], srr, srP, sPP) return f