def make_L1(pcmobj, r_vdw, ylm_1sph, fi): # See JCTC, 9, 3637, Eq (18) mol = pcmobj.mol natm = mol.natm lmax = pcmobj.lmax eta = pcmobj.eta nlm = (lmax + 1)**2 coords_1sph, weights_1sph = ddcosmo.make_grids_one_sphere( pcmobj.lebedev_order) ngrid_1sph = weights_1sph.size atom_coords = mol.atom_coords() ylm_1sph = ylm_1sph.reshape(nlm, ngrid_1sph) Lmat = numpy.zeros((natm, 3, natm, nlm, natm, nlm)) fi1 = make_fi1(pcmobj, pcmobj.get_atomic_radii()) for ja in range(natm): part_weights = weights_1sph.copy() part_weights[fi[ja] > 1] /= fi[ja, fi[ja] > 1] part_weights1 = numpy.zeros((natm, 3, ngrid_1sph)) tmp = part_weights[fi[ja] > 1] / fi[ja, fi[ja] > 1] part_weights1[:, :, fi[ja] > 1] = -tmp * fi1[:, :, ja, fi[ja] > 1] for ka in ddcosmo.atoms_with_vdw_overlap(ja, atom_coords, r_vdw): vjk = r_vdw[ja] * coords_1sph + atom_coords[ja] - atom_coords[ka] rv = lib.norm(vjk, axis=1) tjk = rv / r_vdw[ka] wjk0 = pcmobj.regularize_xt(tjk, eta, r_vdw[ka]) wjk1 = regularize_xt1(tjk, eta * r_vdw[ka]) sjk = vjk.T / rv wjk1 = 1. / r_vdw[ka] * wjk1 * sjk wjk01 = wjk0 * part_weights1 wjk0 *= part_weights wjk1 *= part_weights pol0 = sph.multipoles(vjk, lmax) pol1 = multipoles1(vjk, lmax) p1 = 0 for l in range(lmax + 1): fac = 4 * numpy.pi / (l * 2 + 1) / r_vdw[ka]**(l + 1) p0, p1 = p1, p1 + (l * 2 + 1) a = numpy.einsum('xn,zn,mn->zxm', ylm_1sph, wjk1, pol0[l]) a += numpy.einsum('xn,n,zmn->zxm', ylm_1sph, wjk0, pol1[l]) Lmat[ja, :, ja, :, ka, p0:p1] += -fac * a Lmat[ka, :, ja, :, ka, p0:p1] -= -fac * a a = numpy.einsum('xn,azn,mn->azxm', ylm_1sph, wjk01, pol0[l]) Lmat[:, :, ja, :, ka, p0:p1] += -fac * a return Lmat
def make_L1(pcmobj, r_vdw, ylm_1sph, fi): # See JCTC, 9, 3637, Eq (18) mol = pcmobj.mol natm = mol.natm lmax = pcmobj.lmax eta = pcmobj.eta nlm = (lmax+1)**2 coords_1sph, weights_1sph = ddcosmo.make_grids_one_sphere(pcmobj.lebedev_order) ngrid_1sph = weights_1sph.size atom_coords = mol.atom_coords() ylm_1sph = ylm_1sph.reshape(nlm,ngrid_1sph) Lmat = numpy.zeros((natm,3,natm,nlm,natm,nlm)) fi1 = make_fi1(pcmobj, pcmobj.get_atomic_radii()) for ja in range(natm): part_weights = weights_1sph.copy() part_weights[fi[ja]>1] /= fi[ja,fi[ja]>1] part_weights1 = numpy.zeros((natm,3,ngrid_1sph)) tmp = part_weights[fi[ja]>1] / fi[ja,fi[ja]>1] part_weights1[:,:,fi[ja]>1] = -tmp * fi1[:,:,ja,fi[ja]>1] for ka in ddcosmo.atoms_with_vdw_overlap(ja, atom_coords, r_vdw): vjk = r_vdw[ja] * coords_1sph + atom_coords[ja] - atom_coords[ka] rv = lib.norm(vjk, axis=1) tjk = rv / r_vdw[ka] wjk0 = pcmobj.regularize_xt(tjk, eta, r_vdw[ka]) wjk1 = regularize_xt1(tjk, eta*r_vdw[ka]) sjk = vjk.T / rv wjk1 = 1./r_vdw[ka] * wjk1 * sjk wjk01 = wjk0 * part_weights1 wjk0 *= part_weights wjk1 *= part_weights pol0 = sph.multipoles(vjk, lmax) pol1 = multipoles1(vjk, lmax) p1 = 0 for l in range(lmax+1): fac = 4*numpy.pi/(l*2+1) / r_vdw[ka]**(l+1) p0, p1 = p1, p1 + (l*2+1) a = numpy.einsum('xn,zn,mn->zxm', ylm_1sph, wjk1, pol0[l]) a+= numpy.einsum('xn,n,zmn->zxm', ylm_1sph, wjk0, pol1[l]) Lmat[ja,:,ja,:,ka,p0:p1] += -fac * a Lmat[ka,:,ja,:,ka,p0:p1] -= -fac * a a = numpy.einsum('xn,azn,mn->azxm', ylm_1sph, wjk01, pol0[l]) Lmat[:,:,ja,:,ka,p0:p1] += -fac * a return Lmat
def make_L(pcmobj, r_vdw, ylm_1sph, fi): # See JCTC, 9, 3637, Eq (18) mol = pcmobj.mol natm = mol.natm lmax = pcmobj.lmax eta = pcmobj.eta nlm = (lmax + 1)**2 coords_1sph, weights_1sph = make_grids_one_sphere(pcmobj.lebedev_order) ngrid_1sph = weights_1sph.size atom_coords = mol.atom_coords() ylm_1sph = ylm_1sph.reshape(nlm, ngrid_1sph) # JCP, 141, 184108 Eq (9), (12) is incorrect # L_diag = <lm|(1/|s-s'|)|l'm'> # Using Laplace expansion for electrostatic potential 1/r # L_diag = 4pi/(2l+1)/|s| <lm|l'm'> L_diag = numpy.zeros((natm, nlm)) p1 = 0 for l in range(lmax + 1): p0, p1 = p1, p1 + (l * 2 + 1) L_diag[:, p0:p1] = 4 * numpy.pi / (l * 2 + 1) L_diag *= 1. / r_vdw[:natm].reshape(-1, 1) Lmat = numpy.diag(L_diag.ravel()).reshape(natm, nlm, natm, nlm) for ja in range(natm): # scale the weight, precontract d_nj and w_n # see JCTC 9, 3637, Eq (16) - (18) # Note all values are scaled by 1/r_vdw to make the formulas # consistent to Psi in JCP, 141, 184108 part_weights = weights_1sph.copy() part_weights[fi[ja] > 1] /= fi[ja, fi[ja] > 1] for ka in atoms_with_vdw_overlap(ja, atom_coords, r_vdw[:natm]): vjk = r_vdw[ja] * coords_1sph + atom_coords[ja] - atom_coords[ka] tjk = lib.norm(vjk, axis=1) / r_vdw[ka] wjk = pcmobj.regularize_xt(tjk, eta, r_vdw[ka]) wjk *= part_weights pol = sph.multipoles(vjk, lmax) p1 = 0 for l in range(lmax + 1): fac = 4 * numpy.pi / (l * 2 + 1) / r_vdw[ka]**(l + 1) p0, p1 = p1, p1 + (l * 2 + 1) a = numpy.einsum('xn,n,mn->xm', ylm_1sph, wjk, pol[l]) Lmat[ja, :, ka, p0:p1] += -fac * a return Lmat
def make_L(pcmobj, r_vdw, ylm_1sph, fi): # See JCTC, 9, 3637, Eq (18) mol = pcmobj.mol natm = mol.natm lmax = pcmobj.lmax eta = pcmobj.eta nlm = (lmax+1)**2 coords_1sph, weights_1sph = make_grids_one_sphere(pcmobj.lebedev_order) ngrid_1sph = weights_1sph.size atom_coords = mol.atom_coords() ylm_1sph = ylm_1sph.reshape(nlm,ngrid_1sph) # JCP, 141, 184108 Eq (9), (12) is incorrect # L_diag = <lm|(1/|s-s'|)|l'm'> # Using Laplace expansion for electrostatic potential 1/r # L_diag = 4pi/(2l+1)/|s| <lm|l'm'> L_diag = numpy.zeros((natm,nlm)) p1 = 0 for l in range(lmax+1): p0, p1 = p1, p1 + (l*2+1) L_diag[:,p0:p1] = 4*numpy.pi/(l*2+1) L_diag *= 1./r_vdw.reshape(-1,1) Lmat = numpy.diag(L_diag.ravel()).reshape(natm,nlm,natm,nlm) for ja in range(natm): # scale the weight, precontract d_nj and w_n # see JCTC 9, 3637, Eq (16) - (18) # Note all values are scaled by 1/r_vdw to make the formulas # consistent to Psi in JCP, 141, 184108 part_weights = weights_1sph.copy() part_weights[fi[ja]>1] /= fi[ja,fi[ja]>1] for ka in atoms_with_vdw_overlap(ja, atom_coords, r_vdw): vjk = r_vdw[ja] * coords_1sph + atom_coords[ja] - atom_coords[ka] tjk = lib.norm(vjk, axis=1) / r_vdw[ka] wjk = pcmobj.regularize_xt(tjk, eta, r_vdw[ka]) wjk *= part_weights pol = sph.multipoles(vjk, lmax) p1 = 0 for l in range(lmax+1): fac = 4*numpy.pi/(l*2+1) / r_vdw[ka]**(l+1) p0, p1 = p1, p1 + (l*2+1) a = numpy.einsum('xn,n,mn->xm', ylm_1sph, wjk, pol[l]) Lmat[ja,:,ka,p0:p1] += -fac * a return Lmat
def cache_fake_multipoles(grids, r_vdw, lmax): # For each type of atoms, cache the product of last two terms in # JCP, 141, 184108, Eq (31): # x_{<}^{l} / x_{>}^{l+1} Y_l^m mol = grids.mol atom_grids_tab = grids.gen_atomic_grids(mol) r_vdw_type = {} for ia in range(mol.natm): symb = mol.atom_symbol(ia) if symb not in r_vdw_type: r_vdw_type[symb] = r_vdw[ia] cached_pol = {} for symb in atom_grids_tab: x_nj, w = atom_grids_tab[symb] r = lib.norm(x_nj, axis=1) # Different equations are used in JCTC, 9, 3637. r*Ys (the fake_pole) # is computed as r^l/r_vdw. "leak_idx" is not needed. # Here, the implementation is based on JCP, 141, 184108 leak_idx = r > r_vdw_type[symb] pol = sph.multipoles(x_nj, lmax) fak_pol = [] for l in range(lmax + 1): # x_{<}^{l} / x_{>}^{l+1} Y_l^m in JCP, 141, 184108, Eq (31) #:Ys = sph.real_sph_vec(x_nj/r.reshape(-1,1), lmax, True) #:rr = numpy.zeros_like(r) #:rr[r<=r_vdw[ia]] = r[r<=r_vdw[ia]]**l / r_vdw[ia]**(l+1) #:rr[r> r_vdw[ia]] = r_vdw[ia]**l / r[r>r_vdw[ia]]**(l+1) #:xx_ylm = numpy.einsum('n,mn->mn', rr, Ys[l]) xx_ylm = pol[l] * (1. / r_vdw_type[symb]**(l + 1)) # The line below is not needed for JCTC, 9, 3637 xx_ylm[:, leak_idx] *= (r_vdw_type[symb] / r[leak_idx])**(2 * l + 1) fak_pol.append(xx_ylm) cached_pol[symb] = (fak_pol, leak_idx) return cached_pol
def make_A(pcmobj, r_vdw, ylm_1sph, ui): # Part of A matrix defined in JCP, 144, 054101, Eq (43), (44) mol = pcmobj.mol natm = mol.natm lmax = pcmobj.lmax eta = pcmobj.eta nlm = (lmax + 1)**2 coords_1sph, weights_1sph = ddcosmo.make_grids_one_sphere( pcmobj.lebedev_order) ngrid_1sph = weights_1sph.size atom_coords = mol.atom_coords() ylm_1sph = ylm_1sph.reshape(nlm, ngrid_1sph) Amat = numpy.zeros((natm, nlm, natm, nlm)) for ja in range(natm): # w_u = precontract w_n U_j w_u = weights_1sph * ui[ja] p1 = 0 for l in range(lmax + 1): fac = 2 * numpy.pi / (l * 2 + 1) p0, p1 = p1, p1 + (l * 2 + 1) a = numpy.einsum('xn,n,mn->xm', ylm_1sph, w_u, ylm_1sph[p0:p1]) Amat[ja, :, ja, p0:p1] += -fac * a for ka in ddcosmo.atoms_with_vdw_overlap(ja, atom_coords, r_vdw): vjk = r_vdw[ja] * coords_1sph + atom_coords[ja] - atom_coords[ka] rjk = lib.norm(vjk, axis=1) pol = sph.multipoles(vjk, lmax) p1 = 0 weights = w_u / rjk**(l * 2 + 1) for l in range(lmax + 1): fac = 4 * numpy.pi * l / (l * 2 + 1) * r_vdw[ka]**(l + 1) p0, p1 = p1, p1 + (l * 2 + 1) a = numpy.einsum('xn,n,mn->xm', ylm_1sph, weights, pol[l]) Amat[ja, :, ka, p0:p1] += -fac * a return Amat
def cache_fake_multipoles(grids, r_vdw, lmax): # For each type of atoms, cache the product of last two terms in # JCP, 141, 184108, Eq (31): # x_{<}^{l} / x_{>}^{l+1} Y_l^m mol = grids.mol atom_grids_tab = grids.gen_atomic_grids(mol) r_vdw_type = {} for ia in range(mol.natm): symb = mol.atom_symbol(ia) if symb not in r_vdw_type: r_vdw_type[symb] = r_vdw[ia] cached_pol = {} for symb in atom_grids_tab: x_nj, w = atom_grids_tab[symb] r = lib.norm(x_nj, axis=1) # Different equations are used in JCTC, 9, 3637. r*Ys (the fake_pole) # is computed as r^l/r_vdw. "leak_idx" is not needed. # Here, the implementation is based on JCP, 141, 184108 leak_idx = r > r_vdw_type[symb] pol = sph.multipoles(x_nj, lmax) fak_pol = [] for l in range(lmax+1): # x_{<}^{l} / x_{>}^{l+1} Y_l^m in JCP, 141, 184108, Eq (31) #:Ys = sph.real_sph_vec(x_nj/r.reshape(-1,1), lmax, True) #:rr = numpy.zeros_like(r) #:rr[r<=r_vdw[ia]] = r[r<=r_vdw[ia]]**l / r_vdw[ia]**(l+1) #:rr[r> r_vdw[ia]] = r_vdw[ia]**l / r[r>r_vdw[ia]]**(l+1) #:xx_ylm = numpy.einsum('n,mn->mn', rr, Ys[l]) xx_ylm = pol[l] * (1./r_vdw_type[symb]**(l+1)) # The line below is not needed for JCTC, 9, 3637 xx_ylm[:,leak_idx] *= (r_vdw_type[symb]/r[leak_idx])**(2*l+1) fak_pol.append(xx_ylm) cached_pol[symb] = (fak_pol, leak_idx) return cached_pol
def make_A(pcmobj, r_vdw, ylm_1sph, ui): # Part of A matrix defined in JCP, 144, 054101, Eq (43), (44) mol = pcmobj.mol natm = mol.natm lmax = pcmobj.lmax eta = pcmobj.eta nlm = (lmax+1)**2 coords_1sph, weights_1sph = ddcosmo.make_grids_one_sphere(pcmobj.lebedev_order) ngrid_1sph = weights_1sph.size atom_coords = mol.atom_coords() ylm_1sph = ylm_1sph.reshape(nlm,ngrid_1sph) Amat = numpy.zeros((natm,nlm,natm,nlm)) for ja in range(natm): # w_u = precontract w_n U_j w_u = weights_1sph * ui[ja] p1 = 0 for l in range(lmax+1): fac = 2*numpy.pi/(l*2+1) p0, p1 = p1, p1 + (l*2+1) a = numpy.einsum('xn,n,mn->xm', ylm_1sph, w_u, ylm_1sph[p0:p1]) Amat[ja,:,ja,p0:p1] += -fac * a for ka in ddcosmo.atoms_with_vdw_overlap(ja, atom_coords, r_vdw): vjk = r_vdw[ja] * coords_1sph + atom_coords[ja] - atom_coords[ka] rjk = lib.norm(vjk, axis=1) pol = sph.multipoles(vjk, lmax) p1 = 0 weights = w_u / rjk**(l*2+1) for l in range(lmax+1): fac = 4*numpy.pi*l/(l*2+1) * r_vdw[ka]**(l+1) p0, p1 = p1, p1 + (l*2+1) a = numpy.einsum('xn,n,mn->xm', ylm_1sph, weights, pol[l]) Amat[ja,:,ka,p0:p1] += -fac * a return Amat