def compute_coupling( self, S, indexA, indexB, doETF=False, # Do Joe Subotnik ETF? ): if indexA == indexB: raise ValueError('indexA == indexB: you want a gradient?') # Sizes nao = self.sizes['nao'] nmo = self.sizes['nmo'] nocc = self.sizes['nocc'] nvir = self.sizes['nvir'] # Energy difference between states dE = self.evals[S][indexB] - self.evals[S][indexA] CA = self.evecs[S][indexA] CB = self.evecs[S][indexB] Cocc = self.tensors['Cocc'] Cvir = self.tensors['Cvir'] # Difference transition OPDM in AO basis D1dao = ls.Tensor((nao, ) * 2) if CA is None: # <0|B> # T_jb = + sqrt(2) CB_jb D1dao[...] += np.sqrt(2.0) * ls.Tensor.chain([Cocc, CB, Cvir], [False, False, True]) elif CB is None: # <A|0> # T_ai = + sqrt(2) CA_ia D1dao[...] += np.sqrt(2.0) * ls.Tensor.chain([Cvir, CA, Cocc], [False, True, True]) else: # T_ab <- + CA_ia CB_ib D1dao[...] = ls.Tensor.chain([Cvir, CA, CB, Cvir], [False, True, False, True]) # T_ji <- - CA_ia CB_ja (watch the tanspose!) D1dao[...] -= ls.Tensor.chain([Cocc, CB, CA, Cocc], [False, False, True, True]) raise ValueError("Not implemented")
def orbital_atomic_charges( self, C, ): """ Compute the orbital atomic charges Q_A^i for a set of orbitals C_mi Only the electronic contribution is considered. Params: C (ls.Tensor of shape (nao, ni)) - Orbital coefficients Returns: Q (ls.Tensor of shape (natom, ni)) - Orbital atomic charges """ L = ls.Tensor.chain([C,self.S,self.A],[True,False,False]) Q = ls.Tensor((self.minbasis.natom,C.shape[1]), 'Q IBO') for A, inds2 in enumerate(self.minao_inds): Q[A,:] = np.sum(L[:,inds2]**2,1) return Q
def test_casbox_orbital_transformation_det( S=0, M=6, Na=3, Nb=3, H1=None, I1=None, ): # Random rotation matrix A = ls.Tensor.array(np.random.rand(M, M) + 0.3 * np.eye(M)) Q, R = sp.qr(A) Q = ls.Tensor.array(Q) # Random permutation matrix (forces pivoting) perm = np.random.permutation(list(range(M))) Q2 = ls.Tensor((M,)*2) for k, k2 in enumerate(perm): Q2[k,k2] = 1.0 # Orbital transformation matrix (some rotation, required pivoting in LU) Q3 = ls.Tensor.chain([Q, Q2], [False, False]) # Integrals in transformed basis H2 = ls.Tensor.array(np.einsum('ae,bf,ab->ef', Q3, Q3, H1)) I2 = ls.Tensor.array(np.einsum('ae,bf,cg,dh,abcd->efgh', Q3, Q3, Q3, Q3, I1)) casbox1 = ls.CASBox(M, Na, Nb, H1, I1) casbox2 = ls.CASBox(M, Na, Nb, H2, I2) ebox1 = ls.ExplicitCASBox(casbox1) ebox2 = ls.ExplicitCASBox(casbox2) evec1 = ebox1.evec(S,0) evec2 = ebox2.evec(S,0) evec1d = casbox1.CSF_basis(S).transform_CSF_to_det(evec1) evec2d = casbox2.CSF_basis(S).transform_CSF_to_det(evec2) evec1dp = casbox1.orbital_transformation_det(Q3, evec1d) O = evec1dp.vector_dot(evec2d) OK = abs(abs(O) - 1.0) < 1.0E-13 return OK
def atomic_charges( self, D, ): """ Compute the atomic charges Q_A for an OPDM D_mn. Only the electronic contribution is considered. Params: D (ls.Tensor of shape (nao, nao)) - density matrix Returns: Q (ls.Tensor of shape (natom,)) - atomic charges """ V = ls.Tensor.chain([self.A, self.S, D, self.S, self.A], [True, False, False, False, False]) V2 = np.diag(V) Q = ls.Tensor((self.minbasis.natom,), 'Q IBO') for A, inds2 in enumerate(self.minao_inds): Q[A] = np.sum(V2[inds2]) return Q
def compute_external_potential( self, pairlist, ): """ Return the complete external potential of this Geometry, including the nuclear attraction potential of the QM molecule, and any external potential terms involving other structures in the Geometry. """ V = ls.Tensor((pairlist.basis1.nao, pairlist.basis2.nao)) ls.IntBox.potential(self.resources, self.ewald, pairlist, self.qm_molecule_ecp.xyzZ, V) # QM/MM if self.qmmm: V[...] += self.qmmm.compute_mm_potential(self.resources, self.ewald, pairlist) # ECP if self.ecp: V[...] += ls.IntBox.ecp( self.resources, pairlist, self.ecp, self.options['threecp'], ) return V
O 0.000000000000 0.000000000000 -0.129476890157 H 0.000000000000 -1.494186750504 1.027446102928 H 0.000000000000 1.494186750504 1.027446102928""", name='h2o', scale=1.0) bas = ls.Basis.from_gbs_file(mol, 'cc-pvdz') minao = ls.Basis.from_gbs_file(mol, 'cc-pvdz-minao') res = ls.ResourceList.build_cpu() nocc = ls.SAD.sad_nocc_neutral(mol) print(nocc) Q = ls.Tensor((3, )) Q[0] = 4.5 Q[1] = 0.5 Q[2] = 0.5 nocc = ls.SAD.sad_nocc(mol, Q) print(nocc) print(ls.SAD.sad_nocc_atoms()) S = ls.IntBox.overlap(res, ls.PairList.build_schwarz(bas, bas, True, 1.0E-14)) print(S) C = ls.SAD.sad_orbitals(res, nocc, minao, bas) print(C)
def __init__( self, options, # A dictionary of user options ): # => Default Options <= # self.options = RHF.options.copy() # => Override Options <= # for key, val in options.items(): if not key in list(self.options.keys()): raise ValueError('Invalid user option: %s' % key) self.options[key] = val # => Useful Registers <= # self.sizes = {} self.scalars = {} self.tensors = {} # => Title Bars <= # print('==> RHF <==\n') # => Problem Geometry <= # self.resources = self.options['resources'] self.molecule = self.options['molecule'] self.basis = self.options['basis'] self.minbasis = self.options['minbasis'] print(self.resources) print(self.molecule) print(self.basis) print(self.minbasis) self.sizes['natom'] = self.molecule.natom self.sizes['nao'] = self.basis.nao self.sizes['nmin'] = self.minbasis.nao # => Nuclear Repulsion <= # self.scalars['Enuc'] = self.molecule.nuclear_repulsion_energy() print('Nuclear Repulsion Energy = %20.14f\n' % self.scalars['Enuc']) # => Integral Thresholds <= # print('Integral Thresholds:') print(' Threshold PQ = %11.3E' % self.options['thre_pq']) print(' Threshold DP = %11.3E' % self.options['thre_dp']) print(' Threshold SP = %11.3E' % self.options['thre_sp']) print('') # => PairList <= # self.pairlist = ls.PairList.build_schwarz( self.basis, self.basis, True, self.options['thre_pq']) print(self.pairlist) # => One-Electron Integrals <= # print('One-Electron Integrals:\n') self.tensors['S'] = ls.IntBox.overlap( self.resources, self.pairlist) self.tensors['T'] = ls.IntBox.kinetic( self.resources, self.pairlist) self.tensors['V'] = ls.IntBox.potential( self.resources, ls.Ewald.coulomb(), self.pairlist, self.molecule.xyzZ) self.tensors['H'] = ls.Tensor.array(self.tensors['T'].np + self.tensors['V'].np) # => Canonical Orthogonalization <= # print('Canonical Orthogonalization:') print(' Threshold = %11.3E' % self.options['thre_canonical']) self.tensors['X'] = ls.Tensor.canonical_orthogonalize(self.tensors['S'], self.options['thre_canonical']) self.sizes['nmo'] = self.tensors['X'].shape[1] print(' Nmo = %11d' % self.sizes['nmo']) print(' Ndiscard = %11d' % (self.sizes['nao'] - self.sizes['nmo'])) print('') # => SAD Guess <= # print('SAD Guess:\n') self.tensors['CSAD'] = ls.SAD.orbitals( self.resources, self.molecule, self.basis, self.minbasis) self.tensors['DSAD'] = ls.Tensor.chain([self.tensors['CSAD'],self.tensors['CSAD']],[False,True]) # => DIIS <= # diis = ls.DIIS( self.options['diis_max_vecs'], ) print(diis) # => Determine number of electrons (and check for integral singlet) <= # if self.options['nocc'] is None: if self.options['charge'] is None: charge = self.molecule.charge else: charge = self.options['charge'] nelec = self.molecule.nuclear_charge - charge nalpha = 0.5 * nelec if nalpha != round(nalpha): raise ValueError('Cannot do fractional electrons. Possibly charge/multiplicity are wrong.') self.sizes['nocc'] = int(nalpha) else: self.sizes['nocc'] = int(self.options['nocc']) self.sizes['nvir'] = self.sizes['nmo'] - self.sizes['nocc'] print('Orbital Spaces:') print(' Nocc = %d' % self.sizes['nocc']) print(' Nvir = %d' % self.sizes['nvir']) print('') # ==> SCF Iterations <== # print('Convergence Options:') print(' Max Iterations = %11d' % self.options['maxiter']) print(' E Convergence = %11.3E' % self.options['e_convergence']) print(' G Convergence = %11.3E' % self.options['g_convergence']) print('') self.tensors['D'] = ls.Tensor.array(self.tensors['DSAD']) Eold = 0. converged = False print('SCF Iterations:\n') print('%4s: %24s %11s %11s' % ('Iter', 'Energy', 'dE', 'dG')) for iter in range(self.options['maxiter']): # => Compute J/K Matrices <= # import time start = time.time() self.tensors['J'] = ls.IntBox.coulomb( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, self.tensors['D'], self.options['thre_sp'], self.options['thre_dp'], ) print('J: %11.3E' % (time.time() - start)) start = time.time() self.tensors['K'] = ls.IntBox.exchange( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, self.tensors['D'], True, self.options['thre_sp'], self.options['thre_dp'], ) print('K: %11.3E' % (time.time() - start)) start = time.time() # => Build Fock Matrix <= # self.tensors['F'] = ls.Tensor.array(self.tensors['H']) self.tensors['F'].np[...] += 2. * self.tensors['J'].np self.tensors['F'].np[...] -= 1. * self.tensors['K'].np # => Compute Energy <= # self.scalars['Escf'] = self.scalars['Enuc'] + \ self.tensors['D'].vector_dot(self.tensors['H']) + \ self.tensors['D'].vector_dot(self.tensors['F']) dE = self.scalars['Escf'] - Eold Eold = self.scalars['Escf'] # => Compute Orbital Gradient <= # G1 = ls.Tensor.chain([self.tensors[x] for x in ['X', 'S', 'D', 'F', 'X']], [True] + [False]*4) G1.antisymmetrize() G1[...] *= 2.0 self.tensors['G'] = G1 dG = np.max(np.abs(self.tensors['G'])) # => Print Iteration <= # print('%4d: %24.16E %11.3E %11.3E' % (iter, self.scalars['Escf'], dE, dG)) # => Check Convergence <= # if iter > 0 and abs(dE) < self.options['e_convergence'] and dG < self.options['g_convergence']: converged = True break # => DIIS Fock Matrix <= # if iter > 0: self.tensors['F'] = diis.iterate(self.tensors['F'], self.tensors['G']) # => Diagonalize Fock Matrix <= # F2 = ls.Tensor.chain([self.tensors[x] for x in ['X', 'F', 'X']],[True,False,False]) e2, U2 = ls.Tensor.eigh(F2) C2 = ls.Tensor.chain([self.tensors['X'], U2],[False,False]) e2.name = 'eps' C2.name = 'C' self.tensors['C'] = C2 self.tensors['eps'] = e2 # => Aufbau Occupy Orbitals <= # self.tensors['Cocc'] = ls.Tensor((self.sizes['nao'], self.sizes['nocc']),'Cocc') self.tensors['Cocc'].np[...] = self.tensors['C'].np[:,:self.sizes['nocc']] # => Compute Density Matrix <= # self.tensors['D'] = ls.Tensor.chain([self.tensors['Cocc'], self.tensors['Cocc']], [False, True]) print('T: %11.3E' % (time.time() - start)) # => Print Convergence <= # print('') if converged: print('SCF Converged\n') else: print('SCF Failed\n') # => Print Final Energy <= # print('SCF Energy = %24.16E\n' % self.scalars['Escf']) # => Cache the Orbitals/Eigenvalues (for later use) <= # self.tensors['Cvir'] = ls.Tensor((self.sizes['nao'], self.sizes['nvir']), 'Cvir') self.tensors['Cvir'].np[...] = self.tensors['C'].np[:,self.sizes['nocc']:] self.tensors['eps_occ'] = ls.Tensor((self.sizes['nocc'],), 'eps_occ') self.tensors['eps_occ'].np[...] = self.tensors['eps'].np[:self.sizes['nocc']] self.tensors['eps_vir'] = ls.Tensor((self.sizes['nvir'],), 'eps_vir') self.tensors['eps_vir'].np[...] = self.tensors['eps'].np[self.sizes['nocc']:] # => Print Orbital Energies <= # print(self.tensors['eps_occ']) print(self.tensors['eps_vir']) # => Trailer Bars <= # print('"I love it when a plan comes together!"') print(' --LTC John "Hannibal" Smith\n') print('==> End RHF <==\n')
def diffraction_pattern( self, S, index1, index2, ): """ Compute ab initio elastic or inelastic diffraction pattern for xray or ued probes, using a variety of computational methods. If it is tractable to use moments to exactly compute the scattering signal (isotropic, parallel, perpendicular cases) this is done first. If this is not tractable, the full diffraction pattern is directly computed. Params: S (int) - spin index index1 (int) - index of bra state index2 (int) - index of ket state Elastic scattering is assumed if index1==index2 (scattering off of total state density) Inelastic scattering is assumed if index1!=index2 (scattering off of transition density) Returns: I (ls.Tensor of shape (ns,neta)) - diffraction pattern. """ # First try to compute via moments if self.anisotropy in ['isotropic', 'parallel', 'perpendicular']: IM = self.diffraction_moments(S=S,index1=index1,index2=index2) return AISCAT.pattern_from_moments(IM,eta=self.eta) # Otherwise, must compute aligned scattering signal # Grid density collocation xyzq = self.compute_xyzq( D=self.lot.compute_opdm_ao(S, index1, index2), include_nuclei=self.mode=='ued' and index1==index2, ) print('Grid Density: %12.6f\n' % np.sum(xyzq[:,3])) # Scattering collocation points theta = 2.0 * np.arcsin(self.s[...] * self.L / (4.0 * np.pi)) tt, ee = np.meshgrid(theta, self.eta, indexing='ij') ss, ee = np.meshgrid(self.s, self.eta, indexing='ij') # Compute scattering vectors sx = ss * np.cos(tt / 2.0) * np.sin(ee) sy = ss * np.sin(tt / 2.0) sz = ss * np.cos(tt / 2.0) * np.cos(ee) # Pack for LS sxyz = ls.Tensor((sx.size,3)) sxyz[:,0] = np.ravel(sx) sxyz[:,1] = np.ravel(sy) sxyz[:,2] = np.ravel(sz) # Diffraction function if self.algorithm == 'cpu': fun = ls2.aligned_diffraction2 elif self.algorithm == 'gpu_f_f': fun = ls2.aligned_diffraction_gpu_f_f elif self.algorithm == 'gpu_f_d': fun = ls2.aligned_diffraction_gpu_f_d elif self.algorithm == 'gpu_d_d': fun = ls2.aligned_diffraction_gpu_d_d # Compute diffraction pattern I = fun( self.lot.geometry.resources, sxyz, xyzq, ) # Make sure return is (ns, neta) I = ls.Tensor.array(np.reshape(I, (self.s.shape[0], self.eta.shape[0]))) # 1/s^4 weight in UED if self.mode == 'ued': I[...] /= ss**4 return I
def compute_overlap( cisA, cisB, S, ): """ Compute the overlap between all states in spin block S in two CIS object. Params: cisA (CIS) - the bra-side CIS object cisB (CIS) - the ket-side CIS object S (int) - the spin index Returns: O (ls.Tensor, shape (nstateA, nstateB)) - the overlap matrix elements """ # TODO: Benchmark these overlaps # Build the overlap integrals (p|q') in the AO basis pairlist = ls.PairList.build_schwarz(cisA.basis, cisB.basis, False, cisA.pairlist.thre) Sao = ls.IntBox.overlap(cisA.resources, pairlist) # Build the orbital overlap integrals in the occupied and active blocks of the MO basis Sij = ls.Tensor.chain( [cisA.tensors['Cocc'], Sao, cisB.tensors['Cocc']], [True, False, False]) Sib = ls.Tensor.chain( [cisA.tensors['Cocc'], Sao, cisB.tensors['Cvir']], [True, False, False]) Saj = ls.Tensor.chain( [cisA.tensors['Cvir'], Sao, cisB.tensors['Cocc']], [True, False, False]) Sab = ls.Tensor.chain( [cisA.tensors['Cvir'], Sao, cisB.tensors['Cvir']], [True, False, False]) # Compute the overlap inverse and determinant in the core space detC = Sij.invert_lu() # Form modified metric integrals Mij = Sij Mia = ls.Tensor((cisA.sizes['nocc'], cisA.sizes['nvir'])) Mjb = ls.Tensor((cisB.sizes['nocc'], cisB.sizes['nvir'])) Mab = ls.Tensor.array(Sab) if detC != 0.0: # Mai[...] = ls.Tensor.chain([Saj, Mij], [False, False]) Mia[...] = ls.Tensor.chain( [Mij, Saj], [True, True]) # Transposed from the notes for easier dot Mjb[...] = ls.Tensor.chain([Mij, Sib], [False, False]) Mab[...] -= ls.Tensor.chain([Saj, Mjb], [False, False]) # Compute the intermediates for connected overlap term A2s = [] for A, evecA in enumerate(cisA.evecs[S]): if S == 0 and A == 0: A2s.append(None) continue A2s.append(ls.Tensor.chain([evecA, Mab], [False, False])) B2s = [] for B, evecB in enumerate(cisB.evecs[S]): if S == 0 and B == 0: B2s.append(None) continue B2s.append(ls.Tensor.chain([Mij, evecB], [True, False])) # Compute the overlaps O = ls.Tensor((len(cisA.evecs[S]), len(cisB.evecs[S]))) for A, evecA in enumerate(cisA.evecs[S]): for B, evecB in enumerate(cisB.evecs[S]): if S == 0 and A == 0 and B == 0: # <0|0> O[A, B] = 1.0 continue if S == 0 and A == 0: # <0|B> O[A, B] = np.sqrt(2.0) * evecB.vector_dot(Mjb) continue if S == 0 and B == 0: # <A|0> O[A, B] = np.sqrt(2.0) * evecA.vector_dot(Mia) continue else: # <A|B> O[A, B] = A2s[A].vector_dot(B2s[B]) if S == 0: O[A, B] += 2.0 * evecA.vector_dot( Mia) * evecB.vector_dot(Mjb) # Account for the core overlap O[...] *= detC**2 return O
def compute_gradient( self, S, index, ): # Handle the reference gradient separately if S == 0 and index == 0: return self.reference.compute_gradient() # Sizes nao = self.sizes['nao'] nmo = self.sizes['nmo'] nocc = self.sizes['nocc'] nvir = self.sizes['nvir'] # CI vector and orbitals Cvec = self.evecs[S][index] C = self.tensors['C'] Cocc = self.tensors['Cocc'] Cvir = self.tensors['Cvir'] # Difference OPDM in AO basis D1dao = ls.Tensor.chain([Cvir, Cvec, Cvec, Cvir], [False, True, False, True]) D1dao[...] -= ls.Tensor.chain([Cocc, Cvec, Cvec, Cocc], [False, False, True, True]) # Total OPDM in AO basis D1ao = ls.Tensor.chain([Cocc, Cocc], [False, True]) D1ao[...] *= 2.0 D1ao[...] += D1dao # CI Vector in AO basis C1ao = ls.Tensor.chain([Cocc, Cvec, Cvir], [False, False, True]) # => J/K contributions <= # JD1 = ls.IntBox.coulomb( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, D1dao, self.options['thre_sp'], self.options['thre_dp'], ) KD1 = ls.IntBox.exchange( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, D1dao, True, self.options['thre_sp'], self.options['thre_dp'], ) JC1 = ls.IntBox.coulomb( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, C1ao, self.options['thre_sp'], self.options['thre_dp'], ) KC1 = ls.IntBox.exchange( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, C1ao, False, self.options['thre_sp'], self.options['thre_dp'], ) # TODO: The following manipulations can be *much* faster if one exploits block sparsity # => Fock Matrices (Long Way) <= # FO2 = ls.Tensor.chain([C, self.reference.tensors['F'], C], [True, False, False]) FD2 = ls.Tensor.chain([C, JD1, C], [True, False, False]) FD2.np[...] -= 0.5 * ls.Tensor.chain([C, KD1, C], [True, False, False]).np FA2 = ls.Tensor.array(FO2) FA2.np[...] += FD2 # => MO-Basis OPDMs (Long Way) <= # D2 = ls.Tensor((nmo, ) * 2) D2[nocc:, nocc:] += ls.Tensor.chain([Cvec, Cvec], [True, False]) D2[:nocc, :nocc] -= ls.Tensor.chain([Cvec, Cvec], [False, True]) A2 = ls.Tensor.array(np.diag(self.reference.tensors['n'])) A2[...] *= 2.0 A2[...] += D2 # => MO-Basis Amplitudes (Long Way) <= # C2 = ls.Tensor((nmo, ) * 2) C2[:nocc, nocc:] = Cvec # => Lagrangian (Long Way) <= # X = ls.Tensor([self.sizes['nmo']] * 2) X.np[...] += ls.Tensor.chain([A2, FA2], [False, False]) X.np[...] -= ls.Tensor.chain([D2, FD2], [False, False]) if S == 0: X.np[...] += 2. * ls.Tensor.chain([C2, C, JC1, C], [False, True, True, False]).np X.np[...] += 2. * ls.Tensor.chain([C2, C, JC1, C], [True, True, False, False]).np X.np[...] -= 1. * ls.Tensor.chain([C2, C, KC1, C], [False, True, True, False]).np X.np[...] -= 1. * ls.Tensor.chain([C2, C, KC1, C], [True, True, False, False]).np X[...] *= 2.0 # Spin-summed convention X = X.transpose( ) # Ed's convention: X_pq = 2 h_pr D1_qr + 4 (pr|st) D2_qrst # => TPDM Gradient <= # grads = collections.OrderedDict() # Coulomb integral gradient 1 grads['J1'] = ls.IntBox.coulombGrad( self.resources, ls.Ewald.coulomb(), self.pairlist, D1ao, D1ao, self.options['thre_sp'], self.options['thre_dp'], ) grads['J1'].np[...] *= +0.5 # Coulomb integral gradient 2 grads['J2'] = ls.IntBox.coulombGrad( self.resources, ls.Ewald.coulomb(), self.pairlist, D1dao, D1dao, self.options['thre_sp'], self.options['thre_dp'], ) grads['J2'].np[...] *= -0.5 # Exchange integral gradient 1 grads['K1'] = ls.IntBox.exchangeGrad( self.resources, ls.Ewald.coulomb(), self.pairlist, D1ao, D1ao, True, True, True, self.options['thre_sp'], self.options['thre_dp'], ) grads['K1'].np[...] *= -0.25 # Exchange integral gradient 2 grads['K2'] = ls.IntBox.exchangeGrad( self.resources, ls.Ewald.coulomb(), self.pairlist, D1dao, D1dao, True, True, True, self.options['thre_sp'], self.options['thre_dp'], ) grads['K2'].np[...] *= +0.25 # Coulomb integral gradient 3 grads['J3'] = ls.IntBox.coulombGrad( self.resources, ls.Ewald.coulomb(), self.pairlist, C1ao, C1ao, self.options['thre_sp'], self.options['thre_dp'], ) grads['J3'].np[...] *= +2.0 if S == 0 else 0.0 # Exchange integral gradient 2 grads['K3'] = ls.IntBox.exchangeGrad( self.resources, ls.Ewald.coulomb(), self.pairlist, C1ao, C1ao, False, False, True, self.options['thre_sp'], self.options['thre_dp'], ) grads['K3'].np[...] *= -1.0 grad2e = ls.Tensor.zeros_like(grads['J1']) for grad in list(grads.values()): grad2e[...] += grad # for key, grad in grads.iteritems(): # grad.name = key # print grad return self.reference.compute_hf_based_gradient(D1ao, X, grad2e)
def compute_dipoles( self, S, x0=0.0, y0=0.0, z0=0.0, ): """Calculates total and transition dipole moments for all states in spin block S. Params: S (int) - spin block index x0 (float) - origin y0 (float) - origin z0 (float) - origin Returns: XYZ ((nstates, nstates) ls.Tensor.array) - transition dipole matrix """ # xyz dipole matrix in AO basis XYZ = ls.IntBox.dipole( self.resources, self.pairlist, x0, y0, z0, ) evecs = self.evecs[S] nstate = len(evecs) Cocc = self.tensors['Cocc'] Cvir = self.tensors['Cvir'] XYZ3 = [ls.Tensor((nstate, nstate)) for x in range(3)] for A, evecA in enumerate(evecs): for B, evecB in enumerate(evecs): if A > B: continue if evecA is None and evecB is None: continue # <0|D|0> if evecA is None: # <0|D|0> # D_ia = + sqrt(2) C_ia Dao = ls.Tensor.chain([Cocc, evecB, Cvir], [False, False, True]) Dao[...] *= np.sqrt(2.0) else: # D_ab = + C_ia C_ib Dao = ls.Tensor.chain([Cvir, evecA, evecB, Cvir], [False, True, False, True]) # D_ij = - C_ja C_ia Dao[...] -= ls.Tensor.chain([Cocc, evecB, evecA, Cocc], [False, False, True, True]) # Dot with AB-basis XYZ3[0][A, B] = XYZ3[0][B, A] = Dao.vector_dot(XYZ[0]) XYZ3[1][A, B] = XYZ3[1][B, A] = Dao.vector_dot(XYZ[1]) XYZ3[2][A, B] = XYZ3[2][B, A] = Dao.vector_dot(XYZ[2]) # Reference density matrix Dcore = ls.Tensor.chain([Cocc, Cocc], [False, True]) Dcore[...] *= 2.0 Xcore = Dcore.vector_dot(XYZ[0]) Ycore = Dcore.vector_dot(XYZ[1]) Zcore = Dcore.vector_dot(XYZ[2]) # Get nuclear dipole for A, atomA in enumerate(self.qm_molecule.atoms): Xcore -= atomA.Z * (atomA.x - x0) Ycore -= atomA.Z * (atomA.y - y0) Zcore -= atomA.Z * (atomA.z - z0) for A in range(nstate): XYZ3[0][A, A] += Xcore XYZ3[1][A, A] += Ycore XYZ3[2][A, A] += Zcore return XYZ3
def compute_states( self, Sindex, nstate, ): # => Title <= # title = 'S=%d' % Sindex if self.print_level: print('=> %s States <=\n' % title) if Sindex not in [0, 2]: raise RuntimeError('Invalid Sindex for CIS: %d' % Sindex) # => Preconditioner <= # F = ls.Tensor((self.sizes['nocc'], self.sizes['nvir'])) F.np[...] += np.einsum('i,a->ia', np.ones(self.sizes['nocc']), self.tensors['eps_vir']) F.np[...] -= np.einsum('i,a->ia', self.tensors['eps_occ'], np.ones(self.sizes['nvir'])) # => Guess Vectors <= # nstate_new = nstate - 1 if Sindex == 0 else nstate nguess = min(self.sizes['nocc'] * self.sizes['nvir'], nstate_new * self.options['nguess_per_root']) if self.print_level > 1: print('Guess Details:') print(' Nguess: %11d' % nguess) Forder = sorted([(x, xind[0], xind[1]) for xind, x in np.ndenumerate(F)]) Cs = [] for k in range(nguess): C = ls.Tensor((self.sizes['nocc'], self.sizes['nvir'])) r = Forder[k][1] c = Forder[k][2] if self.print_level > 1: print(' %3d: %5d -> %-5d' % (k, r, c + self.sizes['nocc'])) C.np[r, c] = +1.0 Cs.append(C) if self.print_level > 1: print('') # => Convergence Info <= # if self.print_level > 1: print('Convergence:') print(' Maxiter: %11d' % self.options['maxiter']) print('') # => Davidson Object <= # dav = ls.Davidson( nstate_new, self.options['nmax'], self.options['r_convergence'], self.options['norm_cutoff'], ) if self.print_level > 1: print(dav) # ==> Davidson Iterations <== # if self.print_level: print('%s CIS Iterations:\n' % title) print('%4s: %11s' % ('Iter', '|R|')) converged = False for iter in range(self.options['maxiter']): # Sigma Vector Builds Ss = [ls.Tensor.clone(C) for C in Cs] for C, S in zip(Cs, Ss): # One-Particle Term S.np[...] *= F # Two-Particle Term D = ls.Tensor.chain( [self.tensors['Cocc'], C, self.tensors['Cvir']], [False, False, True]) if Sindex == 0: J = ls.IntBox.coulomb( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, D, self.options['thre_sp'], self.options['thre_dp'], ) J2 = ls.Tensor.chain( [self.tensors['Cocc'], J, self.tensors['Cvir']], [True, False, False]) K = ls.IntBox.exchange( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, D, False, self.options['thre_sp'], self.options['thre_dp'], ) K2 = ls.Tensor.chain( [self.tensors['Cocc'], K, self.tensors['Cvir']], [True, False, False]) if Sindex == 0: S.np[...] += 2. * J2.np - K2.np else: S.np[...] -= K2.np # Add vectors/diagonalize Davidson subspace Rs, Es = dav.add_vectors(Cs, Ss, use_disk=self.options['use_disk']) # Print Iteration Trace if self.print_level: print('%4d: %11.3E' % (iter, dav.max_rnorm)) # Check for convergence if dav.is_converged: converged = True break # Precondition the desired residuals for R, E in zip(Rs, Es): R.np[...] /= -(F.np[...] - E) # Get new trial vectors Cs = dav.add_preconditioned(Rs, use_disk=self.options['use_disk']) # => Output Quantities <= # # Print Convergence if self.print_level: print('') if converged: print('%s CIS Converged\n' % title) else: print('%s CIS Failed\n' % title) # Cache quantities self.converged[Sindex] = converged self.evals[Sindex] = [] self.evecs[Sindex] = [] # Make allowance for the reference state if Sindex == 0: self.evals[Sindex] += [self.scalars['Escf']] self.evecs[Sindex] += [None] self.evals[Sindex] += [x + self.scalars['Escf'] for x in dav.evals] self.evecs[Sindex] += [ ls.Storage.from_storage(x, Ss[0]) for x in dav.evecs ] # Print residuals if self.print_level: print('CIS %s Residuals:\n' % title) print('%4s: %11s' % ('I', '|R|')) for rind, r in enumerate(dav.rnorms): Soffset = 1 if Sindex == 0 else 0 # offset for the ground state print('%4d: %11.3E %s' % (rind + Soffset, r, 'Converged' if r < self.options['r_convergence'] else 'Not Converged')) print('') # Print energies if self.print_level: print('CIS %s Energies:\n' % title) print('%4s: %24s' % ('I', 'Total E')) for Eind, E in enumerate(self.evals[Sindex]): print('%4d: %24.16E' % (Eind, E)) print('') if self.print_level: print('=> End %s States <=\n' % title)
def compute_cphf( self, G, # A tensor which is nocc x nvir options={}, # Override options ): # => Override Options <= # options2 = options options = RHF.cphf_options.copy() for key, val in options2.items(): if not key in list(options.keys()): raise ValueError('Invalid user option: %s' % key) options[key] = val # Check that G has correct shape G.shape_error((self.sizes['nocc'], self.sizes['nvir'])) # => Header <= # #print '==> CPHF <==\n' # => DIIS <= # diis = ls.DIIS(options['diis_max_vecs'], ) #print diis # => Preconditioner <= # # Fock-Matrix Contribution (Preconditioner) F = ls.Tensor((self.sizes['nocc'], self.sizes['nvir']), 'F') F.np[...] += np.einsum('i,a->ia', np.ones(self.sizes['nocc']), self.tensors['eps_vir']) F.np[...] -= np.einsum('i,a->ia', self.tensors['eps_occ'], np.ones(self.sizes['nvir'])) # => Initial Guess (UCHF Result) <= # X = ls.Tensor.array(G) X.np[...] /= 4. * F.np[...] # ==> Iterations <== # converged = False #print 'CPHF Iterations:\n' #print '%4s: %11s' % ('Iter', 'dR') for iter in range(options['maxiter'] + 1): # => Compute the Residual <= # R = ls.Tensor.array(G) # One-electron term R.np[...] -= 4. * F.np[...] * X.np[...] # Two-electron terms D = ls.Tensor.chain( [self.tensors['Cocc'], X, self.tensors['Cvir']], [False, False, True]) D.symmetrize() I = ls.Tensor.zeros_like(D) I.np[...] += 16. * ls.IntBox.coulomb( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, D, options['thre_sp'], options['thre_dp'], ).np I.np[...] -= 8. * ls.IntBox.exchange( self.resources, ls.Ewald.coulomb(), self.pairlist, self.pairlist, D, True, options['thre_sp'], options['thre_dp'], ).np R.np[...] -= ls.Tensor.chain( [self.tensors['Cocc'], I, self.tensors['Cvir']], [True, False, False]) # => Precondition the Residual <= # R.np[...] /= 4. * F.np X.np[...] += R.np # Approximate NR-Step # => Check Convergence <= # Rmax = np.max(np.abs(R)) #print '%4d: %11.3E' % (iter, Rmax) if Rmax < options['convergence']: converged = True break # => Perform DIIS <= # X = diis.iterate(X, R, use_disk=options['diis_use_disk']) # => Print Convergence <= # #print '' if converged: #print 'CPHF Converged\n' pass else: #print 'CPHF Failed\n' return False # => Trailer <= # #print '==> End CPHF <==\n' return X
def localize( self, C, F, print_level=0, ): """ Localize orbitals. Params: C (ls.Tensor of shape (nao, ni)) - Orbitals to localize - these must live inside the span of Cref. F (ls.Tensor of shape, (ni, ni)) - Fock matrix in orbital basis. Used to sort localized orbitals to ascending orbital energies. print_level (int) - 0 - no printing, 1 - print iterative history and converence info. Returns: U (ls.Tensor of shape (ni, ni)) - Rotation from original orbitals (rows) to localized orbitals (cols). L (ls.Tensor of shape (nao, ni)) - Localized orbital coefficients. F (ls.Tensor of shape (ni, ni)) - Fock matrix in local orbital basis. converged (bool) - Did the localization procedure converge (True) or not (False). Definitions: L = C * U F2 = L' * F * L Localized orbitals are sorted to have ascending orbital energies. """ if print_level: print('==> IBO Localization <==\n') print('IBO Convergence Options:') print(' Power = %11d' % self.options['power']) print(' Maxiter = %11d' % self.options['maxiter']) print(' G Convergence = %11.3E' % self.options['g_convergence']) print('') L = ls.Tensor.chain([C,self.S,self.A],[True,False,False]) U = ls.Tensor((C.shape[1],)*2) ret = ls.Local.localize( self.options['power'], self.options['maxiter'], self.options['g_convergence'], self.minbasis, L, U, ) converged = ret[-1,1] < self.options['g_convergence'] if print_level: print('IBO %4s: %24s %11s %11s' % ('Iter', 'Metric', 'Delta', 'Gradient')) retnp = ret.np Eold = 0.0 for ind in range(ret.shape[0]): E = retnp[ind,0] G = retnp[ind,1] print('IBO %4d: %24.16E %11.3E %11.3E' % ( ind+1, E, E-Eold, G, )) Eold = E print('') if converged: print('IBO converged\n') else: print('IBO failed\n') # Energy ordering eps = np.diag(ls.Tensor.chain([U,F,U],[True,False,False])) U = ls.Tensor.array(U[:,np.argsort(eps)]) # Localized orbitals C2 = ls.Tensor.chain([C,U],[False,False]) # Localized Fock matrix F = ls.Tensor.chain([U,F,U],[True,False,False]) if print_level: print('==> End IBO Localization <==\n') return U, C2, F, converged