Skip to content

Commit 146bf04

Browse files
authored
Merge pull request #75 from lcmd-epfl/spahm-field
Spahm in field
2 parents e0f0ecc + ed5cc6c commit 146bf04

File tree

4 files changed

+148
-29
lines changed

4 files changed

+148
-29
lines changed

qstack/spahm/compute_spahm.py

+90-18
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,135 @@
1+
import numpy as np
12
from pyscf import scf, grad
23
from qstack.spahm.guesses import solveF, get_guess, get_occ, get_dm, eigenvalue_grad, get_guess_g
34

4-
def get_guess_orbitals(mol, guess, xc="pbe"):
5-
""" Compute the guess Hamiltonian.
5+
6+
def get_guess_orbitals(mol, guess, xc="pbe", field=None):
7+
""" Compute the guess Hamiltonian orbitals
68
79
Args:
810
mol (pyscf Mole): pyscf Mole object.
9-
guess (funct): Method used to compute the guess Hamiltonian. Output of get_guess.
11+
guess (func): Method used to compute the guess Hamiltonian. Output of get_guess.
1012
xc (str): Exchange-correlation functional. Defaults to pbe.
13+
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
1114
1215
Returns:
1316
A 1D numpy array containing the eigenvalues and a 2D numpy array containing the eigenvectors of the guess Hamiltonian.
1417
"""
1518
if guess == 'huckel':
16-
e,v = scf.hf._init_guess_huckel_orbitals(mol)
19+
if field is not None:
20+
raise NotImplementedError
21+
e, v = scf.hf._init_guess_huckel_orbitals(mol)
1722
else:
1823
fock = guess(mol, xc)
19-
e,v = solveF(mol, fock)
20-
return e,v
24+
if field is not None:
25+
with mol.with_common_orig((0,0,0)):
26+
ao_dip = mol.intor_symmetric('int1e_r', comp=3)
27+
fock += np.einsum('xij,x->ij', ao_dip, field)
28+
e, v = solveF(mol, fock)
29+
return e, v
30+
31+
32+
def ext_field_generator(mol, field):
33+
""" Generator for Hext (i.e. applied uniform electiric field interaction) gradient
34+
35+
Args:
36+
mol (pyscf Mole): pyscf Mole object.
37+
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
38+
39+
Returns:
40+
func(int: iat): returns the derivative of Hext wrt the coordinates of atom iat, i.e. dHext/dr[iat]
41+
"""
42+
43+
shls_slice = (0, mol.nbas, 0, mol.nbas)
44+
with mol.with_common_orig((0,0,0)):
45+
int1e_irp = mol.intor('int1e_irp', shls_slice=shls_slice).reshape(3, 3, mol.nao, mol.nao) # ( | rc nabla | )
46+
aoslices = mol.aoslice_by_atom()[:,2:]
47+
if field is None:
48+
field = (0,0,0)
49+
def field_deriv(iat):
50+
p0, p1 = aoslices[iat]
51+
dmu_dr = np.zeros_like(int1e_irp) # dim(mu)×dim(r)×nao×nao
52+
dmu_dr[:,:,p0:p1,:] -= int1e_irp[:,:,:,p0:p1].transpose((0,1,3,2)) # TODO not sure why minus
53+
dmu_dr[:,:,:,p0:p1] -= int1e_irp[:,:,:,p0:p1] # TODO check/fix E definition
54+
dhext_dr = np.einsum('x,xypq->ypq', field, dmu_dr)
55+
return dhext_dr
56+
return field_deriv
57+
58+
59+
def get_guess_orbitals_grad(mol, guess, field=None):
60+
""" Compute the guess Hamiltonian eigenvalues and their derivatives
61+
62+
Args:
63+
mol (pyscf Mole): pyscf Mole object.
64+
guess (func): Tuple of methods used to compute the guess Hamiltonian and its eigenvalue derivatives. Output of get_guess_g
65+
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
66+
67+
Returns:
68+
numpy 1d array (mol.nao,): eigenvalues
69+
numpy 3d ndarray (mol.nao,mol.natm,3): gradient of the eigenvalues in Eh/bohr
70+
"""
2171

22-
def get_guess_orbitals_grad(mol, guess):
23-
e, c = get_guess_orbitals(mol, guess[0])
72+
e, c = get_guess_orbitals(mol, guess[0], field=field)
2473
mf = grad.rhf.Gradients(scf.RHF(mol))
2574
s1 = mf.get_ovlp(mol)
26-
h1 = guess[1](mf)
27-
return eigenvalue_grad(mol, e, c, s1, h1)
75+
h0 = guess[1](mf)
76+
77+
if field is None:
78+
h1 = h0
79+
else:
80+
hext = ext_field_generator(mf.mol, field)
81+
h1 = lambda iat: h0(iat) + hext(iat)
2882

29-
def get_guess_dm(mol, guess, xc="pbe", openshell=None):
83+
return e, eigenvalue_grad(mol, e, c, s1, h1)
84+
85+
86+
def get_guess_dm(mol, guess, xc="pbe", openshell=None, field=None):
3087
""" Compute the density matrix with the guess Hamiltonian.
3188
3289
Args:
3390
mol (pyscf Mole): pyscf Mole object.
34-
guess (funct): Method used to compute the guess Hamiltonian. Output of get_guess.
91+
guess (func): Method used to compute the guess Hamiltonian. Output of get_guess.
3592
xc (str): Exchange-correlation functional. Defaults to pbe
3693
openshell (bool): . Defaults to None.
3794
3895
Returns:
3996
A numpy ndarray containing the density matrix computed using the guess Hamiltonian.
4097
"""
41-
e,v = get_guess_orbitals(mol, guess, xc)
98+
e,v = get_guess_orbitals(mol, guess, xc, field=field)
4299
return get_dm(v, mol.nelec, mol.spin if mol.spin>0 or not openshell is None else None)
43100

44-
def get_spahm_representation(mol, guess_in, xc="pbe"):
101+
102+
def get_spahm_representation(mol, guess_in, xc="pbe", field=None):
45103
""" Compute the SPAHM representation.
46104
47105
Args:
48106
mol (pyscf Mole): pyscf Mole object.
49107
guess_in (str): Method used to obtain the guess Hamiltoninan.
50108
xc (str): Exchange-correlation functional. Defaults to pbe.
109+
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
51110
52111
Returns:
53112
A numpy ndarray containing the SPAHM representation.
54113
"""
55114
guess = get_guess(guess_in)
56-
e, v = get_guess_orbitals(mol, guess, xc)
115+
e, v = get_guess_orbitals(mol, guess, xc, field=field)
57116
e1 = get_occ(e, mol.nelec, mol.spin)
58117
return e1
59118

60-
def get_spahm_representation_grad(mol, guess_in):
119+
120+
def get_spahm_representation_grad(mol, guess_in, field=None):
121+
""" Compute the SPAHM representation and its gradient
122+
123+
Args:
124+
mol (pyscf Mole): pyscf Mole object.
125+
guess_in (str): Method used to obtain the guess Hamiltoninan.
126+
xc (str): Exchange-correlation functional. Defaults to pbe.
127+
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
128+
129+
Returns:
130+
numpy 1d array (occ,): the SPAHM representation (Eh).
131+
numpy 3d array (occ,mol.natm,3): gradient of the representation (Eh/bohr)
132+
"""
61133
guess = get_guess_g(guess_in)
62-
agrad = get_guess_orbitals_grad(mol, guess)
63-
return get_occ(agrad, mol.nelec, mol.spin)
134+
e, agrad = get_guess_orbitals_grad(mol, guess, field=field)
135+
return get_occ(e, mol.nelec, mol.spin), get_occ(agrad, mol.nelec, mol.spin)

tests/data/H2O_dist_rot.xyz

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
3
2+
3+
O 0.187252 -0.055745 -0.112535
4+
H -0.637721 -0.524458 -0.381093
5+
H 0.450469 0.580203 0.493628

tests/test_spahm.py

+10
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@ def test_spahm_LB_ecp():
4747
assert np.allclose(R2, true_R2)
4848

4949

50+
def test_spahm_LB_field():
51+
path = os.path.dirname(os.path.realpath(__file__))
52+
mol = compound.xyz_to_mol(path+'/data/H2O.xyz', 'def2svp')
53+
R = compute_spahm.get_spahm_representation(mol, 'lb', field=(0.01,0.01,0.01))
54+
true_R = np.array([-18.26790464, -0.7890498, -0.32432933, -0.17412611, -0.10335613])
55+
assert np.allclose(R[0], R[1])
56+
assert np.allclose(true_R, R[0])
57+
58+
5059
if __name__ == '__main__':
5160
test_spahm_huckel()
5261
test_spahm_LB()
5362
test_spahm_LB_ecp()
63+
test_spahm_LB_field()

tests/test_spahm_grad.py

+43-11
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ def spahm_ev(r, mol, guess):
3939
e, c = spahm.compute_spahm.get_guess_orbitals(mymol, guess[0])
4040
return e
4141
path = os.path.dirname(os.path.realpath(__file__))
42-
mol = compound.xyz_to_mol(path+'/data/H2O_dist.xyz', 'def2svp', charge=0, spin=0)
42+
mol = compound.xyz_to_mol(path+'/data/H2O_dist_rot.xyz', 'def2svp', charge=0, spin=0)
4343
guess = spahm.guesses.get_guess_g('lb')
44-
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess)
45-
ngrad = grad_num(spahm_ev, mol, guess)
46-
for g1, g2 in zip(ngrad.T, agrad.reshape(-1, 9)):
44+
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess)[1].reshape(-1, mol.natm*3)
45+
ngrad = grad_num(spahm_ev, mol, guess).T
46+
for g1, g2 in zip(ngrad, agrad):
4747
assert(np.linalg.norm(g1-g2)<1e-6)
4848

4949

@@ -53,11 +53,11 @@ def spahm_re(r, mol, guess_in):
5353
e = spahm.compute_spahm.get_spahm_representation(mymol, guess_in)
5454
return e
5555
path = os.path.dirname(os.path.realpath(__file__))
56-
mol = compound.xyz_to_mol(path+'/data/H2O_dist.xyz', 'def2svp', charge=1, spin=1)
56+
mol = compound.xyz_to_mol(path+'/data/H2O_dist_rot.xyz', 'def2svp', charge=1, spin=1)
5757
guess = 'lb'
58-
agrad = spahm.compute_spahm.get_spahm_representation_grad(mol, guess)
59-
ngrad = grad_num(spahm_re, mol, guess)
60-
for g1, g2 in zip(ngrad.reshape(9, -1).T, agrad.reshape(-1, 9)):
58+
agrad = spahm.compute_spahm.get_spahm_representation_grad(mol, guess)[1].reshape(-1, mol.natm*3)
59+
ngrad = grad_num(spahm_re, mol, guess).reshape(mol.natm*3, -1).T
60+
for g1, g2 in zip(ngrad, agrad):
6161
assert(np.linalg.norm(g1-g2)<1e-6)
6262

6363

@@ -69,13 +69,45 @@ def spahm_ev(r, mol, guess):
6969
path = os.path.dirname(os.path.realpath(__file__))
7070
mol = compound.xyz_to_mol(path+'/data/H2Te.xyz', 'minao', charge=0, spin=0, ecp='def2-svp')
7171
guess = spahm.guesses.get_guess_g('lb')
72-
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess)
73-
ngrad = grad_num(spahm_ev, mol, guess)
74-
for g1, g2 in zip(ngrad.T, agrad.reshape(-1, 9)):
72+
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess)[1].reshape(-1, mol.natm*3)
73+
ngrad = grad_num(spahm_ev, mol, guess).T
74+
for g1, g2 in zip(ngrad, agrad):
75+
assert(np.linalg.norm(g1-g2)<1e-6)
76+
77+
78+
def test_spahm_ev_grad_field():
79+
def spahm_ev(r, mol, guess):
80+
mymol = build_mol(mol, r)
81+
e, c = spahm.compute_spahm.get_guess_orbitals(mymol, guess[0], field=field)
82+
return e
83+
path = os.path.dirname(os.path.realpath(__file__))
84+
mol = compound.xyz_to_mol(path+'/data/H2O_dist_rot.xyz', 'def2svp', charge=0, spin=0)
85+
field = np.array((0.01, 0.01, 0.01))
86+
guess = spahm.guesses.get_guess_g('lb')
87+
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess, field=field)[1].reshape(-1, mol.natm*3)
88+
ngrad = grad_num(spahm_ev, mol, guess).T
89+
for g1, g2 in zip(ngrad, agrad):
90+
assert(np.linalg.norm(g1-g2)<1e-6)
91+
92+
93+
def test_spahm_re_grad_field():
94+
def spahm_re(r, mol, guess_in):
95+
mymol = build_mol(mol, r)
96+
e = spahm.compute_spahm.get_spahm_representation(mymol, guess_in, field=field)
97+
return e
98+
path = os.path.dirname(os.path.realpath(__file__))
99+
mol = compound.xyz_to_mol(path+'/data/H2O_dist_rot.xyz', 'def2svp', charge=1, spin=1)
100+
field = np.array((0.01, 0.01, 0.01))
101+
guess = 'lb'
102+
agrad = spahm.compute_spahm.get_spahm_representation_grad(mol, guess, field=field)[1].reshape(-1, mol.natm*3)
103+
ngrad = grad_num(spahm_re, mol, guess).reshape(mol.natm*3, -1).T
104+
for g1, g2 in zip(ngrad, agrad):
75105
assert(np.linalg.norm(g1-g2)<1e-6)
76106

77107

78108
if __name__ == '__main__':
79109
test_spahm_ev_grad()
80110
test_spahm_re_grad()
81111
test_spahm_ev_grad_ecp()
112+
test_spahm_ev_grad_field()
113+
test_spahm_re_grad_field()

0 commit comments

Comments
 (0)