Source code for besmarts.mechanics.hessians
"""
besmarts.mechanics.hessians
"""
import copy
import numpy as np
import pprint
from besmarts.core import assignments
from besmarts.core import graphs
from besmarts.core import geometry
from besmarts.core import configs
from besmarts.mechanics import molecular_models as mm
from besmarts.mechanics import vibration
au2kcal = 627.51/(0.529177**2)
PRECISION = configs.precision
def transform(B):
"""
full inv G
"""
B = np.atleast_2d(np.asarray(B))
G = np.dot(B, B.T)
G = G.round(PRECISION)
u, v = np.linalg.eigh(G)
u = u.round(PRECISION)
v = v.round(PRECISION)
ginv = np.zeros_like(G)
for ui, vi in zip(u[::-1], v.T[::-1]):
if ui > 1e-11:
# w.append(vi)
# ginv += np.outer(vi/ui,vi)
vv = np.outer(vi/ui, vi)
vv = vv.round(PRECISION)
# print(f"VV is u={ui}", vv.min(), vv.mean(), vv.max())
ginv += vv
ginv = ginv.round(PRECISION)
# print("GINV is", ginv.min(), ginv.mean(), ginv.max())
# w = np.array(w).T
# print("G inverse")
# print(ginv)
GB = np.dot(ginv, B) # MxN
GB = GB.round(PRECISION)
# print("GB is", GB.min(), GB.mean(), GB.max())
return GB
[docs]
def transform(B):
"""
full inv G
"""
B = np.atleast_2d(np.asarray(B))
G = np.atleast_2d(np.dot(B, B.T))
G = G.round(PRECISION)
u, v = np.linalg.eigh(G)
u = u.round(PRECISION)
v = v.round(PRECISION)
# print("G eigenvals", u)
# w = []
c = 0.99
s = 0
N = sum(u)
ginv = np.zeros_like(G)
for ui, vi in zip(u[::-1], v.T[::-1]):
s += ui
if s/N < c:
# w.append(vi)
# ginv += np.outer(vi/ui,vi)
vv = np.outer(vi/ui, vi)
vv = vv.round(PRECISION)
# print(f"VV is u={ui}", vv.min(), vv.mean(), vv.max())
ginv += vv
ginv = ginv.round(PRECISION)
# print("GINV is", ginv.min(), ginv.mean(), ginv.max())
# w = np.array(w).T
# print("G inverse")
# print(ginv)
GB = np.dot(ginv, B) # MxN
GB = GB.round(PRECISION)
# print("GB is", GB.min(), GB.mean(), GB.max())
return GB
[docs]
def transform_v2(B):
"""
just use diag of inv G
"""
G = np.dot(B, B.T)
G = G.round(PRECISION)
u, v = np.linalg.eigh(G)
u = u.round(PRECISION)
v = v.round(PRECISION)
# print("G eigenvals", u)
# w = []
ginv = np.zeros_like(G)
for ui, vi in zip(u[::-1], v.T[::-1]):
if ui > 1e-10:
# w.append(vi)
# ginv += np.outer(vi/ui,vi)
vv = np.diag(vi*vi / ui)
vv = vv.round(PRECISION)
# print(f"VV is u={ui}", vv.min(), vv.mean(), vv.max())
ginv += vv
ginv = ginv.round(PRECISION)
# print("GINV is", ginv.min(), ginv.mean(), ginv.max())
# w = np.array(w).T
# print("G inverse")
# print(ginv)
GB = np.dot(ginv, B) # MxN
GB = GB.round(PRECISION)
# print("GB is", GB.min(), GB.mean(), GB.max())
return GB
[docs]
def transform_v3(B):
"""
just use diag of G
"""
G = np.dot(B, B.T)
G = np.diag(np.diag(G))
G = G.round(PRECISION)
u, v = np.linalg.eigh(G)
u = u.round(PRECISION)
v = v.round(PRECISION)
# print("G eigenvals", u)
# w = []
ginv = np.zeros_like(G)
for ui, vi in zip(u[::-1], v.T[::-1]):
if ui > 1e-10:
# w.append(vi)
# ginv += np.outer(vi/ui,vi)
vv = np.diag(vi*vi / ui)
vv = vv.round(PRECISION)
# print(f"VV is u={ui}", vv.min(), vv.mean(), vv.max())
ginv += vv
ginv = ginv.round(PRECISION)
# print("GINV is", ginv.min(), ginv.mean(), ginv.max())
# w = np.array(w).T
# print("G inverse")
# print(ginv)
GB = np.dot(ginv, B) # MxN
GB = GB.round(PRECISION)
# print("GB is", GB.min(), GB.mean(), GB.max())
return GB
[docs]
def project_ics(B, H):
H = np.array(H, dtype=float)
H = H.round(PRECISION)
GB = transform(B)
Q = np.dot(np.dot(GB, H), GB.T)
Q = Q.round(PRECISION)
return Q
[docs]
def project_gradient(B, gx, shm=None):
gx = np.array(gx, dtype=float)
gx = gx.round(PRECISION)
GB = transform(B)
gq = np.dot(GB, gx)
gq = gq.round(PRECISION)
return gq
[docs]
def subtract_projection(Bnb, H_qm_x, H_mm_x):
if not len(Bnb):
return H_qm_x
H_mm_nb = project_ics(Bnb, H_mm_x)
H_qm_x = np.array(H_qm_x, dtype=float)
H_qm_x = H_qm_x.round(PRECISION)
H_mm_x = np.dot(Bnb.T, np.dot(H_mm_nb, Bnb))
H_mm_x = np.round(H_mm_x, PRECISION)
H_qm_nb = project_ics(Bnb, H_qm_x)
H_qm_nb = H_qm_nb.round(PRECISION)
H_qm_nbx = np.dot(Bnb.T, np.dot(H_qm_nb, Bnb))
H_qm_nbx = np.round(H_qm_nbx, PRECISION)
H = H_qm_x
return H
[docs]
def remove_tr(H):
"""
be lazy and just remove the first 6
"""
u, v = np.linalg.eigh(H)
u = np.atleast_2d(u[6:])
v = v[:, 6:]
return np.dot(v*u, v.T)
[docs]
def project_onto_ics_from_data(psys, fc_map):
ic_msm_fcs = {}
d = fc_map
for ic, k in d["bonds"].items():
bond = geometry.bond(tuple(int(x) for x in ic.split()))
if k < 0:
k = 0.0
elif k > 4000:
k = 4000
ic_msm_fcs[bond] = k
for ic, k in d["angles"].items():
if k < 0:
k = 0.0
elif k > 4000:
k = 4000
angle = geometry.angle(tuple(int(x) for x in ic.split()))
ic_msm_fcs[angle] = k
pos = psys.models[0].positions[0]
return assignments.graph_assignment(pos.smiles, ic_msm_fcs, pos.graph)
[docs]
def hessian_project_onto_ics(
csys,
psys: mm.physical_model,
hess_qm,
verbose=False,
B=None,
shm=None
) -> dict:
pos = psys.models[0].positions
xyz = np.vstack([x[0] for posi in pos for x in posi.selections.values()], dtype=float)
xyz = xyz.round(PRECISION)
sym = [s for posi in pos for s in graphs.graph_symbols(posi.graph).values()]
mass = np.array([[vibration.mass_table[s]]*3 for s in sym])
# sym = list(sym.values())
remove1_3 = True
torsions = True
outofplanes = True
pairs = False
hess_qm_freq, hess_qm_modes = vibration.hessian_modes(
hess_qm,
sym,
xyz,
mass,
0,
remove=0,
stdtr=True,
verbose=False
)
omega = np.round(hess_qm_freq, PRECISION)
if verbose:
print("Ref Hessian Frequencies (cm-1):")
print(omega)
if B is None:
ics, B = assignments.bmatrix(
pos,
torsions=torsions,
outofplanes=outofplanes,
pairs=pairs,
remove1_3=remove1_3
)
else:
ics, B = B
B = np.array(B)
ic_qm_fcs = {}
if len(B.shape) > 1 and B.shape[0] > 0 and B.shape[1] > 0:
hess_qm_ic = project_ics(B, hess_qm)
hess_qm_ic = np.array(np.diag(hess_qm_ic))
if (hess_qm_ic < 0).any() and verbose:
print("Warning, negative force constants found")
if (hess_qm_ic > 4000).any() and verbose:
print("Warning, large force constants found")
hess_qm_ic[hess_qm_ic < 0] = 0.0
hess_qm_ic[hess_qm_ic > 4000] = 4000
ic_qm_fcs = dict(zip(ics, hess_qm_ic))
if verbose:
print("Projected MM Fcs")
pprint.pprint(ic_qm_fcs, sort_dicts=False)
return ic_qm_fcs
# return assignments.graph_assignment(pos.smiles, ic_qm_fcs, pos.graph)
[docs]
def hessian_transform(mass, hess_mm, grad_mm, DL, ics, B, B2):
# sym = graphs.graph_symbols(g)
# mass = np.array([[vibration.mass_table[sym[n]]]*3 for n in sym])
mass = np.array(mass)
hgx = np.zeros_like(hess_mm)
# needed if we do analytic Hessian (TODO)
# ic_grad = dict(zip(ics, project_gradient(B, grad_mm)))
# plug in atom index to get matrix index
# id_map = {v: k for k, v in enumerate(g.nodes)}
# for ic, b2 in zip(ics, B2):
# for ai, a in enumerate(ic):
# a = id_map[a]
# for bi, b in enumerate(ic):
# b = id_map[b]
# for i in range(3):
# for j in range(3):
# b2ab = b2[0][3*ai + i][3*bi + j]
# hgx[3*a + i][3*b + j] += ic_grad[ic] * b2ab
hess_mm_au = vibration.hessian_transform_mass_weighted(hess_mm - hgx, mass)
hess_mm_freq = np.dot(np.dot(DL.T, hess_mm_au), DL)
hess_mm_freq = vibration.converteig(hess_mm_freq)
hess_mm_freq = np.round(hess_mm_freq, PRECISION)
return hess_mm_freq
[docs]
def hessian_frequencies(mass, hess_mm, grad_mm, DL, ics, B, B2):
return np.diag(hessian_transform(mass, hess_mm, grad_mm, DL, ics, B, B2))