Skip to content

Commit

Permalink
Merge branch 'master' into small-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
YAY-C committed Dec 20, 2024
2 parents e1c9cca + 146bf04 commit 8e9ee0e
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 30 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package-conda-cache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
- name: Set cache date
run: echo "DATE=$(date +'%Y%m%d')" >> $GITHUB_ENV

- uses: actions/cache@v2
- uses: actions/cache@v4
with:
path: ${{ matrix.prefix }}
key: ${{ matrix.label }}-conda-${{ hashFiles('environment.yml') }}-${{ env.DATE }}-${{ env.CACHE_NUMBER }}
Expand Down
108 changes: 90 additions & 18 deletions qstack/spahm/compute_spahm.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,135 @@
import numpy as np
from pyscf import scf, grad
from qstack.spahm.guesses import solveF, get_guess, get_occ, get_dm, eigenvalue_grad, get_guess_g

def get_guess_orbitals(mol, guess, xc="pbe"):
""" Compute the guess Hamiltonian.

def get_guess_orbitals(mol, guess, xc="pbe", field=None):
""" Compute the guess Hamiltonian orbitals
Args:
mol (pyscf Mole): pyscf Mole object.
guess (funct): Method used to compute the guess Hamiltonian. Output of get_guess.
guess (func): Method used to compute the guess Hamiltonian. Output of get_guess.
xc (str): Exchange-correlation functional. Defaults to pbe.
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
Returns:
A 1D numpy array containing the eigenvalues and a 2D numpy array containing the eigenvectors of the guess Hamiltonian.
"""
if guess == 'huckel':
e,v = scf.hf._init_guess_huckel_orbitals(mol)
if field is not None:
raise NotImplementedError
e, v = scf.hf._init_guess_huckel_orbitals(mol)
else:
fock = guess(mol, xc)
e,v = solveF(mol, fock)
return e,v
if field is not None:
with mol.with_common_orig((0,0,0)):
ao_dip = mol.intor_symmetric('int1e_r', comp=3)
fock += np.einsum('xij,x->ij', ao_dip, field)
e, v = solveF(mol, fock)
return e, v


def ext_field_generator(mol, field):
""" Generator for Hext (i.e. applied uniform electiric field interaction) gradient
Args:
mol (pyscf Mole): pyscf Mole object.
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
Returns:
func(int: iat): returns the derivative of Hext wrt the coordinates of atom iat, i.e. dHext/dr[iat]
"""

shls_slice = (0, mol.nbas, 0, mol.nbas)
with mol.with_common_orig((0,0,0)):
int1e_irp = mol.intor('int1e_irp', shls_slice=shls_slice).reshape(3, 3, mol.nao, mol.nao) # ( | rc nabla | )
aoslices = mol.aoslice_by_atom()[:,2:]
if field is None:
field = (0,0,0)
def field_deriv(iat):
p0, p1 = aoslices[iat]
dmu_dr = np.zeros_like(int1e_irp) # dim(mu)×dim(r)×nao×nao
dmu_dr[:,:,p0:p1,:] -= int1e_irp[:,:,:,p0:p1].transpose((0,1,3,2)) # TODO not sure why minus
dmu_dr[:,:,:,p0:p1] -= int1e_irp[:,:,:,p0:p1] # TODO check/fix E definition
dhext_dr = np.einsum('x,xypq->ypq', field, dmu_dr)
return dhext_dr
return field_deriv


def get_guess_orbitals_grad(mol, guess, field=None):
""" Compute the guess Hamiltonian eigenvalues and their derivatives
Args:
mol (pyscf Mole): pyscf Mole object.
guess (func): Tuple of methods used to compute the guess Hamiltonian and its eigenvalue derivatives. Output of get_guess_g
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
Returns:
numpy 1d array (mol.nao,): eigenvalues
numpy 3d ndarray (mol.nao,mol.natm,3): gradient of the eigenvalues in Eh/bohr
"""

def get_guess_orbitals_grad(mol, guess):
e, c = get_guess_orbitals(mol, guess[0])
e, c = get_guess_orbitals(mol, guess[0], field=field)
mf = grad.rhf.Gradients(scf.RHF(mol))
s1 = mf.get_ovlp(mol)
h1 = guess[1](mf)
return eigenvalue_grad(mol, e, c, s1, h1)
h0 = guess[1](mf)

if field is None:
h1 = h0
else:
hext = ext_field_generator(mf.mol, field)
h1 = lambda iat: h0(iat) + hext(iat)

def get_guess_dm(mol, guess, xc="pbe", openshell=None):
return e, eigenvalue_grad(mol, e, c, s1, h1)


def get_guess_dm(mol, guess, xc="pbe", openshell=None, field=None):
""" Compute the density matrix with the guess Hamiltonian.
Args:
mol (pyscf Mole): pyscf Mole object.
guess (funct): Method used to compute the guess Hamiltonian. Output of get_guess.
guess (func): Method used to compute the guess Hamiltonian. Output of get_guess.
xc (str): Exchange-correlation functional. Defaults to pbe
openshell (bool): . Defaults to None.
Returns:
A numpy ndarray containing the density matrix computed using the guess Hamiltonian.
"""
e,v = get_guess_orbitals(mol, guess, xc)
e,v = get_guess_orbitals(mol, guess, xc, field=field)
return get_dm(v, mol.nelec, mol.spin if mol.spin>0 or not openshell is None else None)

def get_spahm_representation(mol, guess_in, xc="pbe"):

def get_spahm_representation(mol, guess_in, xc="pbe", field=None):
""" Compute the SPAHM representation.
Args:
mol (pyscf Mole): pyscf Mole object.
guess_in (str): Method used to obtain the guess Hamiltoninan.
xc (str): Exchange-correlation functional. Defaults to pbe.
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
Returns:
A numpy ndarray containing the SPAHM representation.
"""
guess = get_guess(guess_in)
e, v = get_guess_orbitals(mol, guess, xc)
e, v = get_guess_orbitals(mol, guess, xc, field=field)
e1 = get_occ(e, mol.nelec, mol.spin)
return e1

def get_spahm_representation_grad(mol, guess_in):

def get_spahm_representation_grad(mol, guess_in, field=None):
""" Compute the SPAHM representation and its gradient
Args:
mol (pyscf Mole): pyscf Mole object.
guess_in (str): Method used to obtain the guess Hamiltoninan.
xc (str): Exchange-correlation functional. Defaults to pbe.
field (numpy.array(3)): applied uniform electric field i.e. $\\vec \\nabla \\phi$ in a.u.
Returns:
numpy 1d array (occ,): the SPAHM representation (Eh).
numpy 3d array (occ,mol.natm,3): gradient of the representation (Eh/bohr)
"""
guess = get_guess_g(guess_in)
agrad = get_guess_orbitals_grad(mol, guess)
return get_occ(agrad, mol.nelec, mol.spin)
e, agrad = get_guess_orbitals_grad(mol, guess, field=field)
return get_occ(e, mol.nelec, mol.spin), get_occ(agrad, mol.nelec, mol.spin)
5 changes: 5 additions & 0 deletions tests/data/H2O_dist_rot.xyz
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
3

O 0.187252 -0.055745 -0.112535
H -0.637721 -0.524458 -0.381093
H 0.450469 0.580203 0.493628
10 changes: 10 additions & 0 deletions tests/test_spahm.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,17 @@ def test_spahm_LB_ecp():
assert np.allclose(R2, true_R2)


def test_spahm_LB_field():
path = os.path.dirname(os.path.realpath(__file__))
mol = compound.xyz_to_mol(path+'/data/H2O.xyz', 'def2svp')
R = compute_spahm.get_spahm_representation(mol, 'lb', field=(0.01,0.01,0.01))
true_R = np.array([-18.26790464, -0.7890498, -0.32432933, -0.17412611, -0.10335613])
assert np.allclose(R[0], R[1])
assert np.allclose(true_R, R[0])


if __name__ == '__main__':
test_spahm_huckel()
test_spahm_LB()
test_spahm_LB_ecp()
test_spahm_LB_field()
54 changes: 43 additions & 11 deletions tests/test_spahm_grad.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ def spahm_ev(r, mol, guess):
e, c = spahm.compute_spahm.get_guess_orbitals(mymol, guess[0])
return e
path = os.path.dirname(os.path.realpath(__file__))
mol = compound.xyz_to_mol(path+'/data/H2O_dist.xyz', 'def2svp', charge=0, spin=0)
mol = compound.xyz_to_mol(path+'/data/H2O_dist_rot.xyz', 'def2svp', charge=0, spin=0)
guess = spahm.guesses.get_guess_g('lb')
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess)
ngrad = grad_num(spahm_ev, mol, guess)
for g1, g2 in zip(ngrad.T, agrad.reshape(-1, 9)):
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess)[1].reshape(-1, mol.natm*3)
ngrad = grad_num(spahm_ev, mol, guess).T
for g1, g2 in zip(ngrad, agrad):
assert(np.linalg.norm(g1-g2)<1e-6)


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


Expand All @@ -69,13 +69,45 @@ def spahm_ev(r, mol, guess):
path = os.path.dirname(os.path.realpath(__file__))
mol = compound.xyz_to_mol(path+'/data/H2Te.xyz', 'minao', charge=0, spin=0, ecp='def2-svp')
guess = spahm.guesses.get_guess_g('lb')
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess)
ngrad = grad_num(spahm_ev, mol, guess)
for g1, g2 in zip(ngrad.T, agrad.reshape(-1, 9)):
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess)[1].reshape(-1, mol.natm*3)
ngrad = grad_num(spahm_ev, mol, guess).T
for g1, g2 in zip(ngrad, agrad):
assert(np.linalg.norm(g1-g2)<1e-6)


def test_spahm_ev_grad_field():
def spahm_ev(r, mol, guess):
mymol = build_mol(mol, r)
e, c = spahm.compute_spahm.get_guess_orbitals(mymol, guess[0], field=field)
return e
path = os.path.dirname(os.path.realpath(__file__))
mol = compound.xyz_to_mol(path+'/data/H2O_dist_rot.xyz', 'def2svp', charge=0, spin=0)
field = np.array((0.01, 0.01, 0.01))
guess = spahm.guesses.get_guess_g('lb')
agrad = spahm.compute_spahm.get_guess_orbitals_grad(mol, guess, field=field)[1].reshape(-1, mol.natm*3)
ngrad = grad_num(spahm_ev, mol, guess).T
for g1, g2 in zip(ngrad, agrad):
assert(np.linalg.norm(g1-g2)<1e-6)


def test_spahm_re_grad_field():
def spahm_re(r, mol, guess_in):
mymol = build_mol(mol, r)
e = spahm.compute_spahm.get_spahm_representation(mymol, guess_in, field=field)
return e
path = os.path.dirname(os.path.realpath(__file__))
mol = compound.xyz_to_mol(path+'/data/H2O_dist_rot.xyz', 'def2svp', charge=1, spin=1)
field = np.array((0.01, 0.01, 0.01))
guess = 'lb'
agrad = spahm.compute_spahm.get_spahm_representation_grad(mol, guess, field=field)[1].reshape(-1, mol.natm*3)
ngrad = grad_num(spahm_re, mol, guess).reshape(mol.natm*3, -1).T
for g1, g2 in zip(ngrad, agrad):
assert(np.linalg.norm(g1-g2)<1e-6)


if __name__ == '__main__':
test_spahm_ev_grad()
test_spahm_re_grad()
test_spahm_ev_grad_ecp()
test_spahm_ev_grad_field()
test_spahm_re_grad_field()

0 comments on commit 8e9ee0e

Please sign in to comment.