-
Notifications
You must be signed in to change notification settings - Fork 21
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
Add MSDFT method #77
base: master
Are you sure you want to change the base?
Add MSDFT method #77
Conversation
det_ovlp.append(phase * np.prod(s_a)*np.prod(s_b)) | ||
|
||
# One-particle asymmetric density matrix. See also pyscf.scf.uhf.make_asym_dm | ||
dm_01a = c1_a.dot(x_a).dot(c2_a.conj().T) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@baopengbp This density matrix is very singular, leading to huge J,K matrices. These quantities are numerically super sensitive to the underlying SCF results. Does this treatment make sense? Should these coupling cases be screened, when singular s_a
and s_b
are found?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@baopengbp This density matrix is very singular, leading to huge J,K matrices. These quantities are numerically super sensitive to the underlying SCF results. Does this treatment make sense? Should these coupling cases be screened, when singular
s_a
ands_b
are found?
E = |T1'ST2|H12(P12), P12=T1(T2'ST1)^-1T2',(note alpha and beta, see the equations of appendix in J. Am. Chem. SOC. 1990, 112, 4214, and need the generalized slater-condon rule: J. Chem. Phys. 131, 124113, 2009) so if we set singular values to 1e^-11 if they are less than 1e^-11, the 1e energy and 2e energy will be almost not change, and this method is easier than using the generalized slater-condon rule. Then these very small singular value and their vectors should be not screened.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is not about the mathematical background. Here, the implementation may introduce artificial interactions between states. For instance, consider a molecule with point-group symmetry, the ground state and a single-excitation state belong to different irreps. Ideally, the interaction term between these states should be strictly zero. However, this treatment can mix the two states. Moreover, if these irreps are degenerate in energy, even a small off-diagonal term could cause a state with energy significantly lower than the ground state energy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The coupling error is about 1e^-11 for one electron because all other singular values are about 1.0 if there is only one or two small singular values.
Another method is using the generalized slater-condon rule.
Maybe we should compare these two methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deleted my comment again because i misread the context again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK so if you mentally transform everything to the biorthogonal basis and just think about the generalized Slater-Condon rules I think it can be shown that this still works out. Yes, if there are two determinants which differ by symmetry, then the JK matrices themselves will go crazy. But:
- If there is only one zero singular value due to symmetry, then the symmetry of the two-electron integrals will ultimately cancel the crazy (AO-basis) JK matrix.
(ii'|jj')
and(ij'|ji')
are only nonzero ifj\to j'
corresponds to the same symmetry element asi\to i'
, but this would imply that thej
th singular value must also be zero, and thei==j
case is canceled by exchange. So the two-electron part of the energy can't contribute, and neither can the one-electron part because it will be zero by symmetry. - If there are two singular values corresponding to the same symmetry change, then the two states do not in fact have different symmetries. The artificial
1e-11
floored singular values cancel between the two factors of the density matrix and the final multiplication bydet_ovlp
, and all is well. - If there are more than two zero singular values, then neither of the two terms of the Hamiltonian can cancel all of the
1e-11
factors indet_ovlp
, and the whole thing is at most~1e-11
, which is close enough to zero in most cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A generalized slater-condon rule code was finished. These two methods gave the same result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the canceling of self-interaction between Coulomb and exchange is possibly numerically problematic in the one-zero case since the difference between E(J) and E(K) is 11 orders of magnitude smaller than E(J) and E(K) themselves, and rather than discarding that small difference it is precisely the quantity we need. (In the two-zero case it's not a problem: 10^22 is actually where we need our point to float to.) You might go partway to the GSC rules by branching on the case of exactly 1 zero singular value. In the branch, you would omit the zero mode from the density matrix (D
) and from det_ovlp, but you would make a second pseudo-density matrix (P = u0 v0'
) from the vectors of the zero mode, without dividing by anything. Then you would compute the energy asymmetrically as
E = det_ovlp * (h.P + vhf[D].P)
Then to go the rest of the way, the branch for exactly two zero singular values would omit both of those modes from det_ovlp
, but you would make the density matrix out of just those modes (P = u0 v0' + u1 v1'
) with no divisors and the energy would be
E = det_ovlp * vhf[P].P / 2
What you can't do is just index down the left-hand sides of lines 83-84, since the zeros are necessary information to make interactions that fail to couple the two determinants vanish properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code was already send to Qiming when it was finished. Here is the main part.
def det_ovlp(mo1, mo2, occ1, occ2, ovlp):
if numpy.sum(occ1) !=numpy.sum(occ2):
raise RuntimeError('Electron numbers are not equal. Electronic coupling does not exist.')
c1_a = mo1[0][:, occ1[0]>0]
c1_b = mo1[1][:, occ1[1]>0]
c2_a = mo2[0][:, occ2[0]>0]
c2_b = mo2[1][:, occ2[1]>0]
o_a = numpy.asarray(reduce(numpy.dot, (c1_a.conj().T, ovlp, c2_a)))
o_b = numpy.asarray(reduce(numpy.dot, (c1_b.conj().T, ovlp, c2_b)))
u_a, s_a, vt_a = scipy.linalg.svd(o_a)
u_b, s_b, vt_b = scipy.linalg.svd(o_b)
sv_a = len(s_a[s_a<1e-8])
sv_b = len(s_b[s_b<1e-8])
s_a = numpy.where(abs(s_a) > 1.0e-11, s_a, 1.0e-11)
s_b = numpy.where(abs(s_b) > 1.0e-11, s_b, 1.0e-11)
OV = numpy.linalg.det(u_a)*numpy.linalg.det(u_b) \
* numpy.prod(s_a)*numpy.prod(s_b) \
* numpy.linalg.det(vt_a)*numpy.linalg.det(vt_b)
x_a = reduce(numpy.dot, (u_a*numpy.reciprocal(s_a), vt_a))
x_b = reduce(numpy.dot, (u_b*numpy.reciprocal(s_b), vt_b))
return OV, numpy.array((x_a, x_b)), sv_a + sv_b
def make_asym_dm_1(mo1, mo2, occ1, occ2, ovlp, x):
if numpy.sum(occ1) !=numpy.sum(occ2):
raise RuntimeError('Electron numbers are not equal. Electronic coupling does not exist.')
mo1_a = mo1[0][:, occ1[0]>0]
mo1_b = mo1[1][:, occ1[1]>0]
mo2_a = mo2[0][:, occ2[0]>0]
mo2_b = mo2[1][:, occ2[1]>0]
o_a = numpy.asarray(reduce(numpy.dot, (mo1_a.conj().T, ovlp, mo2_a)))
o_b = numpy.asarray(reduce(numpy.dot, (mo1_b.conj().T, ovlp, mo2_b)))
u_a, s_a, vt_a = scipy.linalg.svd(o_a)
u_b, s_b, vt_b = scipy.linalg.svd(o_b)
s_ah = s_a[s_a>1e-8]
s_bh = s_b[s_b>1e-8]
OV = numpy.linalg.det(u_a)*numpy.linalg.det(u_b) \
* numpy.prod(s_ah)*numpy.prod(s_bh) \
* numpy.linalg.det(vt_a)*numpy.linalg.det(vt_b)
x_a = reduce(numpy.dot, (u_a[:,s_a<1e-8], vt_a[s_a<1e-8]))
x_b = reduce(numpy.dot, (u_b[:,s_b<1e-8], vt_b[s_b<1e-8]))
dm_a = reduce(numpy.dot, (mo1_a, x_a, mo2_a.conj().T))
dm_b = reduce(numpy.dot, (mo1_b, x_b, mo2_b.conj().T))
x_a = reduce(numpy.dot, (u_a[:,s_a>1e-8], vt_a[s_a>1e-8]))
x_b = reduce(numpy.dot, (u_b[:,s_b>1e-8], vt_b[s_b>1e-8]))
dm_ra = reduce(numpy.dot, (mo1_a, x_a, mo2_a.conj().T))
dm_rb = reduce(numpy.dot, (mo1_b, x_b, mo2_b.conj().T))
return OV, numpy.array((dm_a, dm_b)), numpy.array((dm_ra, dm_rb))
def make_asym_dm_2(mo1, mo2, occ1, occ2, ovlp, x):
if numpy.sum(occ1) !=numpy.sum(occ2):
raise RuntimeError('Electron numbers are not equal. Electronic coupling does not exist.')
mo1_a = mo1[0][:, occ1[0]>0]
mo1_b = mo1[1][:, occ1[1]>0]
mo2_a = mo2[0][:, occ2[0]>0]
mo2_b = mo2[1][:, occ2[1]>0]
o_a = numpy.asarray(reduce(numpy.dot, (mo1_a.conj().T, ovlp, mo2_a)))
o_b = numpy.asarray(reduce(numpy.dot, (mo1_b.conj().T, ovlp, mo2_b)))
u_a, s_a, vt_a = scipy.linalg.svd(o_a)
u_b, s_b, vt_b = scipy.linalg.svd(o_b)
s_ah = s_a[s_a>1e-8]
s_bh = s_b[s_b>1e-8]
OV = numpy.linalg.det(u_a)*numpy.linalg.det(u_b) \
* numpy.prod(s_ah)*numpy.prod(s_bh) \
* numpy.linalg.det(vt_a)*numpy.linalg.det(vt_b)
x_a = reduce(numpy.dot, (u_a[:,s_a<1e-8], vt_a[s_a<1e-8]))
x_b = reduce(numpy.dot, (u_b[:,s_b<1e-8], vt_b[s_b<1e-8]))
dm_a = reduce(numpy.dot, (mo1_a, x_a, mo2_a.conj().T))
dm_b = reduce(numpy.dot, (mo1_b, x_b, mo2_b.conj().T))
return OV, numpy.array((dm_a, dm_b))
def scoup(mol, mo0, mo1, occ0, occ1, sv):
if sv == 'dml':
return scoup_dml(mol, mo0, mo1, occ0, occ1)
else:
return scoup_gsc(mol, mo0, mo1, occ0, occ1)
density matrix limitation
def scoup_dml(mol, mo0, mo1, occ0, occ1):
mf = scf.UHF(mol)
Calculate overlap between two determiant <I|F>
s, x, nsv = det_ovlp(mo0, mo1, occ0, occ1, mf.get_ovlp())
Construct density matrix
dm_01 = mf.make_asym_dm(mo0, mo1, occ0, occ1, x)
One-electron part contrbution
h1e = mf.get_hcore(mol)
e1_01 = numpy.einsum('ji,ji', h1e.conj(), dm_01[0]+dm_01[1])
Two-electron part contrbution. D_{IF} is asymmetric
#vhf_01 = get_veff(mf, dm_01, hermi=0)
vj, vk = mf.get_jk(mol, dm_01, hermi=0)
vhf_01 = vj[0] + vj[1] - vk
New total energy
e_01 = mf.energy_elec(dm_01, h1e, vhf_01)
return s, s * e_01[0], dm_01
Generalized Slater-Condon Rule
def scoup_gsc(mol, mo0, mo1, occ0, occ1):
mf = scf.UHF(mol)
Calculate overlap between two determiant <I|F>
s, x, nsv = det_ovlp(mo0, mo1, occ0, occ1, mf.get_ovlp())
if nsv == 0:
# Construct density matrix
dm_01 = mf.make_asym_dm(mo0, mo1, occ0, occ1, x)
# One-electron part contrbution
h1e = mf.get_hcore(mol)
e1_01 = numpy.einsum('ji,ji', h1e.conj(), dm_01[0]+dm_01[1])
# Two-electron part contrbution. D_{IF} is asymmetric
#vhf_01 = get_veff(mf, dm_01, hermi=0)
vj, vk = mf.get_jk(mol, dm_01, hermi=0)
vhf_01 = vj[0] + vj[1] - vk
# New total energy
e_01 = mf.energy_elec(dm_01, h1e, vhf_01)
coup = s * e_01[0]
elif nsv == 1:
# Construct density matrix
s0, dm_01, dm_r = make_asym_dm_1(mo0, mo1, occ0, occ1, mf.get_ovlp(), x)
# One-electron part contrbution
h1e = mf.get_hcore(mol)
e1_01 = numpy.einsum('ji,ji', h1e.conj(), dm_01[0]+dm_01[1])
# Two-electron part contrbution. D_{IF} is asymmetric
#vhf_01 = get_veff(mf, dm_01, hermi=0)
vj, vk = mf.get_jk(mol, dm_r, hermi=0)
vhf_01 = vj[0] + vj[1] - vk
# New total energy
e_01 = mf.energy_elec(dm_01, h1e, vhf_01)
coup = s0 * e_01[0]
elif nsv == 2:
# Construct density matrix
s0, dm_01 = make_asym_dm_2(mo0, mo1, occ0, occ1, mf.get_ovlp(), x)
# One-electron part contrbution
h1e = mf.get_hcore(mol)
e1_01 = numpy.einsum('ji,ji', h1e.conj(), dm_01[0]+dm_01[1])
# Two-electron part contrbution. D_{IF} is asymmetric
#vhf_01 = get_veff(mf, dm_01, hermi=0)
vj, vk = mf.get_jk(mol, dm_01, hermi=0)
vhf_01 = vj[0] + vj[1] - vk
# New total energy
e_01 = mf.energy_elec(dm_01, h1e, vhf_01)
coup = s0 * e_01[0]
else:
s = coup = dm_01 = 0
return s, coup, dm_01
The "sm-t" use the difference energy difference between mix state and Ms=1 triplet state as the coupling between two symmetry-adapted mix state. This can be more accurate than the approximate HF coupling. |
@baopengbp This implementation basically follows your original version. I'm not clear what are the "sm-t" eigenvalues, so I didn't implement them in this version. Please have a look whether this implementation makes sense.