#!/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
import copy
from scipy import linalg
from pyscf import lib, dft
from pyscf.lib import logger
from pyscf.dft import libxc
from pyscf.dft.gen_grid import Grids
from pyscf.dft.numint import _NumInt, NumInt
from pyscf.mcpdft import pdft_veff, tfnal_derivs, _libxc, _dms, pdft_feff, pdft_eff
from pyscf.mcpdft.otpd import get_ontop_pair_density
from pyscf import __config__
FT_R0 = getattr(__config__, 'mcpdft_otfnal_ftransfnal_R0', 0.9)
FT_R1 = getattr(__config__, 'mcpdft_otfnal_ftransfnal_R1', 1.15)
FT_A = getattr(__config__, 'mcpdft_otfnal_ftransfnal_A', -475.60656009)
FT_B = getattr(__config__, 'mcpdft_otfnal_ftransfnal_B', -379.47331922)
FT_C = getattr(__config__, 'mcpdft_otfnal_ftransfnal_C', -85.38149682)
OT_ALIAS = {'MC23': 'tMC23'}
OT_HYB_ALIAS = {'PBE0' : '0.25*HF + 0.75*PBE, 0.25*HF + 0.75*PBE',
}
REG_OT_FUNCTIONALS={}
# ALIAS for the preset on-top functional
OT_PRESET={
# Reparametrized-M06L: rep-M06L
# MC23 = { '0.2952*HF + (1-0.2952)*rep-M06L, 0.2952*HF + (1-0.2952)*rep-M06L'}}
# XC_ID_MGGA_C_M06_L = 233
# XC_ID_MGGA_X_M06_L = 203
'MC23':{
'xc_base':'M06L',
'ext_params':{203: np.array([3.352197, 6.332929e-01, -9.469553e-01, 2.030835e-01,
2.503819, 8.085354e-01, -3.619144, -5.572321e-01,
-4.506606, 9.614774e-01, 6.977048, -1.309337, -2.426371,
-7.896540e-03, 1.364510e-02, -1.714252e-06, -4.698672e-05, 0.0]),
233: np.array([0.06, 0.0031, 0.00515088, 0.00304966, 2.427648, 3.707473,
-7.943377, -2.521466, 2.658691, 2.932276, -8.832841e-01,
-1.895247, -2.899644, -5.068570e-01, -2.712838, 9.416102e-02,
-3.485860e-03, -5.811240e-04, 6.668814e-04, 0.0, 2.669169e-01,
-7.563289e-02, 7.036292e-02, 3.493904e-04, 6.360837e-04, 0.0, 1e-10])},
'hyb':(0.2952,0.2952,0),
'facs':(0.7048,0.7048)}
}
[docs]
def register_otfnal(xc_code, preset):
'''
This function registers the new on-top functional if it hasn't been
registered previously.
Args:
xc_code: str
The name of the on-top functional to be registered.
preset: dict
The dictionary containing the information about the on-top functional
to be registered.
xc_base: str
The name of the underylying KS-functional in the libxc library.
ext_params: dict, with LibXC exchange and correlation functional integer ID as key, and
an array-like object containing the functional parameters as value.
hyb: tuple
The hybrid functional parameters.
facs: tuple
The mixing factors.
kwargs: dict
The additional keyword arguments.
'''
libxc_register_code = xc_code.upper ()
libxc_base_code = preset['xc_base']
ext_params = preset['ext_params']
hyb = preset.get('hyb', None)
facs = preset.get('facs', None)
libxc.register_custom_functional_(libxc_register_code, libxc_base_code,
ext_params=ext_params, hyb=hyb, facs=facs)
REG_OT_FUNCTIONALS[xc_code.upper()] = {'hyb_x':preset.get('hyb',[0])[0],
'hyb_c':preset.get('hyb',[0])[0]}
[docs]
def unregister_otfnal(xc_code):
'''
This function unregisters the on-top functional if it has been registered
previously.
Args:
xc_code: str
The name of the on-top functional to be unregistered.
'''
try:
if xc_code.upper() in REG_OT_FUNCTIONALS:
libxc_unregister_code = xc_code.upper()
libxc.unregister_custom_functional_(libxc_unregister_code)
del REG_OT_FUNCTIONALS[xc_code.upper()]
except Exception as e:
raise RuntimeError(f"Failed to unregister functional '{xc_code}': {e}") from e
def _get_registered_ot_functional(xc_code, mol):
'''
This function returns the on-top functional if it has been registered
previously.
Args:
xc_code: str
The name of the on-top functional to be registered.
'''
if (xc_code.upper() not in REG_OT_FUNCTIONALS) and (xc_code.upper() in OT_PRESET):
preset = OT_PRESET[xc_code.upper()]
register_otfnal(xc_code, preset)
logger.info(mol, 'Registered the on-top functional: %s', xc_code)
return xc_code.upper()
[docs]
def energy_ot (ot, casdm1s, casdm2, mo_coeff, ncore, max_memory=2000, hermi=1):
'''Compute the on-top energy - the last term in
E_MCPDFT = h_pq l_pq + 1/2 v_pqrs l_pq l_rs + E_ot[rho,Pi]
Args:
ot : an instance of otfnal class
casdm1s : ndarray of shape (2, ncas, ncas)
Contains spin-separated one-body density matrices in an
active-orbital basis
casdm2 : ndarray of shape (ncas, ncas, ncas, ncas)
Contains spin-summed two-body density matrix in an active-
orbital basis
mo_coeff : ndarray of shape (nao, nmo)
Contains molecular orbital coefficients for active-space
orbitals. Columns ncore through ncore+ncas give the basis
in which casdm1s and casdm2 are expressed.
ncore : integer
Number of doubly occupied inactive "core" orbitals not
explicitly included in casdm1s and casdm2
Kwargs:
max_memory : int or float
maximum cache size in MB
default is 2000
hermi : int
1 if 1rdms are assumed hermitian, 0 otherwise
Returns : float
The MC-PDFT on-top (nonclassical) energy
'''
E_ot = 0.0
ni, xctype = ot._numint, ot.xctype
if xctype=='HF': return E_ot
dens_deriv = ot.dens_deriv
Pi_deriv = ot.Pi_deriv
nao = mo_coeff.shape[0]
ncas = casdm2.shape[0]
cascm2 = _dms.dm2_cumulant (casdm2, casdm1s)
dm1s = _dms.casdm1s_to_dm1s (ot, casdm1s, mo_coeff=mo_coeff, ncore=ncore,
ncas=ncas)
mo_cas = mo_coeff[:,ncore:][:,:ncas]
t0 = (logger.process_clock (), logger.perf_counter ())
make_rho = tuple (ni._gen_rho_evaluator (ot.mol, dm1s[i,:,:], hermi=hermi, with_lapl=False) for
i in range(2))
for ao, mask, weight, _ in ni.block_loop (ot.mol, ot.grids, nao,
dens_deriv, max_memory):
rho = np.asarray ([m[0] (0, ao, mask, xctype) for m in make_rho])
t0 = logger.timer (ot, 'untransformed density', *t0)
Pi = get_ontop_pair_density (ot, rho, ao, cascm2, mo_cas,
Pi_deriv, mask)
t0 = logger.timer (ot, 'on-top pair density calculation', *t0)
if rho.ndim == 2:
rho = np.expand_dims (rho, 1)
Pi = np.expand_dims (Pi, 0)
E_ot += ot.eval_ot (rho, Pi, dderiv=0, weights=weight)[0].dot (weight)
t0 = logger.timer (ot, 'on-top energy calculation', *t0)
return E_ot
[docs]
class otfnal:
r''' Parent class of on-top pair-density functional. The main
callable is ``eval_ot,'' which is comparable to pyscf.dft.libxc
``eval_xc.'' A true ``kernel'' method, which would take arbitrary
1- and 2-RDMs and return the total PDFT energy, awaits design
decisions on how far I'm willing/able to generalize the otpd
functions. For instance, in MP2 or CCSD, the 2-RDM spans the
whole orbital space and it may not be possible to hold it in
memory. At present, it's all designed around MC-SCF, which is
why the ``kernel'' function that actually calculates the energy
is in mcpdft.py instead of here.
Attributes:
mol : object of class pyscf.gto.mole
grids : object of class pyscf.dft.gen_grid.Grids
eval_ot : function with calling signature shown below
_numint : object of class pyscf.dft.NumInt
member functions "hybrid_coeff", "nlc_coeff, "rsh_coeff",
and "_xc_type" (at least) must be overloaded; see below
otxc : string
name of on-top pair-density exchange-correlation functional
'''
def __init__ (self, mol):
self.mol = mol
self.verbose = mol.verbose
self.stdout = mol.stdout
Pi_deriv = 0
def _init_info (self):
logger.info (self, 'Building %s functional', self.otxc)
hyb = self._numint.rsh_and_hybrid_coeff(self.otxc, spin=self.mol.spin)[2]
if hyb[0] > 0:
logger.info (self, 'Hybrid functional with %s CASSCF exchange',
hyb)
@property
def xctype (self):
return self._numint._xc_type (self.otxc)
@property
def dens_deriv (self):
return ['LDA', 'GGA', 'MGGA'].index (self.xctype)
[docs]
def eval_ot (self, rho, Pi, dderiv=0, **kwargs):
r''' Evaluate the on-dop energy and its functional derivatives
on a grid
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
Returns:
eot : ndarray of shape (ngrids)
integrand of the on-top exchange-correlation energy
vot : (array_like (rho), array_like (Pi)) or None
first functional derivative of Eot wrt (density, pair-
density) and their derivatives
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, |drho|^2, drho'.dPi, |dPi|) stopping at Pi (3
elements) for t-LDA and |drho|^2 (6 elements) for t-GGA.
'''
raise NotImplementedError("on-top xc functional not defined")
energy_ot = energy_ot
get_eff_1body = pdft_eff.get_eff_1body
get_eff_2body = pdft_eff.get_eff_2body
get_eff_2body_kl = pdft_eff.get_eff_2body_kl
get_veff_1body = pdft_veff.get_veff_1body
get_veff_2body = pdft_veff.get_veff_2body
get_veff_2body_kl = pdft_veff.get_veff_2body_kl
get_feff_1body = pdft_feff.get_feff_1body
get_feff_2body = pdft_feff.get_feff_2body
[docs]
def reset (self, mol=None):
''' Discard cached grid data and optionally update the mol '''
if mol is not None:
self.mol = mol
self.grids.reset (mol=mol)
[docs]
class transfnal (otfnal):
__doc__ = otfnal.__doc__ + r'''
``translated functional'' of Li Manni et al., JCTC 10, 3669 (2014).
The extra attributes are all callables; see their docstrings for
more information.
Args:
ks : object of :class:`dft.RKS`
ks.xc is the Kohn-Sham functional being ``translated''
'''
transl_prefix='t'
def __init__ (self, ks, **kwargs):
otfnal.__init__(self, ks.mol, **kwargs)
self.otxc = 't' + ks.xc
self._numint = copy.copy (ks._numint)
self._numint.libxc = libxc
self.grids = copy.copy (ks.grids)
self._numint.hybrid_coeff = t_hybrid_coeff.__get__(self._numint)
self._numint.nlc_coeff = t_nlc_coeff.__get__(self._numint)
self._numint.rsh_coeff = t_rsh_coeff.__get__(self._numint)
self._numint.eval_xc = t_eval_xc.__get__(self._numint)
self._numint._xc_type = t_xc_type.__get__(self._numint)
self._init_info ()
[docs]
def get_ratio (self, Pi, rho_avg):
r''' R = Pi / [rho/2]^2 = Pi / rho_avg^2
An intermediate quantity when computing the translated spin
densities
Note this function returns 1 for values and 0 for
derivatives for every point where the charge density is
close to zero (i.e., convention: 0/0 = 1)
Args:
Pi : ndarray of shape (*,ngrids)
Contains on-top pair density on a grid
rho_avg : ndarray of shape (*,ngrids)
Contains the average of the spin-up and spin-down
charge densities on a grid, (rho[0]+rho[1])/2
Returns:
R : ndarray of shape (*,ngrids)
on-top ratio
'''
nderiv = min (rho_avg.shape[0], Pi.shape[0])
ngrids = rho_avg.shape[1]
assert (Pi.shape[1] == ngrids)
if nderiv > 4:
raise NotImplementedError("derivatives above order 1")
R = np.zeros ((nderiv,ngrids), dtype=Pi.dtype)
R[0,:] = 1
R[0, Pi[0] == 0] = 0.0
idx = rho_avg[0] >= (1e-15 / 2)
# Chain rule!
for ideriv in range(nderiv):
R[ideriv,idx] = Pi[ideriv,idx] / rho_avg[0,idx] / rho_avg[0,idx]
# Product rule!
for ideriv in range (1,nderiv):
R[ideriv,idx] -= (2 * rho_avg[ideriv,idx] * R[0,idx]
/ rho_avg[0,idx])
return R
[docs]
def get_rho_translated (self, Pi, rho, _fn_deriv=0):
r''' Compute the "translated" alpha and beta densities:
For the unrestricted case,
rho = [rho^a, rho^b]
Here:
rho^a will have dim of 1,4 or 6 depends on the functional. For MGGA,
rho^a = [rho_u,grad_xu, grad_yu, grad_zu, laplacian_u, tau_u]
Similar for rho_b.
The translation is done as follows:
rho_t^a = (rho/2) * (1 + zeta)
rho_t^b = (rho/2) * (1 - zeta)
rho'_t^a = (rho'/2) * (1 + zeta)
rho'_t^b = (rho'/2) * (1 - zeta)
tau_t^a = (tau/2) * (1 + zeta)
tau_t^b = (tau/2) * (1 - zeta)
See "get_zeta" for the meaning of "zeta"
Args:
Pi : ndarray of shape (*, ngrids)
containing on-top pair density [and derivatives]
rho : ndarray of shape (2, *, ngrids)
containing spin density [and derivatives]
Kwargs:
_fn_deriv : integer
Order of functional derivatives of zeta to compute.
In "translated" functionals, no functional derivatives
of zeta are used. This kwarg is used for convenience
when calling from children classes. It changes the
return signature and should not normally be touched by
users.
Returns:
rho_t : ndarray of shape (2,*,ngrids)
Translated spin density (and derivatives) in case of LDA or GGAs
Translated spin density, derivatives, and kinetic energy density in case of MGGA
'''
# For nonzero charge & pair density, set alpha dens = beta dens
# = 1/2 charge dens
rho_avg = (rho[0,:,:] + rho[1,:,:]) / 2
rho_t = rho.copy ()
rho_t[0] = rho_t[1] = rho_avg
# For 0 <= ratio < 1 and 0 <= rho, correct spin density using on-top
# density
nderiv_R = Pi.shape[0] if _fn_deriv else 1
R = self.get_ratio (Pi[0:nderiv_R,:], rho_avg[0:nderiv_R,:])
zeta = self.get_zeta (R, fn_deriv=_fn_deriv)
# Chain rule!
w = rho_avg * zeta[0:1]
rho_t[0] += w
rho_t[1] -= w
if _fn_deriv > 0: return rho_t, R, zeta
return rho_t
[docs]
def get_zeta (self, R, fn_deriv=0, _Rmax=1):
r''' Compute the intermediate zeta used to compute the
translated spin densities and its functional derivatives
From the original translation [Li Manni et al., JCTC 10, 3669
(2014)]:
zeta = (1-ratio)^(1/2) ; ratio < 1
= 0 ; otherwise
Args:
R : ndarray of shape (*,ngrids)
Ratio (4Pi/rho^2) and possibly its spatial derivatives
Only the first row is used in this function
Kwargs:
fn_deriv : integer
order of functional derivative (d^n z / dR^n) to return
along with the value of zeta
_Rmax : float
maximum value of R for which to compute zeta or its
derivatives; columns of zeta with R[0]>_Rmax are zero.
This is a hook for the ``fully-translated'' child class
and should not be touched normally.
Returns:
zeta : ndarray of shape (fn_deriv+1, ngrids)
'''
if R.ndim == 2: R = R[0]
ngrids = R.size
zeta = np.zeros ((fn_deriv+1, ngrids), dtype=R.dtype)
idx = R < _Rmax
zeta[0,idx] = np.sqrt (1.0 - R[idx])
if fn_deriv:
zeta[1,idx] = -0.5 / zeta[0,idx]
if fn_deriv > 1: fac = 0.5 / (1.0-R[idx])
for n in range (1,fn_deriv):
zeta[n+1,idx] = zeta[n,idx] * (2*n-1) * fac
return zeta
[docs]
def split_x_c (self):
''' Get one translated functional for just the exchange and one
for just the correlation part of the energy.
Returns:
xfnal : object of :class:`transfnal`
this functional, but only the exchange part
cfnal : object of :class:`transfnal`
this functional, but only the correlation part
'''
xc_base = self.otxc[len (self.transl_prefix):]
x_code, c_code = _libxc.split_x_c_comma (xc_base)
x_code = self.transl_prefix + x_code + ','
c_code = self.transl_prefix + ',' + c_code
xfnal = copy.copy (self)
xfnal._numint = copy.copy (self._numint)
xfnal.grids = copy.copy (self.grids)
xfnal.verbose = self.verbose
xfnal.stdout = self.stdout
xfnal.otxc = x_code
cfnal = copy.copy (self)
cfnal._numint = copy.copy (self._numint)
cfnal.grids = copy.copy (self.grids)
cfnal.verbose = self.verbose
cfnal.stdout = self.stdout
cfnal.otxc = c_code
return xfnal, cfnal
[docs]
def jT_op (self, x, rho, Pi):
r''' Evaluate jTx = (x.j)T where j is the Jacobian of the
translated densities in terms of the untranslated density and
pair density
Args:
x : ndarray of shape (2,*,ngrids)
Usually, a functional derivative of the on-top xc energy
wrt translated densities
rho : ndarray of shape (2,*,ngrids)
containing spin-density [and derivatives]
Pi : ndarray with shape (*,ngrids)
containing on-top pair density [and derivatives]
Returns: ndarray of shape (*,ngrids)
Usually, a functional derivative of the on-top pair density
exchange-correlation energy wrt to total density and its
derivatives. The potential must be spin-symmetric in
pair-density functional theory.
2 rows for tLDA, 3 rows for tGGA, and 4 rows for meta-GGA
'''
# ordering: rho, Pi, |rho'|^2, tau
ncol = (2, 3, 4)[self.dens_deriv]
ngrid = rho.shape[-1]
jTx = np.zeros ((ncol,ngrid), dtype=x[0].dtype)
rho = rho.sum (0)
R = self.get_ratio (Pi, rho/2)
zeta = self.get_zeta (R, fn_deriv=1)
jTx[:2] = tfnal_derivs._gentLDA_jT_op (x, rho, Pi, R, zeta)
if self.dens_deriv > 0:
jTx[:3] += tfnal_derivs._tGGA_jT_op (x, rho, Pi, R, zeta)
if self.dens_deriv > 1:
jTx[:4] += tfnal_derivs._tmetaGGA_jT_op(x, rho, Pi, R, zeta)
return jTx
[docs]
def d_jT_op (self, x, rho, Pi):
r''' Evaluate the x.(nabla j) contribution to the second density
derivatives of the on-top energy in terms of the untranslated
density and pair density
Args:
x : ndarray of shape (2,*,ngrids)
Usually, a functional derivative of the on-top xc energy
wrt translated densities
rho : ndarray of shape (2,*,ngrids)
containing spin-density [and derivatives]
Pi : ndarray with shape (*,ngrids)
containing on-top pair density [and derivatives]
Returns: ndarray of shape (*,ngrids)
second derivative of the translation dotted with x
3 rows for tLDA and 5 rows for tGGA
'''
nrow = 3 + 2*int(self.dens_deriv>0)
f = np.zeros ((nrow, x[0].shape[-1]), dtype=x[0].dtype)
rho = rho.sum (0)
R = self.get_ratio (Pi, rho/2)
zeta = self.get_zeta (R, fn_deriv=2)
f[:3] = tfnal_derivs._gentLDA_d_jT_op (x, rho, Pi, R, zeta)
if self.dens_deriv:
f[:] += tfnal_derivs._tGGA_d_jT_op (x, rho, Pi, R, zeta)
if self.verbose >= logger.DEBUG:
idx = zeta[0] == 0
logger.debug (self, 'MC-PDFT fot zeta check: %d zeta=0 columns',
np.count_nonzero (idx))
if np.count_nonzero (idx):
for ix, frow in enumerate (f):
logger.debug (self, 'MC-PDFT fot zeta check: f[%d] norm '
'over zeta=0 columns: %e', ix, linalg.norm (frow[idx]))
return f
[docs]
def eval_ot (self, rho, Pi, dderiv=1, weights=None, _unpack_vot=True):
eot, vot, fot = tfnal_derivs.eval_ot (self, rho, Pi, dderiv=dderiv,
weights=weights, _unpack_vot=_unpack_vot)
if (self.verbose <= logger.DEBUG) or (dderiv<1) or (weights is None):
return eot, vot, fot
if rho.ndim == 2: rho = rho[:,None,:]
if Pi.ndim == 1: Pi = Pi[None,:]
rho_tot = rho.sum (0)
nvr = rho_tot.shape[0]
ngrids = rho_tot.shape[-1]
r0 = 2*(np.random.rand (ngrids)-1)
for p in range (20):
# ~~~ eval_xc reference ~~~
rho_t0 = self.get_rho_translated (Pi, rho)
exc, vxc_p, fxc = self._numint.eval_xc (self.otxc, (rho_t0[0,:,:],
rho_t0[1,:,:]), spin=1, relativity=0, deriv=dderiv,
verbose=self.verbose)[:3]
exc *= rho_t0[:,0,:].sum (0)
vxc_p = tfnal_derivs._reshape_vxc_sigma (vxc_p, self.dens_deriv)
vxc = tfnal_derivs._unpack_sigma_vector (vxc_p, rho_t0[0,1:4], rho_t0[1,1:4])
if dderiv>1: fxc = tfnal_derivs._pack_fxc_ltri (fxc, self.dens_deriv)
# ~~~ shift translated rho directly ~~~
r = rho_t0 * r0 / 2**p
drho_t = np.zeros_like (rho_t0, dtype=rho_t0.dtype)
ndf = 2 * (1 + int (nvr>1))
drho_t[0,0,0::ndf] = r[0,0,0::ndf]
drho_t[1,0,1::ndf] = r[1,0,1::ndf]
if ndf > 2:
drho_t[0,1:4,2::ndf] = r[0,1:4,2::ndf]
drho_t[1,1:4,3::ndf] = r[1,1:4,3::ndf]
# ~~~ eval_xc @ rho_t1 = rho_t0 + drho_t ~~~
rho_t1 = rho_t0 + drho_t
exc1, vxc1 = self._numint.eval_xc (self.otxc, (rho_t1[0,:,:],
rho_t1[1,:,:]), spin=1, relativity=0, deriv=dderiv,
verbose=self.verbose)[:2]
exc1 *= rho_t1[:,0,:].sum (0)
vxc1 = tfnal_derivs._unpack_vxc_sigma (vxc1, rho_t1, self.dens_deriv)
df_lbl = ('rhoa', 'rhob', "rhoa'", "rhob'")[:2*(1+int(nvr>1))]
_v_err_report (self, 'eval_xc {}'.format (p), df_lbl, rho_t0[0], rho_t0[1], exc, vxc,
vxc_p, fxc, exc1, vxc1, drho_t, weights)
# ~~~ eval_ot compare ~~~
nvP = vot[1].shape[0]
d1 = rho_tot[1:4] if nvr > 1 else None
d2 = Pi[1:4] if nvP > 1 else None
if _unpack_vot:
vot_u = vot
vot_p = tfnal_derivs.eval_ot (self, rho, Pi, dderiv=dderiv,
weights=weights, _unpack_vot=False)[1]
else:
vot_p = vot
vot_u = tfnal_derivs._unpack_sigma_vector (vot, d1, d2)
drho = rho_tot * r0 / 2**p
dPi = Pi * r0 / 2**p
r, P = drho.copy (), dPi.copy ()
drho[:] = dPi[:] = 0.0
ndf = 2 + int(nvr>1) + int(nvP>1)
drho[0,0::ndf] = r[0,0::ndf]
dPi[0,1::ndf] = P[0,1::ndf]
if ndf > 2: drho[1:4,2::ndf] = r[1:4,2::ndf]
if ndf > 3: dPi[1:4,3::ndf] = P[1:4,3::ndf]
rho1 = rho+(drho/2) # /2 because rho has one more dimension of size = 2
# that gets summed later
Pi1 = Pi + dPi
# ~~~ ignore numerical instability of unfully-translated fnals ~~~
if self.otxc[0].lower () == 't':
z0 = self.get_zeta (self.get_ratio (Pi, rho_tot/2)[0],
fn_deriv=0)[0]
z1 = self.get_zeta (self.get_ratio (Pi1, rho1.sum(0)/2)[0],
fn_deriv=0)[0]
idx = (z0==0) |(z1==0)
drho[:,idx] = dPi[:,idx] = 0
rho1[:,:,idx] = rho[:,:,idx]
Pi1[:,idx] = Pi[:,idx]
# ~~~ eval_ot @ rho1 = rho + drho ~~~
eot1, vot1 = tfnal_derivs.eval_ot (self, rho1, Pi1,
dderiv=dderiv, weights=weights, _unpack_vot=True)[:2]
#vot1 = tfnal_derivs._unpack_sigma_vector (vot1, d1, d2)
df_lbl = ('rho', 'Pi', "rho'", "Pi'")[:ndf]
_v_err_report (self, 'eval_ot {}'.format (p), df_lbl, rho_tot, Pi, eot, vot_u, vot_p, fot,
eot1, vot1, (drho, dPi), weights)
return eot, vot, fot
eval_ot.__doc__ = otfnal.eval_ot.__doc__
# TODO: test continuity of smoothing function and warn at initialization?
[docs]
class ftransfnal (transfnal):
__doc__ = transfnal.__doc__ + r'''
Extra attributes for ``fully-translated'' extension of Carlson
et al., JCTC 11, 4077 (2015):
R0 : float
connecting point to polynomial smoothing function;
R0 <= 1.0. Default is 0.9.
R1 : float
endpoint of polynomial smoothing function, zeta(R1) =
zeta'(R1) = zeta''(R1) = 0.0; R1 >= 1.0. Default is 1.15.
A : float
Quintic coefficient of polynomial smoothing function.
Default = -475.60656009 is chosen to make zeta continuous
through its second derivative at given the default R0 and R1.
B : float
Quartic coefficient of polynomial smoothing function.
Default = -379.47331922 is chosen to make zeta continuous
through its second derivative given the default R0 and R1.
C : float
Cubic coefficient of polynomial smoothing function.
Default = -85.38149682 chosen to make zeta continuous
through its second derivative given the default R0 and R1.
'''
transl_prefix='ft'
def __init__ (self, ks, **kwargs):
otfnal.__init__(self, ks.mol, **kwargs)
self.R0=FT_R0
self.R1=FT_R1
self.A=FT_A
self.B=FT_B
self.C=FT_C
self.otxc = 'ft' + ks.xc
self._numint = copy.copy (ks._numint)
self._numint.libxc = libxc
self.grids = copy.copy (ks.grids)
self._numint.hybrid_coeff = ft_hybrid_coeff.__get__(self._numint)
self._numint.nlc_coeff = ft_nlc_coeff.__get__(self._numint)
self._numint.rsh_coeff = ft_rsh_coeff.__get__(self._numint)
self._numint.eval_xc = ft_eval_xc.__get__(self._numint)
self._numint._xc_type = ft_xc_type.__get__(self._numint)
self._init_info ()
Pi_deriv = transfnal.dens_deriv
[docs]
def get_rho_translated (self, Pi, rho):
r''' Compute the "fully-translated" alpha and beta densities
and their derivatives. This is the same as "translated" except
rho'_t^a += zeta' * rho / 2
rho'_t^b -= zeta' * rho / 2
And the functional form of "zeta" is changed (see "get_zeta")
Args:
Pi : ndarray of shape (*, ngrids)
containing on-top pair density [and derivatives]
rho : ndarray of shape (2, *, ngrids)
containing spin density [and derivatives]
Returns:
rho_ft : ndarray of shape (2,*,ngrids)
Fully-translated spin density (and derivatives)
'''
nderiv_R = max (rho.shape[1], Pi.shape[0])
if nderiv_R == 1: return transfnal.get_rho_translated (self, Pi, rho)
# Spin density and first term of spin gradient in common with transfnal
rho_avg = (rho[0,:,:] + rho[1,:,:]) / 2
rho_ft, R, zeta = transfnal.get_rho_translated (self, Pi, rho,
_fn_deriv=1)
# Add propagation of chain rule through zeta
w = (rho_avg[0] * zeta[1])[None,:] * R[1:4]
rho_ft[0][1:4] += w
rho_ft[1][1:4] -= w
return rho_ft
[docs]
def get_zeta (self, R, fn_deriv=1):
r''' Compute the intermediate zeta used to compute the translated spin
densities and its functional derivatives
From the "full" translation [Carlson et al., JCTC 11, 4077 (2015)]:
zeta = (1-R)^(1/2) ; R < R0
= A*(R-R1)^5 + B*(R-R1)^4 + C*(R-R1)^3 ; R0 <= R < R1
= 0 ; otherwise
Args:
R : ndarray of shape (*,ngrids)
Ratio (4Pi/rho^2) and possibly its spatial derivatives
Only the first row is used in this function
Kwargs:
fn_deriv : integer
order of functional derivative (d^n z / dR^n) to return
along with the value of zeta
Returns:
zeta : ndarray of shape (fn_deriv+1, ngrids)
'''
# Rmax unused here. It only needs to be passed in the transfnal version
if R.ndim == 2: R = R[0]
R0, R1, A, B, C = self.R0, self.R1, self.A, self.B, self.C
zeta = transfnal.get_zeta (self, R, fn_deriv=fn_deriv, _Rmax=R0)
idx = (R >= R0) & (R < R1)
if not np.count_nonzero (idx): return zeta
zeta[:,idx] = 0.0
dR = np.stack ([np.power (R[idx] - R1, n)
for n in range (1,6)], axis=0)
def _derivs ():
yield A*dR[4] + B*dR[3] + C*dR[2]
yield 5*A*dR[3] + 4*B*dR[2] + 3*C*dR[1]
yield 20*A*dR[2] + 12*B*dR[1] + 6*C*dR[0]
yield 60*A*dR[1] + 24*B*dR[0] + 6*C
yield 120*A*dR[0] + 24*B
yield 120*A
for n, row in enumerate (_derivs ()):
zeta[n,idx] = row
if n == fn_deriv: break
return zeta
[docs]
def jT_op (self, x, rho, Pi, **kwargs):
r''' Evaluate jTx = (x.j)T where j is the Jacobian of the
translated densities in terms of the untranslated density and
pair density
Args:
x : ndarray of shape (2,*,ngrids)
Usually, a functional derivative of the on-top xc energy
wrt translated densities
rho : ndarray of shape (2,*,ngrids)
containing spin-density [and derivatives]
Pi : ndarray with shape (*,ngrids)
containing on-top pair density [and derivatives]
Returns: ndarray of shape (*,ngrids)
Usually, a functional derivative of the on-top pair density
exchange-correlation energy wrt to total density and its
derivatives. The potential must be spin-symmetric in
pair-density functional theory.
'''
ntc = 2 + int(self.dens_deriv>0)
ncol = 2 + 3*int(self.dens_deriv>0)
ngrid = rho.shape[-1]
jTx = np.zeros ((ncol,ngrid), dtype=x[0].dtype)
jTx[:ntc,:] = transfnal.jT_op (self, x, rho, Pi, **kwargs)
rho = rho.sum (0)
R = self.get_ratio (Pi[0:4,:], rho[0:4,:]/2)
zeta = self.get_zeta (R[0], fn_deriv=2)
if self.dens_deriv > 0:
jTx[:] += tfnal_derivs._ftGGA_jT_op (x, rho, Pi, R, zeta)
return jTx
[docs]
def d_jT_op (self, x, rho, Pi, **kwargs):
r''' Evaluate the x.(nabla j) contribution to the second density
derivatives of the on-top energy in terms of the untranslated
density and pair density
Args:
x : ndarray of shape (2,*,ngrids)
Usually, a functional derivative of the on-top xc energy
wrt translated densities
rho : ndarray of shape (2,*,ngrids)
containing spin-density [and derivatives]
Pi : ndarray with shape (*,ngrids)
containing on-top pair density [and derivatives]
Returns: ndarray of shape (*,ngrids)
second derivative of the translation dotted with x
3 rows for tLDA and 5 rows for tGGA
'''
nrow_t = 3 + 2*int(self.dens_deriv>0)
nrow = 3 + 12*int(self.dens_deriv>0)
f = np.zeros ((nrow, x[0].shape[-1]), dtype=x[0].dtype)
f[:nrow_t] = transfnal.d_jT_op (self, x, rho, Pi, **kwargs)
if self.dens_deriv:
rho = rho.sum (0)
R = self.get_ratio (Pi[0:4,:], rho[0:4,:]/2)
zeta = self.get_zeta (R[0], fn_deriv=3)
f[:] += tfnal_derivs._ftGGA_d_jT_op (x, rho, Pi, R, zeta)
return f
_CS_a_DEFAULT = 0.04918
_CS_b_DEFAULT = 0.132
_CS_c_DEFAULT = 0.2533
_CS_d_DEFAULT = 0.349
def _sanity_check_ftot(xc_code):
'''
This function will check the functional type and will
raise the warning for fully-translated MGGAs or custom functionals.
'''
xc_type = libxc.xc_type(xc_code)
if xc_type not in ['LDA', 'GGA']:
msg = f"fully-translated {xc_type} on-top functionals are not defined"
raise NotImplementedError(msg)
[docs]
def get_transfnal (mol, otxc):
if otxc.upper () in OT_ALIAS:
otxc = OT_ALIAS[otxc.upper ()]
if otxc.upper ().startswith ('T'):
xc_base = otxc[1:]
fnal_class = transfnal
elif otxc.upper ().startswith ('FT'):
xc_base = otxc[2:]
_sanity_check_ftot(xc_base)
fnal_class = ftransfnal
else:
raise NotImplementedError (
'On-top pair-density functional names other than "translated" (t) or '
'"fully-translated (ft).'
)
# Try to register the functional with libxc, if not already done
xc_base = _get_registered_ot_functional (xc_base, mol)
xc_base = OT_HYB_ALIAS.get (xc_base.upper (), xc_base)
if ',' not in xc_base and \
(xc_base.upper() not in REG_OT_FUNCTIONALS) and \
(_libxc.is_hybrid_or_rsh (xc_base)):
raise NotImplementedError (
'Aliased or built-in translated hybrid or range-separated '
'functionals\nother than those listed in otfnal.OT_HYB_ALIAS. '
'Build a compound functional\nstring with a comma separating the '
'exchange and correlation parts, or use\notfnal.make_hybrid_fnal '
'instead.'
)
ks = dft.RKS (mol)
ks.xc = xc_base
return fnal_class (ks)
[docs]
class colle_salvetti_corr (otfnal):
def __init__(self, mol, **kwargs):
super().__init__(mol, **kwargs)
self.otxc = 'Colle_Salvetti'
self._numint = NumInt ()
self.grids = Grids (mol)
self._numint.hybrid_coeff = lambda * args : 0
self._numint.nlc_coeff = lambda * args : [0, 0]
self._numint.rsh_coeff = lambda * args : [0, 0, 0]
self._numint._xc_type = lambda * args : 'MGGA'
self.CS_a =_CS_a_DEFAULT
self.CS_b =_CS_b_DEFAULT
self.CS_c =_CS_c_DEFAULT
self.CS_d =_CS_d_DEFAULT
self._init_info ()
[docs]
def get_E_ot (self, rho, Pi, weights):
r''' Colle & Salvetti, Theor. Chim. Acta 37, 329 (1975)
see also Lee, Yang, Parr, Phys. Rev. B 37, 785 (1988)
[Eq. (3)]'''
a, b, c, d = self.CS_a, self.CS_b, self.CS_c, self.CS_d
rho_tot = rho[0,0] + rho[1,0]
idx = rho_tot > 1e-15
num = -c * np.power (rho_tot[idx], -1/3)
num = np.exp (num, num)
num *= Pi[4,idx]
num *= b * np.power (rho_tot[idx], -8/3)
num += 1
denom = d * np.power (rho_tot[idx], -1/3)
denom += 1
num /= denom
num *= Pi[0,idx]
num /= rho_tot[idx]
num *= weights[idx]
E_ot = np.sum (num)
E_ot *= -4 * a
return E_ot
def _hybrid_2c_coeff (ni, xc_code, spin=0):
''' Wrapper to the xc_code hybrid coefficient parser to return the
exchange and correlation components of the hybrid coefficent
separately '''
if xc_code.upper() in REG_OT_FUNCTIONALS:
hyb_x = REG_OT_FUNCTIONALS[xc_code.upper ()].get('hyb_x', 0)
hyb_c = REG_OT_FUNCTIONALS[xc_code.upper ()].get('hyb_c', 0)
return [hyb_x, hyb_c]
else:
hyb_tot = _NumInt.hybrid_coeff (ni, xc_code, spin=spin)
if hyb_tot == 0: return [0, 0]
# For exchange-only functionals, hyb_c = hyb_x
x_code, c_code = _libxc.split_x_c_comma (xc_code)
x_code = x_code + ','
c_code = ',' + c_code
# All factors of 'HF' are summed by default. Therefore just run the same
# code for the exchange and correlation parts of the string separately
hyb_x = _NumInt.hybrid_coeff(ni, x_code, spin=spin) if len (x_code) else 0
hyb_c = _NumInt.hybrid_coeff(ni, c_code, spin=spin) if len (c_code) else 0
return [hyb_x, hyb_c]
[docs]
def make_scaled_fnal (xc_code, hyb_x = 0, hyb_c = 0, fnal_x = None,
fnal_c = None):
''' Convenience function to write the xc_code corresponding to a
functional of the type
Exc = hyb_x*E_x[Psi] + fnal_x*E_x[rho] + hyb_c*E_c[Psi]
+ fnal_c*E_c[rho]
where E[Psi] is an energy from a wave function, and E[rho] is a
density functional from libxc. The decomposition of E[Psi] into
exchange (E_x) and correlation (E_c) components is arbitrary.
Args:
xc_code : string
As used in pyscf.dft.libxc. An exception is raised if it
is already a hybrid or contains a kinetic-energy
functional component.
Kwargs:
hyb_x : float
fraction of wave function exchange to be included
hyb_c : float
fraction of wave function correlation to be included
fnal_x : float
fraction of density functional exchange to be included.
Defaults to 1 - hyb_x.
fnal_c : float
fraction of density functional correlation to be
included. Defaults to 1 - hyb_c.
returns:
xc_code : string
If xc_code has exchange part x_code and correlation part
c_code, the return value is
'fnal_x * x_code + hyb_x * HF,
fnal_c * c_code + hyb_c * HF'
You STILL HAVE TO PREPEND 't' OR 'ft'!!!
'''
if fnal_x is None: fnal_x = 1 - hyb_x
if fnal_c is None: fnal_c = 1 - hyb_c
if _libxc.is_hybrid_xc (xc_code):
raise RuntimeError ('Functional {} is already a hybrid!'.format (
xc_code))
x_code, c_code = _libxc.split_x_c_comma (xc_code)
x_facs, x_terms = _libxc.parse_xc_formula (x_code)
if fnal_x != 1: x_facs = list (np.asarray (x_facs)*fnal_x)
if hyb_x != 0:
x_facs.append (hyb_x)
x_terms.append ('HF')
x_code = _libxc.assemble_xc_formula (x_facs, x_terms)
c_facs, c_terms = _libxc.parse_xc_formula (c_code)
if fnal_c != 1: c_facs = list (np.asarray (c_facs)*fnal_c)
if hyb_c != 0:
c_facs.append (hyb_c)
c_terms.append ('HF')
c_code = _libxc.assemble_xc_formula (c_facs, c_terms)
return x_code + ',' + c_code
[docs]
def make_hybrid_fnal (xc_code, hyb, hyb_type = 1):
''' Convenience function to write "hybrid" xc functional in terms of
only one parameter
Args:
xc_code : string
As used in pyscf.dft.libxc. An exception is raised if it
is already a hybrid or contains a kinetic-energy
functional component.
hyb : float
Parameter(s) defining the "hybridization" which is
handled in various ways according to hyb_type
Kwargs:
hyb_type : int or string
The type of hybrid functional. Current options are:
- 0 or 'translation': Hybrid fnal is
'hyb*HF + (1-hyb)*x_code, hyb*HF + c_code'.
Based on the idea that 'exact exchange' of the
translated functional corresponds to exchange plus
correlation energy of the underlying wave function.
Requires len (hyb) == 1.
- 1 or 'average': Hybrid fnal is
'hyb*HF + (1-hyb)*x_code, hyb*HF + (1-hyb)*c_code'.
Based on the idea that hyb = 1 recovers the wave
function energy itself. Requires len (hyb) == 1.
- 2 or 'diagram': Hybrid fnal is
'hyb*HF + (1-hyb)*x_code, c_code'.
Based on the idea that the exchange energy of the
wave function somehow can be meaningfully separated
from the correlation energy. Requires len (hyb) == 1.
- 3 or 'lambda': as in arXiv:1911.11162v1. Based on
existing 'double-hybrid' functionals. Requires
len (hyb) == 1.
- 4 or 'scaling': Hybrid fnal is
'a*HF + (1-a)*x_code, a*HF + (1-a**b)*c_code'
where a = hyb[0] and b = 1 + hyb[1]. Based on the
scaling inequalities proven by Levy and Perdew in
PRA 32, 2010 (1985):
E_c[rho_a] < a*E_c[rho] if a < 1 and
E_c[rho_a] > a*E_c[rho] if a > 1;
BUT
E_c[rho_a] ~/~ a^2 E_c[rho], implying that
E_c[rho_a] ~ a^b E_c[rho] with b > 1 unknown.
Requires len (hyb) == 2.
'''
if not hasattr (hyb, '__len__'): hyb = [hyb]
HYB_TYPE_CODE = {'translation': 0,
'average': 1,
'diagram': 2,
'lambda': 3,
'scaling': 4}
if isinstance (hyb_type, str): hyb_type = HYB_TYPE_CODE[hyb_type]
if hyb_type == 0:
assert (len (hyb) == 1)
return make_scaled_fnal (xc_code, hyb_x=hyb[0], hyb_c=hyb[0],
fnal_x=(1-hyb[0]), fnal_c=1)
elif hyb_type == 1:
assert (len (hyb) == 1)
return make_scaled_fnal (xc_code, hyb_x=hyb[0], hyb_c=hyb[0],
fnal_x=(1-hyb[0]), fnal_c=(1-hyb[0]))
elif hyb_type == 2:
assert (len (hyb) == 1)
return make_scaled_fnal (xc_code, hyb_x=hyb[0], hyb_c=0,
fnal_x=(1-hyb[0]), fnal_c=1)
elif hyb_type == 3:
assert (len (hyb) == 1)
return make_scaled_fnal (xc_code, hyb_x=hyb[0], hyb_c=hyb[0],
fnal_x=(1-hyb[0]), fnal_c=(1-(hyb[0]*hyb[0])))
elif hyb_type == 4:
assert (len (hyb) == 2)
a = hyb[0]
b = hyb[0]**(1+hyb[1])
return make_scaled_fnal (xc_code, hyb_x=a, hyb_c=a, fnal_x=(1-a),
fnal_c=(1-b))
else:
raise RuntimeError ('hybrid type undefined')
# TODO: reconsider this goofy API...
__t_doc__="For 'translated' functionals, otxc string = 't'+xc string\n"
__ft_doc__="For 'fully translated' functionals, otxc string = 'ft'+xc string\n"
[docs]
def t_hybrid_coeff(ni, xc_code, spin=0):
#return _NumInt.hybrid_coeff(ni, xc_code[1:], spin=0)
return _hybrid_2c_coeff (ni, xc_code[1:], spin=0)
t_hybrid_coeff.__doc__ = __t_doc__ + str(_NumInt.hybrid_coeff.__doc__)
[docs]
def t_nlc_coeff(ni, xc_code):
return _NumInt.nlc_coeff(ni, xc_code[1:])
t_nlc_coeff.__doc__ = __t_doc__ + str(_NumInt.nlc_coeff.__doc__)
[docs]
def t_rsh_coeff(ni, xc_code):
return _NumInt.rsh_coeff(ni, xc_code[1:])
t_rsh_coeff.__doc__ = __t_doc__ + str(_NumInt.rsh_coeff.__doc__)
[docs]
def t_eval_xc(ni, xc_code, rho, spin=0, relativity=0, deriv=1, verbose=None):
return _NumInt.eval_xc(ni, xc_code[1:], rho, spin=spin,
relativity=relativity, deriv=deriv, verbose=verbose)
t_eval_xc.__doc__ = __t_doc__ + str(_NumInt.eval_xc.__doc__)
[docs]
def t_xc_type(ni, xc_code):
return _NumInt._xc_type(ni, xc_code[1:])
t_xc_type.__doc__ = __t_doc__ + str(_NumInt._xc_type.__doc__)
[docs]
def t_rsh_and_hybrid_coeff(ni, xc_code, spin=0):
return _NumInt.rsh_and_hybrid_coeff (ni, xc_code[1:], spin=spin)
t_rsh_and_hybrid_coeff.__doc__ = (__t_doc__
+ str(_NumInt.rsh_and_hybrid_coeff.__doc__))
[docs]
def ft_hybrid_coeff(ni, xc_code, spin=0):
#return _NumInt.hybrid_coeff(ni, xc_code[2:], spin=0)
return _hybrid_2c_coeff(ni, xc_code[2:], spin=0)
ft_hybrid_coeff.__doc__ = __ft_doc__ + str(_NumInt.hybrid_coeff.__doc__)
[docs]
def ft_nlc_coeff(ni, xc_code):
return _NumInt.nlc_coeff(ni, xc_code[2:])
ft_nlc_coeff.__doc__ = __ft_doc__ + str(_NumInt.nlc_coeff.__doc__)
[docs]
def ft_rsh_coeff(ni, xc_code):
return _NumInt.rsh_coeff(ni, xc_code[2:])
ft_rsh_coeff.__doc__ = __ft_doc__ + str(_NumInt.rsh_coeff.__doc__)
[docs]
def ft_eval_xc(ni, xc_code, rho, spin=0, relativity=0, deriv=1, verbose=None):
return _NumInt.eval_xc(ni, xc_code[2:], rho, spin=spin,
relativity=relativity, deriv=deriv, verbose=verbose)
ft_eval_xc.__doc__ = __ft_doc__ + str(_NumInt.eval_xc.__doc__)
[docs]
def ft_xc_type(ni, xc_code):
return _NumInt._xc_type(ni, xc_code[2:])
ft_xc_type.__doc__ = __ft_doc__ + str(_NumInt._xc_type.__doc__)
[docs]
def ft_rsh_and_hybrid_coeff(ni, xc_code, spin=0):
return _NumInt.rsh_and_hybrid_coeff (ni, xc_code[2:], spin=spin)
ft_rsh_and_hybrid_coeff.__doc__ = (__ft_doc__
+ str(_NumInt.rsh_and_hybrid_coeff.__doc__))
def _v_err_report (otfnal, tag, lbls, rho_tot, Pi, e0, v0, v0_packed, f, e1, v1, x, w):
# Examine the error of the first and second functional derivatives in the
# debugging block under transfnal.eval_ot below
logger.debug (otfnal, '--- v_err_report (%s) ---', tag)
ndf = len (lbls)
nvP = v0[1].shape[0]
de = (e1-e0) * w
vx = ((v0[0]*x[0]).sum (0) + (v0[1]*x[1][:nvP]).sum (0)) * w
if f is None:
xfx = np.zeros_like (de)
else:
xf = tfnal_derivs.contract_fot (otfnal, f, rho_tot, Pi, x[0], x[1], vot_packed=v0_packed)
for row in xf: row[:] *= w
xfx = ((xf[0]*x[0]).sum (0) + (xf[1]*x[1][:nvP]).sum (0)) / 2
xf_df = [xf[0][0], xf[1][0]]
dv_df = [(v1[0][0]-v0[0][0])*w, (v1[1][0]-v0[1][0])*w]
# The lesson of the debug experience from the commented-out block below is:
# the largest errors (fractional or absolute) in the ftLDA fnal gradient
# appear to be for R just under 1.0!
#if 'LDA' in otfnal.otxc:
# print ("bigtab", otfnal.otxc, (np.sum (vx) - np.sum (de))/np.sum (de))
# tab = np.empty ((xf[0][0].size, 6), dtype=xf[0].dtype)
# tab[:,0] = otfnal.get_ratio (Pi, rho_tot/2)[0] - 1.0
# tab[:,1] = rho_tot
# tab[:,2] = Pi
# tab[:,3] = vx
# tab[:,4] = tab[:,3] - de
# tab[:,5] = tab[:,4] / de
# tab[(de==0)&(vx==0),3] = 0.0
# tab[(de==0)&(vx!=0),3] = 1.0
# tab = tab[np.argsort (-np.abs (tab[:,4])),:]
# for row in tab:
# print ("{:20.12e} {:9.2e} {:9.2e} {:9.2e} {:9.2e} {:9.2e}".format
# (*row))
if ndf > 2:
xf_df += [xf[0][1:4].T,]
dv_df += [((v1[0][1:4]-v0[0][1:4])*w).T,]
if ndf > 3:
xf_df += [xf[1][1:4].T,]
dv_df += [((v1[1][1:4]-v0[1][1:4])*w).T,]
de_err1 = de - vx
de_err2 = de_err1 - xfx
for ix, lbl in enumerate (lbls):
lib.logger.debug (otfnal, "%s gradient debug %s: %e - %e (- %e) -> %e "
"(%e)", tag, lbl, np.sum (de[ix::ndf]), np.sum (vx[ix::ndf]),
np.sum (xfx[ix::ndf]), np.sum (de_err1[ix::ndf]),
np.sum (de_err2[ix::ndf]))
if f is not None:
for lbl_row, xf_row, dv_row in zip (lbls, xf_df, dv_df):
err_row = dv_row-xf_row
for ix_col, lbl_col in enumerate (lbls):
lib.logger.debug (otfnal, ("%s Hessian debug (H.x_%s)_%s: "
"%e - %e -> %e"), tag, lbl_col, lbl_row,
linalg.norm (dv_row[ix_col::ndf]),
linalg.norm (xf_row[ix_col::ndf]),
linalg.norm (err_row[ix_col::ndf]))
# I am not doing rho'.rho'->sigma right for x.f.x/2
# However I am somehow doing it right for f.x vs. delta v?
lib.logger.debug (otfnal, "%s dE - v.x - x.f.x: %e - %e - %e = %e",
tag, de.sum (), vx.sum (), xfx.sum (), de_err2.sum ())