Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Head and wings corrections for k-dTDA #58

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d05d6b0
Cleaning up branches
mkakcl Jan 29, 2024
83e7741
Aligning with dRPA and ewald correction added.
mkakcl Feb 8, 2024
c4dcf82
Updates to Head term
mkakcl Feb 21, 2024
2b0bb39
kpt changes for rounding
mkakcl Feb 29, 2024
779a964
Changes hopefully fixing the naux issues and subsequent Hermiticity i…
mkakcl Mar 11, 2024
12929dc
Initial wings implementation.
mkakcl Mar 19, 2024
0683748
Small fix
mkakcl Mar 19, 2024
f7a844e
Head and wings cleanup
mkakcl Mar 21, 2024
3431d18
Potential MPI fix
mkakcl Apr 3, 2024
fdd0b30
More cleanup
mkakcl Apr 3, 2024
aff4dc5
A few merge issues resolved
mkakcl Apr 4, 2024
ac1ad7e
Linting
mkakcl Apr 4, 2024
c7beb4f
More linting
mkakcl Apr 4, 2024
26e873b
Even more linting
mkakcl Apr 4, 2024
ad9cdee
Fix kwargs
mkakcl Apr 4, 2024
43370f5
Linting
mkakcl Apr 4, 2024
d0acdee
einsum _contraction order correction
mkakcl Apr 5, 2024
fe62183
Additional MPI changes
mkakcl Apr 8, 2024
62fd849
Change to util einsums
mkakcl Apr 10, 2024
cdc2756
HW test for kTDA
mkakcl Apr 17, 2024
32ad7ea
Merge branch 'master' into HW_TDA_from_master
mkakcl Apr 17, 2024
f16b1d9
Update test
mkakcl Apr 17, 2024
1fe4a2c
Frozen static field fix
mkakcl Apr 18, 2024
79d8696
Changes pre discussion in PR
mkakcl May 1, 2024
f00bbfc
Linting
mkakcl May 1, 2024
cbe49f8
Further linting
mkakcl May 2, 2024
5d845b4
More linting
mkakcl May 2, 2024
e5e2b1a
Support inheritance for static SE with finite size corrections
obackhouse May 28, 2024
1e6a5bd
Fix madelung attribute
obackhouse May 28, 2024
7534d99
HWB implementation
mkakcl Sep 27, 2024
5b14ad8
Merge remote-tracking branch 'origin/HW_TDA_from_master' into HW_TDA_…
mkakcl Sep 27, 2024
e2941f7
Corrections to HWB implementation
mkakcl Sep 30, 2024
d7ebf2d
Example and tests for HWB corrections
mkakcl Sep 30, 2024
7f361ee
Small fix and updated test values
mkakcl Sep 30, 2024
b2c0fe7
Some changes related to the old PR and linting.
mkakcl Sep 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 55 additions & 7 deletions momentGW/pbc/gw.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
periodic systems.
"""

from functools import reduce

import numpy as np
from dyson import MBLSE, Lehmann, MixedMBLSE
from pyscf.pbc import tools

from momentGW import energy, logging, util
from momentGW.gw import GW
Expand Down Expand Up @@ -78,16 +81,57 @@ def build_se_static(self, integrals):

Parameters
----------
integrals : KIntegrals
integrals : Integrals
mkakcl marked this conversation as resolved.
Show resolved Hide resolved
Integrals object.

Returns
-------
se_static : numpy.ndarray
Static part of the self-energy at each k-point. If
`self.diagonal_se`, non-diagonal elements are set to zero.
Static part of the self-energy. If `self.diagonal_se`,
non-diagonal elements are set to zero.
mkakcl marked this conversation as resolved.
Show resolved Hide resolved
"""
return super().build_se_static(integrals)

if getattr(self._scf, "xc", "hf") == "hf":
se_static = np.zeros_like(self._scf.make_rdm1(mo_coeff=self.mo_coeff))
if self.fc:
with util.SilentSCF(self._scf):
vmf = self._scf.get_j() - self._scf.get_veff()
dm = self._scf.make_rdm1(mo_coeff=self.mo_coeff)
vk = integrals.get_k(dm, basis="ao")

s = self.cell.pbc_intor("int1e_ovlp", hermi=1, kpts=self.kpts)
madelung = tools.pbc.madelung(self.cell, self.kpts)
for k in range(len(self.kpts)):
vk[k] += madelung * reduce(np.dot, (s[k], dm[k], s[k]))

se_static = vmf - vk * 0.5
se_static = util.einsum(
"...pq,...pi,...qj->...ij", se_static, np.conj(self.mo_coeff), self.mo_coeff
)

else:
with util.SilentSCF(self._scf):
vmf = self._scf.get_j() - self._scf.get_veff()
dm = self._scf.make_rdm1(mo_coeff=self.mo_coeff)
vk = integrals.get_k(dm, basis="ao")

if self.fc:
s = self.cell.pbc_intor("int1e_ovlp", hermi=1, kpts=self.kpts)
madelung = tools.pbc.madelung(self.cell, self.kpts)
for k in range(len(self.kpts)):
vk[k] += madelung * reduce(np.dot, (s[k], dm[k], s[k]))
se_static = vmf - vk * 0.5

se_static = util.einsum(
"...pq,...pi,...qj->...ij", se_static, np.conj(self.mo_coeff), self.mo_coeff
)

if self.diagonal_se:
se_static = util.einsum("...pq,pq->...pq", se_static, np.eye(se_static.shape[-1]))

se_static += util.einsum("...p,...pq->...pq", self.mo_energy, np.eye(se_static.shape[-1]))

return se_static
obackhouse marked this conversation as resolved.
Show resolved Hide resolved

def build_se_moments(self, nmom_max, integrals, **kwargs):
"""Build the moments of the self-energy.
Expand All @@ -110,9 +154,13 @@ def build_se_moments(self, nmom_max, integrals, **kwargs):
Moments of the particle self-energy at each k-point. If
`self.diagonal_se`, non-diagonal elements are set to zero.
"""
if self.fc:
fc = True
else:
fc = False
mkakcl marked this conversation as resolved.
Show resolved Hide resolved

if self.polarizability.lower() == "dtda":
tda = dTDA(self, nmom_max, integrals, **kwargs)
tda = dTDA(self, nmom_max, integrals, fc, **kwargs)
mkakcl marked this conversation as resolved.
Show resolved Hide resolved
return tda.kernel()
if self.polarizability.lower() == "drpa":
rpa = dRPA(self, nmom_max, integrals, **kwargs)
Expand Down Expand Up @@ -334,7 +382,7 @@ def make_rdm1(self, gf=None):

@logging.with_timer("Energy")
@logging.with_status("Calculating energy")
def energy_hf(self, gf=None, integrals=None):
def energy_hf(self, gf=None, integrals=None, **kwargs):
"""Calculate the one-body (Hartree--Fock) energy.

Parameters
Expand Down Expand Up @@ -367,7 +415,7 @@ def energy_hf(self, gf=None, integrals=None):
"kpq,kpi,kqj->kij", self._scf.get_hcore(), self.mo_coeff.conj(), self.mo_coeff
)
rdm1 = self.make_rdm1()
fock = integrals.get_fock(rdm1, h1e)
fock = integrals.get_fock(rdm1, h1e, **kwargs)

# Calculate the Hartree--Fock energy at each k-point
e_1b = sum(energy.hartree_fock(rdm1[k], fock[k], h1e[k]) for k in self.kpts.loop(1))
Expand Down
10 changes: 8 additions & 2 deletions momentGW/pbc/ints.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ def get_cderi_from_thc(self):
Lpx[ki, kj] = Lpx_k
Lia[ki, kj] = Lia_k

q = self.kpts.member(self.kpts.wrap_around(-self.kpts[q]))
invq = self.kpts.member(self.kpts.wrap_around(-self.kpts[q]))

block_switch = util.einsum("Pp,Pq,PQ->Qpq", coll_kj.conj(), coll_ki, cholesky_cou)

Expand All @@ -408,7 +408,7 @@ def get_cderi_from_thc(self):
)
tmp = util.einsum("Lpq,pi,qj->Lij", block_switch, coeffs[0].conj(), coeffs[1])
tmp = tmp.swapaxes(1, 2)
tmp = tmp.reshape(self.naux[q], -1)
tmp = tmp.reshape(self.naux[invq], -1)
Lai_k += tmp

Lai[ki, kj] = Lai_k
Expand Down Expand Up @@ -727,6 +727,12 @@ def get_fock(self, dm, h1e, **kwargs):
"""
return super().get_fock(dm, h1e, **kwargs)

def reciprocal_lattice(self):
"""
Return the reciprocal lattice vectors.
"""
return self.with_df.cell.reciprocal_vectors()

@property
def madelung(self):
"""
Expand Down
118 changes: 116 additions & 2 deletions momentGW/pbc/tda.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import numpy as np
import scipy.special
from pyscf import lib
from pyscf.pbc import dft

from momentGW import logging, mpi_helper, util
from momentGW.tda import dTDA as MoldTDA
Expand Down Expand Up @@ -34,6 +36,22 @@ class dTDA(MoldTDA):
Default value is `None`.
"""

def __init__(
self,
gw,
nmom_max,
integrals,
fc=False,
mkakcl marked this conversation as resolved.
Show resolved Hide resolved
mo_energy=None,
mo_occ=None,
):
super().__init__(gw, nmom_max, integrals, mo_energy=mo_energy, mo_occ=mo_occ)
self.fc = fc
if self.fc:
q = np.array([1e-3, 0, 0]).reshape(1, 3)
self.q_abs = self.kpts.cell.get_abs_kpts(q)
self.qij = self.build_pert_term(self.q_abs[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q_abs is O(1) to compute right? If so, can we make it a property instead.

I'd prefer not storing qij in the class, but if it's needed in several places then maybe it's for the best. Maybe a more descriptive variable name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the variable name, still not great but not sure what makes more sense.


@logging.with_timer("Density-density moments")
@logging.with_status("Constructing density-density moments")
def build_dd_moments(self):
Expand All @@ -49,12 +67,20 @@ def build_dd_moments(self):
kpts = self.kpts
moments = np.zeros((self.nkpts, self.nkpts, self.nmom_max + 1), dtype=object)

if self.fc:
head = np.zeros((self.nkpts, self.nmom_max + 1), dtype=object)

# Get the zeroth order moment
for q in kpts.loop(1):
for kj in kpts.loop(1, mpi=True):
kb = kpts.member(kpts.wrap_around(kpts[q] + kpts[kj]))
moments[q, kb, 0] += self.integrals.Lia[kj, kb] / self.nkpts

if q == 0 and self.fc:
head[kj, 0] += (
np.sqrt(4.0 * np.pi) / np.linalg.norm(self.q_abs[0])
) * self.qij[kj].conj()

# Get the higher order moments
for i in range(1, self.nmom_max + 1):
for q in kpts.loop(1):
Expand All @@ -66,23 +92,42 @@ def build_dd_moments(self):
(self.mo_occ_w[kj], self.mo_occ_w[kb]),
)
moments[q, kb, i] += moments[q, kb, i - 1] * d.ravel()[None]
if self.fc and q == 0:
head[kj, i] += head[kj, i - 1] * d.ravel()

tmp = np.zeros((self.naux[q], self.naux[q]), dtype=complex)
tmp_head = np.zeros((self.naux[q]), dtype=complex)

for ki in kpts.loop(1, mpi=True):
ka = kpts.member(kpts.wrap_around(kpts[q] + kpts[ki]))

tmp += np.dot(moments[q, ka, i - 1], self.integrals.Lia[ki, ka].T.conj())

if q == 0 and self.fc:
tmp_head += util.einsum(
"a,aP->P", head[ki, i - 1], self.integrals.Lia[ki, ki].T.conj()
)

tmp = mpi_helper.allreduce(tmp)
tmp *= 2.0
tmp /= self.nkpts

tmp_head = mpi_helper.allreduce(tmp_head)
tmp_head *= 2.0
tmp_head /= self.nkpts

for kj in kpts.loop(1, mpi=True):
kb = kpts.member(kpts.wrap_around(kpts[q] + kpts[kj]))

moments[q, kb, i] += np.dot(tmp, self.integrals.Lai[kj, kb].conj())

return moments
if q == 0 and self.fc:
head[kj, i] += util.einsum("P,Pa->a", tmp_head, self.integrals.Lia[kj, kj])

if self.fc:
return {"moments": moments, "head": head}
else:
return moments
mkakcl marked this conversation as resolved.
Show resolved Hide resolved

def kernel(self, exact=False):
"""
Expand Down Expand Up @@ -220,13 +265,33 @@ def build_se_moments(self, moments_dd):
eta_shape = lambda k: (self.mo_energy_g[k].size, self.nmom_max + 1, self.nmo, self.nmo)
eta = np.zeros((self.nkpts, self.nkpts), dtype=object)

if self.fc:
cell_vol = self.kpts.cell.vol
total_vol = cell_vol * self.nkpts
q0 = (6 * np.pi**2 / total_vol) ** (1 / 3)
norm_q_abs = np.linalg.norm(self.q_abs[0])
eta_head = np.zeros((self.nkpts, self.nmom_max + 1), dtype=object)
eta_wings = np.zeros((self.nkpts, self.nmom_max + 1), dtype=object)

# Get the moments in (aux|aux) and rotate to (mo|mo)
for n in range(self.nmom_max + 1):
for q in kpts.loop(1):
eta_aux = 0
for kj in kpts.loop(1, mpi=True):
kb = kpts.member(kpts.wrap_around(kpts[q] + kpts[kj]))
eta_aux += np.dot(moments_dd[q, kb, n], self.integrals.Lia[kj, kb].T.conj())
if self.fc:
eta_aux += np.dot(
moments_dd["moments"][q, kb, n], self.integrals.Lia[kj, kb].T.conj()
)
if q == 0:
eta_head[kb, n] += -(np.sqrt(4.0 * np.pi) / norm_q_abs) * np.sum(
moments_dd["head"][kb, n] * self.qij[kb]
)
eta_wings[kb, n] += (np.sqrt(4.0 * np.pi) / norm_q_abs) * util.einsum(
"Pa,a->P", moments_dd["moments"][q, kb, n], self.qij[kb]
)
else:
eta_aux += np.dot(moments_dd[q, kb, n], self.integrals.Lia[kj, kb].T.conj())

eta_aux = mpi_helper.allreduce(eta_aux)
eta_aux *= 2.0
Expand All @@ -242,12 +307,61 @@ def build_se_moments(self, moments_dd):
Lp = self.integrals.Lpx[kp, kx][:, :, x]
subscript = f"P{pchar},Q{qchar},PQ->{pqchar}"
eta[kp, q][x, n] += util.einsum(subscript, Lp, Lp.conj(), eta_aux)
if self.fc and q == 0:
original = eta[kp, q][x, n]

eta[kp, q][x, n] += (2 / np.pi) * (q0) * eta_head[kp, n] * original

wing_tmp = util.einsum("Pp,P->p", Lp, eta_wings[kp, n])
wing_tmp = wing_tmp.real * 2
wing_tmp *= -(np.sqrt(cell_vol / (4 * (np.pi**3))) * q0**2)

eta[kp, q][x, n] += util.einsum("p,pq->pq", wing_tmp, original)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as the moments_dd. Is it possible to have this function be unchanged, but before we do the convolution, there is something like:

if self.fc:
    eta = self._add_eta_fc_correction(moments_dd)

# Construct the self-energy moments
moments_occ, moments_vir = self.convolve(eta)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have been able to do this and split moments_dd as well.


# Construct the self-energy moments
moments_occ, moments_vir = self.convolve(eta)

return moments_occ, moments_vir

def build_pert_term(self, qpt):
"""
Compute 1/sqrt(Ω) * ⟨Ψ_{ik}|e^{iqr}|Ψ_{ak-q}⟩ at q-point index
q using perturbation theory.
mkakcl marked this conversation as resolved.
Show resolved Hide resolved
"""

coords, weights = dft.gen_grid.get_becke_grids(self.gw.cell, level=5)

qij = np.zeros((self.nkpts,), dtype=object)
for k in self.kpts.loop(1):
ao_p = dft.numint.eval_ao(self.gw.cell, coords, kpt=self.kpts[k], deriv=1)
ao, ao_grad = ao_p[0], ao_p[1:4]

ao_ao_grad = util.einsum("g,gm,xgn->xmn", weights, ao.conj(), ao_grad)
q_ao_ao_grad = util.einsum("x,xmn->mn", qpt, ao_ao_grad) * -1.0j
q_mo_mo_grad = util.einsum(
"mn,mi,na->ia",
q_ao_ao_grad,
self.integrals.mo_coeff_w[k][:, self.mo_occ_w[k] > 0].conj(),
self.integrals.mo_coeff_w[k][:, self.mo_occ_w[k] == 0],
)

d = lib.direct_sum(
"a-i->ia",
self.mo_energy_w[k][self.mo_occ_w[k] == 0],
self.mo_energy_w[k][self.mo_occ_w[k] > 0],
)
mkakcl marked this conversation as resolved.
Show resolved Hide resolved
dens = q_mo_mo_grad / d
qij[k] = dens.flatten() / np.sqrt(self.gw.cell.vol)

return qij

build_dd_moments_exact = build_dd_moments
mkakcl marked this conversation as resolved.
Show resolved Hide resolved

@property
def naux(self):
"""Number of auxiliaries."""
return self.integrals.naux

@property
def nov(self):
"""Get the number of ov states in W."""
Expand Down
7 changes: 6 additions & 1 deletion momentGW/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,12 @@ def _fallback():

# Reshape and transpose the output
ct = ct.reshape(shape_ct, order="A")
c = ct.transpose(order_ct)
if ct.flags.f_contiguous:
c = np.asfortranarray(ct.transpose(order_ct))
elif ct.flags.c_contiguous:
c = np.ascontiguousarray(ct.transpose(order_ct))
else:
c = ct.transpose(order_ct)
Comment on lines +459 to +464
Copy link
Contributor

@obackhouse obackhouse Apr 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain these changes? does it just intend the output to be contiguous?

my original code allows ct to be in any contiguous order, and then doesn't therein constrain the contiguity of c. Sometimes a transpose can just be equivalent to reading the array with the opposite contiguity, if the transpose indices are $(N-1, N-2, ..., 0)$. This means that in the worst case the new code will require an entire extra copy of c that isn't actually needed.

if we want to constrain the contiguity of the output, I think it would be better to just do

ct = ct.reshape(shape_ct, order="A")
c = ct.transpose(order_ct)
c = np.asarray(c, order="A")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention is to try to maintain the contiguous nature of ct, particularly given that you can pass a and b as say c contiguous and then have an output that is not. This is particularly problematic with the ao2mo methods which check the contiguity. Happy though to implement it as mentioned in the comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't quite work as the contiguity has already been changed with the transpose, so order="A" just keeps it as F contiguous

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine -- the output doesn't even need to be contiguous really. That being said, I can't think of a situation when it won't be, since it's an output from either C or F implementation of DGEMM. at and bt are either, and the output is fine being either as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't seem to see it in another PR, but just want to check this isn't change in another PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've not changed it, just revert this on this PR


return c

Expand Down
Loading