def localizeValence(mf, mo_coeff, method="iao"): if (method == "iao"): return iao.iao(mf.mol, mo_coeff) elif (method == "ibo"): a = iao.iao(mf.mol, mo_coeff) a = lo.vec_lowdin(a, mf.get_ovlp()) return ibo.ibo(mf.mol, mo_coeff, iaos=a) elif (method == "boys"): return boys.Boys(mf.mol).kernel(mo_coeff) elif (method == "er"): return edmiston.ER(mf.mol).kernel(mo_coeff)
def localizeAllElectron(mf, method="lowdin"): if (method == "lowdin"): return fractional_matrix_power(mf.get_ovlp(), -0.5).T elif (method == "pm"): return pipek.PM(mf.mol).kernel(mf.mo_coeff) elif (method == "boys"): return boys.Boys(mf.mol).kernel(mf.mo_coeff) elif (method == "er"): return edmiston.ER(mf.mol).kernel(mf.mo_coeff) elif (method == "iao"): return iao.iao(mf.mol, mf.mo_coeff) elif (method == "ibo"): a = iao.iao(mf.mol, mf.mo_coeff) a = lo.vec_lowdin(a, mf.get_ovlp()) return ibo.ibo(mf.mol, mf.mo_coeff, iaos=a)
def ibo(mol, orbocc, locmethod='IBO', iaos=None, s=None, exponent=4, grad_tol=1e-8, max_iter=200, verbose=logger.NOTE): '''Intrinsic Bonding Orbitals This function serves as a wrapper to the underlying localization functions ibo_loc and PipekMezey to create IBOs. Args: mol : the molecule or cell object orbocc : occupied molecular orbital coefficients Kwargs: locmethod : string the localization method 'PM' for Pipek Mezey localization or 'IBO' for the IBO localization iaos : 2D array the array of IAOs s : 2D array the overlap array in the ao basis Returns: IBOs in the basis defined in mol object. ''' if s is None: if getattr(mol, 'pbc_intor', None): # whether mol object is a cell if isinstance(orbocc, numpy.ndarray) and orbocc.ndim == 2: s = mol.pbc_intor('int1e_ovlp', hermi=1) else: raise NotImplementedError('k-points crystal orbitals') else: s = mol.intor_symmetric('int1e_ovlp') if iaos is None: iaos = iao.iao(mol, orbocc) locmethod = locmethod.strip().upper() if locmethod == 'PM': EXPONENT = getattr(__config__, 'lo_ibo_PipekMezey_exponent', exponent) ibos = PipekMezey(mol, orbocc, iaos, s, exponent=EXPONENT) del (EXPONENT) else: ibos = ibo_loc(mol, orbocc, iaos, s, exponent=exponent, grad_tol=grad_tol, max_iter=max_iter, verbose=verbose) return ibos
def test_fast_iao_mulliken_pop(self): mf = scf.RHF(mol).run() a = iao.iao(mol, mf.mo_coeff[:, mf.mo_occ > 0]) p, chg = iao.fast_iao_mullikan_pop(mol, mf.make_rdm1(), a) self.assertAlmostEqual(lib.finger(p), 0.56795867043723325, 5) mf = scf.UHF(mol).run() p, chg = iao.fast_iao_mullikan_pop(mol, mf.make_rdm1(), a) self.assertAlmostEqual(lib.finger(p[0] + p[1]), 0.56795867043723325, 5)
def test_fast_iao_mulliken_pop(self): mf = scf.RHF(mol).run() a = iao.iao(mol, mf.mo_coeff[:,mf.mo_occ>0]) p,chg = iao.fast_iao_mullikan_pop(mol, mf.make_rdm1(), a) self.assertAlmostEqual(lib.finger(p), 0.56812564587009806, 5) mf = scf.UHF(mol).run() p,chg = iao.fast_iao_mullikan_pop(mol, mf.make_rdm1(), a) self.assertAlmostEqual(lib.finger(p[0]+p[1]), 0.56812564587009806, 5)
def PipekMezey(mol, orbocc, iaos=None, s=None, exponent=EXPONENT): ''' Note this localization is slightly different to Knizia's implementation. The localization here reserves orthogonormality during optimization. Orbitals are projected to IAO basis first and the Mulliken pop is calculated based on IAO basis (in function atomic_pops). A series of unitary matrices are generated and applied on the input orbitals. The intemdiate orbitals in the optimization and the finally localized orbitals are all orthogonormal. Examples: >>> from pyscf import gto, scf >>> from pyscf.lo import ibo >>> mol = gto.M(atom='H 0 0 0; F 0 0 1', >>> basis='unc-sto3g') >>> mf = scf.RHF(mol).run() >>> pm = ibo.PM(mol, mf.mo_coeff[:,mf.mo_occ>0]) >>> loc_orb = pm.kernel() ''' if hasattr(mol, 'pbc_intor'): # whether mol object is a cell if isinstance(orbocc, numpy.ndarray) and orbocc.ndim == 2: s = mol.pbc_intor('int1e_ovlp', hermi=1) else: raise NotImplementedError('k-points crystal orbitals') else: s = mol.intor_symmetric('int1e_ovlp') if iaos is None: iaos = iao.iao(mol, orbocc) # Different to Knizia's code, the reference IAOs are not neccessary # orthogonal. #iaos = orth.vec_lowdin(iaos, s) cs = numpy.dot(iaos.T.conj(), s) s_iao = numpy.dot(cs, iaos) iao_inv = numpy.linalg.solve(s_iao, cs) iao_mol = iao.reference_mol(mol) # Define the mulliken population of each atom based on IAO basis. # proj[i].trace is the mulliken population of atom i. def atomic_pops(mol, mo_coeff, method=None): nmo = mo_coeff.shape[1] proj = numpy.empty((mol.natm, nmo, nmo)) orb_in_iao = reduce(numpy.dot, (iao_inv, mo_coeff)) for i, (b0, b1, p0, p1) in enumerate(iao_mol.offset_nr_by_atom()): csc = reduce(numpy.dot, (orb_in_iao[p0:p1].T, s_iao[p0:p1], orb_in_iao)) proj[i] = (csc + csc.T) * .5 return proj pm = pipek.PM(mol, orbocc) pm.atomic_pops = atomic_pops pm.exponent = exponent return pm
def vvo(mol, orbocc, orbvirt, iaos=None, s=None, verbose=logger.NOTE): '''Valence Virtual Orbitals ref. 10.1021/acs.jctc.7b00493 Valence virtual orbitals can be formed from the singular value decomposition of the overlap between the canonical molecular orbitals and an accurate underlying atomic basis set. This implementation uses the intrinsic atomic orbital as this underlying set. VVOs can also be formed from the null space of the overlap of the canonical molecular orbitals and the underlying atomic basis sets (IAOs). This is not implemented here. Args: mol : the molecule or cell object orbocc : occupied molecular orbital coefficients orbvirt : virtual molecular orbital coefficients Kwargs: iaos : 2D array the array of IAOs s : 2D array the overlap array in the ao basis Returns: VVOs in the basis defined in mol object. ''' log = logger.new_logger(mol, verbose) if s is None: if getattr(mol, 'pbc_intor', None): # whether mol object is a cell if isinstance(orbocc, numpy.ndarray) and orbocc.ndim == 2: s = mol.pbc_intor('int1e_ovlp', hermi=1) else: raise NotImplementedError('k-points crystal orbitals') else: s = mol.intor_symmetric('int1e_ovlp') if iaos is None: iaos = iao.iao(mol, orbocc) nvvo = iaos.shape[1] - orbocc.shape[1] # Symmetrically orthogonalization of the IAO orbitals as Knizia's # implementation. The IAO returned by iao.iao function is not orthogonal. iaos = orth.vec_lowdin(iaos, s) #S = reduce(np.dot, (orbvirt.T, s, iaos)) S = numpy.einsum('ji,jk,kl->il', orbvirt, s, iaos, optimize=True) U, sigma, Vh = scipy.linalg.svd(S) U = U[:, 0:nvvo] vvo = numpy.einsum('ik,ji->jk', U, orbvirt, optimize=True) return vvo
def PipekMezey(mol, orbocc, iaos=None, s=None, exponent=EXPONENT): ''' Note this localization is slightly different to Knizia's implementation. The localization here reserves orthogonormality during optimization. Orbitals are projected to IAO basis first and the Mulliken pop is calculated based on IAO basis (in function atomic_pops). A series of unitary matrices are generated and applied on the input orbitals. The intemdiate orbitals in the optimization and the finally localized orbitals are all orthogonormal. Examples: >>> from pyscf import gto, scf >>> from pyscf.lo import ibo >>> mol = gto.M(atom='H 0 0 0; F 0 0 1', >>> basis='unc-sto3g') >>> mf = scf.RHF(mol).run() >>> pm = ibo.PM(mol, mf.mo_coeff[:,mf.mo_occ>0]) >>> loc_orb = pm.kernel() ''' if getattr(mol, 'pbc_intor', None): # whether mol object is a cell if isinstance(orbocc, numpy.ndarray) and orbocc.ndim == 2: s = mol.pbc_intor('int1e_ovlp', hermi=1) else: raise NotImplementedError('k-points crystal orbitals') else: s = mol.intor_symmetric('int1e_ovlp') if iaos is None: iaos = iao.iao(mol, orbocc) # Different to Knizia's code, the reference IAOs are not neccessary # orthogonal. #iaos = orth.vec_lowdin(iaos, s) cs = numpy.dot(iaos.T.conj(), s) s_iao = numpy.dot(cs, iaos) iao_inv = numpy.linalg.solve(s_iao, cs) iao_mol = iao.reference_mol(mol) # Define the mulliken population of each atom based on IAO basis. # proj[i].trace is the mulliken population of atom i. def atomic_pops(mol, mo_coeff, method=None): nmo = mo_coeff.shape[1] proj = numpy.empty((mol.natm,nmo,nmo)) orb_in_iao = reduce(numpy.dot, (iao_inv, mo_coeff)) for i, (b0, b1, p0, p1) in enumerate(iao_mol.offset_nr_by_atom()): csc = reduce(numpy.dot, (orb_in_iao[p0:p1].T, s_iao[p0:p1], orb_in_iao)) proj[i] = (csc + csc.T) * .5 return proj pm = pipek.PM(mol, orbocc) pm.atomic_pops = atomic_pops pm.exponent = exponent return pm
def kernel(mf, aolabels, threshold=THRESHOLD, minao=MINAO, with_iao=WITH_IAO, openshell_option=OPENSHELL_OPTION, canonicalize=CANONICALIZE, ncore=0, verbose=None): '''AVAS method to construct mcscf active space. Ref. arXiv:1701.07862 [physics.chem-ph] Args: mf : an :class:`SCF` object aolabels : string or a list of strings AO labels for AO active space Kwargs: threshold : float Tructing threshold of the AO-projector above which AOs are kept in the active space. minao : str A reference AOs for AVAS. with_iao : bool Whether to use IAO localization to construct the reference active AOs. openshell_option : int How to handle singly-occupied orbitals in the active space. The singly-occupied orbitals are projected as part of alpha orbitals if openshell_option=2, or completely kept in active space if openshell_option=3. See Section III.E option 2 or 3 of the reference paper for more details. canonicalize : bool Orbitals defined in AVAS method are local orbitals. Symmetrizing the core, active and virtual space. ncore : integer Number of core orbitals to exclude from the AVAS method. Returns: active-space-size, #-active-electrons, orbital-initial-guess-for-CASCI/CASSCF Examples: >>> from pyscf import gto, scf, mcscf >>> from pyscf.mcscf import avas >>> mol = gto.M(atom='Cr 0 0 0; Cr 0 0 1.6', basis='ccpvtz') >>> mf = scf.RHF(mol).run() >>> ncas, nelecas, mo = avas.avas(mf, ['Cr 3d', 'Cr 4s']) >>> mc = mcscf.CASSCF(mf, ncas, nelecas).run(mo) ''' from pyscf.tools import mo_mapping if isinstance(verbose, logger.Logger): log = verbose elif verbose is not None: log = logger.Logger(mf.stdout, verbose) else: log = logger.Logger(mf.stdout, mf.verbose) mol = mf.mol log.info('\n** AVAS **') if isinstance(mf, scf.uhf.UHF): log.note('UHF/UKS object is found. AVAS takes alpha orbitals only') mo_coeff = mf.mo_coeff[0] mo_occ = mf.mo_occ[0] mo_energy = mf.mo_energy[0] assert (openshell_option != 1) else: mo_coeff = mf.mo_coeff mo_occ = mf.mo_occ mo_energy = mf.mo_energy nocc = numpy.count_nonzero(mo_occ != 0) ovlp = mol.intor_symmetric('int1e_ovlp') log.info(' Total number of HF MOs is equal to %d', mo_coeff.shape[1]) log.info(' Number of occupied HF MOs is equal to %d', nocc) mol = mf.mol pmol = mol.copy() pmol.atom = mol._atom pmol.unit = 'B' pmol.symmetry = False pmol.basis = minao pmol.build(False, False) baslst = pmol.search_ao_label(aolabels) log.info('reference AO indices for %s %s: %s', minao, aolabels, baslst) if with_iao: from pyscf.lo import iao c = iao.iao(mol, mo_coeff[:, ncore:nocc], minao)[:, baslst] s2 = reduce(numpy.dot, (c.T, ovlp, c)) s21 = reduce(numpy.dot, (c.T, ovlp, mo_coeff[:, ncore:])) else: s2 = pmol.intor_symmetric('int1e_ovlp')[baslst][:, baslst] s21 = gto.intor_cross('int1e_ovlp', pmol, mol)[baslst] s21 = numpy.dot(s21, mo_coeff[:, ncore:]) sa = s21.T.dot(scipy.linalg.solve(s2, s21, sym_pos=True)) from pyscf.symm import label_orb_symm symm = label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mo_coeff, tol=1e-5) if openshell_option == 2: wocc, u = numpy.linalg.eigh(sa[:(nocc - ncore), :(nocc - ncore)]) log.info('Option 2: threshold %s', threshold) ncas_occ = (wocc > threshold).sum() nelecas = (mol.nelectron - ncore * 2) - (wocc < threshold).sum() * 2 if ncore > 0: mofreeze = mo_coeff[:, :ncore] mocore = mo_coeff[:, ncore:nocc].dot(u[:, wocc < threshold]) mocas = mo_coeff[:, ncore:nocc].dot(u[:, wocc > threshold]) mask = (wocc > threshold).tolist() #print nocc-ncore,mocore.shape,wocc<threshold #print nocc-ncore,mocas.shape,wocc>threshold #print 'BM',mask,len(mask) wvir, u = numpy.linalg.eigh(sa[(nocc - ncore):, (nocc - ncore):]) ncas_vir = (wvir > threshold).sum() mocas = numpy.hstack( (mocas, mo_coeff[:, nocc:].dot(u[:, wvir > threshold]))) movir = mo_coeff[:, nocc:].dot(u[:, wvir < threshold]) ncas = mocas.shape[1] mask += (wvir > threshold).tolist() #print mo_coeff.shape[0]-nocc,mocas.shape,wvir>threshold #print mo_coeff.shape[0]-nocc,movir.shape,wvir<threshold #print 'BM',mask,len(mask) elif openshell_option == 3: docc = nocc - mol.spin wocc, u = numpy.linalg.eigh(sa[:(docc - ncore), :(docc - ncore)]) log.info('Option 3: threshold %s, num open shell %d', threshold, mol.spin) ncas_occ = (wocc > threshold).sum() nelecas = (mol.nelectron - ncore * 2) - (wocc < threshold).sum() * 2 if ncore > 0: mofreeze = mo_coeff[:, :ncore] mocore = mo_coeff[:, ncore:docc].dot(u[:, wocc < threshold]) mocas = mo_coeff[:, ncore:docc].dot(u[:, wocc > threshold]) wvir, u = numpy.linalg.eigh(sa[(nocc - ncore):, (nocc - ncore):]) ncas_vir = (wvir > threshold).sum() mocas = numpy.hstack((mocas, mo_coeff[:, docc:nocc], mo_coeff[:, nocc:].dot(u[:, wvir > threshold]))) movir = mo_coeff[:, nocc:].dot(u[:, wvir < threshold]) ncas = mocas.shape[1] log.debug('projected occ eig %s', wocc[::-1]) log.debug('projected vir eig %s', wvir[::-1]) log.info('Active from occupied = %d , eig %s', ncas_occ, wocc[wocc > threshold][::-1]) log.info('Inactive from occupied = %d', mocore.shape[1]) log.info('Active from unoccupied = %d , eig %s', ncas_vir, wvir[wvir > threshold][::-1]) log.info('Inactive from unoccupied = %d', movir.shape[1]) log.info('Dimensions of active %d', ncas) nalpha = (nelecas + mol.spin) // 2 nbeta = nelecas - nalpha log.info('# of alpha electrons %d', nalpha) log.info('# of beta electrons %d', nbeta) selected = [i for i in range(len(mask)) if mask[i]] list_of_sym = [symm[i] for i in selected] print selected print list_of_sym for elt in set(symm): n = 0 for i in list_of_sym: if i == elt: n += 1 print elt, n if canonicalize: from pyscf.mcscf import dmet_cas def trans(c): if c.shape[1] == 0: return c else: csc = reduce(numpy.dot, (c.T, ovlp, mo_coeff)) fock = numpy.dot(csc * mo_energy, csc.T) e, u = scipy.linalg.eigh(fock) return dmet_cas.symmetrize(mol, e, numpy.dot(c, u), ovlp, log) if ncore > 0: mo = numpy.hstack( [trans(mofreeze), trans(mocore), trans(mocas), trans(movir)]) else: mo = numpy.hstack([trans(mocore), trans(mocas), trans(movir)]) else: mo = numpy.hstack((mocore, mocas, movir)) return ncas, nelecas, mo
def atomic_pops(mol, mo_coeff, method='meta_lowdin', mf=None): ''' Kwargs: method : string The atomic population projection scheme. It can be mulliken, lowdin, meta_lowdin, iao, or becke Returns: A 3-index tensor [A,i,j] indicates the population of any orbital-pair density |i><j| for each species (atom in this case). This tensor is used to construct the population and gradients etc. You can customize the PM localization wrt other population metric, such as the charge of a site, the charge of a fragment (a group of atoms) by overwriting this tensor. See also the example pyscf/examples/loc_orb/40-hubbard_model_PM_localization.py for the PM localization of site-based population for hubbard model. ''' method = method.lower().replace('_', '-') nmo = mo_coeff.shape[1] proj = numpy.empty((mol.natm,nmo,nmo)) if getattr(mol, 'pbc_intor', None): # whether mol object is a cell s = mol.pbc_intor('int1e_ovlp_sph', hermi=1) else: s = mol.intor_symmetric('int1e_ovlp') if method == 'becke': from pyscf.dft import gen_grid if not (getattr(mf, 'grids', None) and getattr(mf, '_numint', None)): # Call DFT to initialize grids and numint objects mf = mol.RKS() grids = mf.grids ni = mf._numint if not isinstance(grids, gen_grid.Grids): raise NotImplementedError('PM becke scheme for PBC systems') # The atom-wise Becke grids (without concatenated to a vector of grids) coords, weights = grids.get_partition(mol, concat=False) for i in range(mol.natm): ao = ni.eval_ao(mol, coords[i], deriv=0) aow = numpy.einsum('pi,p->pi', ao, weights[i]) charge_matrix = lib.dot(aow.conj().T, ao) proj[i] = reduce(lib.dot, (mo_coeff.conj().T, charge_matrix, mo_coeff)) elif method == 'mulliken': for i, (b0, b1, p0, p1) in enumerate(mol.offset_nr_by_atom()): csc = reduce(numpy.dot, (mo_coeff[p0:p1].conj().T, s[p0:p1], mo_coeff)) proj[i] = (csc + csc.conj().T) * .5 elif method in ('lowdin', 'meta-lowdin'): csc = reduce(lib.dot, (mo_coeff.conj().T, s, orth.orth_ao(mol, method, 'ANO', s=s))) for i, (b0, b1, p0, p1) in enumerate(mol.offset_nr_by_atom()): proj[i] = numpy.dot(csc[:,p0:p1], csc[:,p0:p1].conj().T) elif method in ('iao', 'ibo'): from pyscf.lo import iao assert mf is not None # FIXME: How to handle UHF/UKS object? orb_occ = mf.mo_coeff[:,mf.mo_occ>0] iao_coeff = iao.iao(mol, orb_occ) # # IAO is generally not orthogonalized. For simplicity, we take Lowdin # orthogonalization here. Other orthogonalization can be used. Results # should be very closed to the Lowdin-orth orbitals # # PM with Mulliken population of non-orth IAOs can be found in # ibo.PipekMezey function # iao_coeff = orth.vec_lowdin(iao_coeff, s) csc = reduce(lib.dot, (mo_coeff.conj().T, s, iao_coeff)) iao_mol = iao.reference_mol(mol) for i, (b0, b1, p0, p1) in enumerate(iao_mol.offset_nr_by_atom()): proj[i] = numpy.dot(csc[:,p0:p1], csc[:,p0:p1].conj().T) else: raise KeyError('method = %s' % method) return proj
mol.basis = 'aug-cc-pvtz' mol.symmetry = 1 mol.build() mf = dft.RKS(mol) mf.xc = 'HF*0.2 + .08*LDA + .72*B88, .81*LYP + .19*VWN5' mf.kernel() orbocc = mf.mo_coeff[:,0:mol.nelec[0]] orbvirt = mf.mo_coeff[:,mol.nelec[0]:] mocoeff = mf.mo_coeff ovlpS = mol.intor_symmetric('int1e_ovlp') # plot canonical mos iaos = iao.iao(mol, orbocc) iaos = orth.vec_lowdin(iaos, ovlpS) for i in range(iaos.shape[1]): tools.cubegen.orbital(mol, 'h2o_cmo_{:02d}.cube'.format(i+1), mocoeff[:,i]) # plot intrinsic atomic orbitals for i in range(iaos.shape[1]): tools.cubegen.orbital(mol, 'h2o_iao_{:02d}.cube'.format(i+1), iaos[:,i]) # plot intrinsic bonding orbitals count = 0 ibos = lo.ibo.ibo(mol, orbocc, locmethod='IBO') for i in range(ibos.shape[1]): count += 1 tools.cubegen.orbital(mol, 'h2o_ibo_{:02d}.cube'.format(count), ibos[:,i])
def kernel(mf, aolabels, threshold=THRESHOLD, minao=MINAO, with_iao=WITH_IAO, openshell_option=OPENSHELL_OPTION, canonicalize=CANONICALIZE, ncore=0, verbose=None): '''AVAS method to construct mcscf active space. Ref. arXiv:1701.07862 [physics.chem-ph] Args: mf : an :class:`SCF` object aolabels : string or a list of strings AO labels for AO active space Kwargs: threshold : float Tructing threshold of the AO-projector above which AOs are kept in the active space. minao : str A reference AOs for AVAS. with_iao : bool Whether to use IAO localization to construct the reference active AOs. openshell_option : int How to handle singly-occupied orbitals in the active space. The singly-occupied orbitals are projected as part of alpha orbitals if openshell_option=2, or completely kept in active space if openshell_option=3. See Section III.E option 2 or 3 of the reference paper for more details. canonicalize : bool Orbitals defined in AVAS method are local orbitals. Symmetrizing the core, active and virtual space. ncore : integer Number of core orbitals to exclude from the AVAS method. Returns: active-space-size, #-active-electrons, orbital-initial-guess-for-CASCI/CASSCF Examples: >>> from pyscf import gto, scf, mcscf >>> from pyscf.mcscf import avas >>> mol = gto.M(atom='Cr 0 0 0; Cr 0 0 1.6', basis='ccpvtz') >>> mf = scf.RHF(mol).run() >>> ncas, nelecas, mo = avas.avas(mf, ['Cr 3d', 'Cr 4s']) >>> mc = mcscf.CASSCF(mf, ncas, nelecas).run(mo) ''' from pyscf.tools import mo_mapping if isinstance(verbose, logger.Logger): log = verbose elif verbose is not None: log = logger.Logger(mf.stdout, verbose) else: log = logger.Logger(mf.stdout, mf.verbose) mol = mf.mol log.info('\n** AVAS **') if isinstance(mf, scf.uhf.UHF): log.note('UHF/UKS object is found. AVAS takes alpha orbitals only') mo_coeff = mf.mo_coeff[0] mo_occ = mf.mo_occ[0] mo_energy = mf.mo_energy[0] assert(openshell_option != 1) else: mo_coeff = mf.mo_coeff mo_occ = mf.mo_occ mo_energy = mf.mo_energy nocc = numpy.count_nonzero(mo_occ != 0) ovlp = mol.intor_symmetric('int1e_ovlp') log.info(' Total number of HF MOs is equal to %d' ,mo_coeff.shape[1]) log.info(' Number of occupied HF MOs is equal to %d', nocc) mol = mf.mol pmol = mol.copy() pmol.atom = mol._atom pmol.unit = 'B' pmol.symmetry = False pmol.basis = minao pmol.build(False, False) baslst = pmol.search_ao_label(aolabels) log.info('reference AO indices for %s %s: %s', minao, aolabels, baslst) if with_iao: from pyscf.lo import iao c = iao.iao(mol, mo_coeff[:,ncore:nocc], minao)[:,baslst] s2 = reduce(numpy.dot, (c.T, ovlp, c)) s21 = reduce(numpy.dot, (c.T, ovlp, mo_coeff[:, ncore:])) else: s2 = pmol.intor_symmetric('int1e_ovlp')[baslst][:,baslst] s21 = gto.intor_cross('int1e_ovlp', pmol, mol)[baslst] s21 = numpy.dot(s21, mo_coeff[:, ncore:]) sa = s21.T.dot(scipy.linalg.solve(s2, s21, sym_pos=True)) if openshell_option == 2: wocc, u = numpy.linalg.eigh(sa[:(nocc-ncore), :(nocc-ncore)]) log.info('Option 2: threshold %s', threshold) ncas_occ = (wocc > threshold).sum() nelecas = (mol.nelectron - ncore * 2) - (wocc < threshold).sum() * 2 if ncore > 0: mofreeze = mo_coeff[:,:ncore] mocore = mo_coeff[:,ncore:nocc].dot(u[:,wocc<threshold]) mocas = mo_coeff[:,ncore:nocc].dot(u[:,wocc>threshold]) wvir, u = numpy.linalg.eigh(sa[(nocc-ncore):,(nocc-ncore):]) ncas_vir = (wvir > threshold).sum() mocas = numpy.hstack((mocas, mo_coeff[:,nocc:].dot(u[:,wvir>threshold]))) movir = mo_coeff[:,nocc:].dot(u[:,wvir<threshold]) ncas = mocas.shape[1] elif openshell_option == 3: docc = nocc - mol.spin wocc, u = numpy.linalg.eigh(sa[:(docc-ncore),:(docc-ncore)]) log.info('Option 3: threshold %s, num open shell %d', threshold, mol.spin) ncas_occ = (wocc > threshold).sum() nelecas = (mol.nelectron - ncore * 2) - (wocc < threshold).sum() * 2 if ncore > 0: mofreeze = mo_coeff[:,:ncore] mocore = mo_coeff[:,ncore:docc].dot(u[:,wocc<threshold]) mocas = mo_coeff[:,ncore:docc].dot(u[:,wocc>threshold]) wvir, u = numpy.linalg.eigh(sa[(nocc-ncore):,(nocc-ncore):]) ncas_vir = (wvir > threshold).sum() mocas = numpy.hstack((mocas, mo_coeff[:,docc:nocc], mo_coeff[:,nocc:].dot(u[:,wvir>threshold]))) movir = mo_coeff[:,nocc:].dot(u[:,wvir<threshold]) ncas = mocas.shape[1] log.debug('projected occ eig %s', wocc[::-1]) log.debug('projected vir eig %s', wvir[::-1]) log.info('Active from occupied = %d , eig %s', ncas_occ, wocc[wocc>threshold][::-1]) log.info('Inactive from occupied = %d', mocore.shape[1]) log.info('Active from unoccupied = %d , eig %s', ncas_vir, wvir[wvir>threshold][::-1]) log.info('Inactive from unoccupied = %d', movir.shape[1]) log.info('Dimensions of active %d', ncas) nalpha = (nelecas + mol.spin) // 2 nbeta = nelecas - nalpha log.info('# of alpha electrons %d', nalpha) log.info('# of beta electrons %d', nbeta) if canonicalize: from pyscf.mcscf import dmet_cas def trans(c): if c.shape[1] == 0: return c else: csc = reduce(numpy.dot, (c.T, ovlp, mo_coeff)) fock = numpy.dot(csc*mo_energy, csc.T) e, u = scipy.linalg.eigh(fock) return dmet_cas.symmetrize(mol, e, numpy.dot(c, u), ovlp, log) if ncore > 0: mo = numpy.hstack([trans(mofreeze), trans(mocore), trans(mocas), trans(movir)]) else: mo = numpy.hstack([trans(mocore), trans(mocas), trans(movir)]) else: mo = numpy.hstack((mocore, mocas, movir)) return ncas, nelecas, mo
def livvo(mol, orbocc, orbvirt, locmethod='IBO', iaos=None, s=None, exponent=4, grad_tol=1e-8, max_iter=200, verbose=logger.NOTE): '''Localized Intrinsic Valence Virtual Orbitals ref. 10.1021/acs.jctc.7b00493 Localized Intrinsic valence virtual orbitals are formed when the valence virtual orbitals are localized using an IBO-type of localization. Here the VVOs are created in the IAO basis then the IBO localization functions are called to localize the VVOs. Args: mol : the molecule or cell object orbocc : occupied molecular orbital coefficients orbvirt : virtual molecular orbital coefficients Kwargs: locmethod : string the localization method 'PM' for Pipek Mezey localization or 'IBO' for the IBO localization iaos : 2D array the array of IAOs s : 2D array the overlap array in the ao basis Returns: LIVVOs in the basis defined in mol object. ''' if s is None: if getattr(mol, 'pbc_intor', None): # whether mol object is a cell if isinstance(orbocc, numpy.ndarray) and orbocc.ndim == 2: s = mol.pbc_intor('int1e_ovlp', hermi=1) else: raise NotImplementedError('k-points crystal orbitals') else: s = mol.intor_symmetric('int1e_ovlp') if iaos is None: iaos = iao.iao(mol, orbocc) vvos = vvo(mol, orbocc, orbvirt, iaos=iaos, s=s) locmethod = locmethod.strip().upper() if locmethod == 'PM': EXPONENT = getattr(__config__, 'lo_ibo_PipekMezey_exponent', exponent) livvos = ibo.PipekMezey(mol, vvos, iaos, s, exponent=EXPONENT) del (EXPONENT) else: livvos = ibo.ibo_loc(mol, vvos, iaos, s, exponent=exponent, grad_tol=grad_tol, max_iter=max_iter, verbose=verbose) return livvos
def ibo(mol, orbocc, iaos=None, exponent=4, grad_tol=1e-8, max_iter=200, verbose=logger.NOTE): '''Intrinsic Bonding Orbitals. [Ref. JCTC, 9, 4834] This implementation follows Knizia's implementation execept that the resultant IBOs are symmetrically orthogonalized. Note the IBOs of this implementation do not strictly maximize the IAO Mulliken charges. IBOs can also be generated by another implementation (see function pyscf.lo.ibo.PM). In that function, PySCF builtin Pipek-Mezey localization module was used to maximize the IAO Mulliken charges. Args: mol : the molecule or cell object orbocc : 2D array or a list of 2D array occupied molecular orbitals or crystal orbitals for each k-point Kwargs: iaos : 2D array the array of IAOs exponent : integer Localization power in PM scheme grad_tol : float convergence tolerance for norm of gradients Returns: IBOs in the big basis (the basis defined in mol object). ''' log = logger.new_logger(mol, verbose) assert (exponent in (2, 4)) if hasattr(mol, 'pbc_intor'): # whether mol object is a cell if isinstance(orbocc, numpy.ndarray) and orbocc.ndim == 2: ovlpS = mol.pbc_intor('int1e_ovlp', hermi=1) else: raise NotImplementedError('k-points crystal orbitals') else: ovlpS = mol.intor_symmetric('int1e_ovlp') if iaos is None: iaos = iao.iao(mol, orbocc) # Symmetrically orthogonalization of the IAO orbitals as Knizia's # implementation. The IAO returned by iao.iao function is not orthogonal. iaos = orth.vec_lowdin(iaos, ovlpS) #static variables StartTime = time() L = 0 # initialize a value of the localization function for safety #max_iter = 20000 #for some reason the convergence of solid is slower #fGradConv = 1e-10 #this ought to be pumped up to about 1e-8 but for testing purposes it's fine swapGradTolerance = 1e-12 #dynamic variables Converged = False Atoms = [mol.atom_symbol(i) for i in range(mol.natm)] #generates the parameters we need about the atomic structure nAtoms = len(Atoms) AtomOffsets = MakeAtomIbOffsets(Atoms)[0] iAtSl = [slice(AtomOffsets[A], AtomOffsets[A + 1]) for A in range(nAtoms)] #converts the occupied MOs to the IAO basis CIb = reduce(numpy.dot, (iaos.T, ovlpS, orbocc)) numOccOrbitals = CIb.shape[1] log.debug(" {0:^5s} {1:^14s} {2:^11s} {3:^8s}".format( "ITER.", "LOC(Orbital)", "GRADIENT", "TIME")) for it in range(max_iter): fGrad = 0.00 #calculate L for convergence checking L = 0. for A in range(nAtoms): for i in range(numOccOrbitals): CAi = CIb[iAtSl[A], i] L += numpy.dot(CAi, CAi)**exponent # loop over the occupied orbitals pairs i,j for i in range(numOccOrbitals): for j in range(i): # I eperimented with exponentially falling off random noise Aij = 0.0 #numpy.random.random() * numpy.exp(-1*it) Bij = 0.0 #numpy.random.random() * numpy.exp(-1*it) for k in range(nAtoms): CIbA = CIb[iAtSl[k], :] Cii = numpy.dot(CIbA[:, i], CIbA[:, i]) Cij = numpy.dot(CIbA[:, i], CIbA[:, j]) Cjj = numpy.dot(CIbA[:, j], CIbA[:, j]) #now I calculate Aij and Bij for the gradient search if exponent == 2: Aij += 4. * Cij**2 - (Cii - Cjj)**2 Bij += 4. * Cij * (Cii - Cjj) else: Bij += 4. * Cij * (Cii**3 - Cjj**3) Aij += -Cii**4 - Cjj**4 + 6 * ( Cii**2 + Cjj**2) * Cij**2 + Cii**3 * Cjj + Cii * Cjj**3 if (Aij**2 + Bij**2 < swapGradTolerance) and False: continue #this saves us from replacing already fine orbitals else: #THE BELOW IS TAKEN DIRECLTY FROMG KNIZIA's FREE CODE # Calculate 2x2 rotation angle phi. # This correspond to [2] (12)-(15), re-arranged and simplified. phi = .25 * numpy.arctan2(Bij, -Aij) fGrad += Bij**2 # ^- Bij is the actual gradient. Aij is effectively # the second derivative at phi=0. # 2x2 rotation form; that's what PM suggest. it works # fine, but I don't like the asymmetry. cs = numpy.cos(phi) ss = numpy.sin(phi) Ci = 1. * CIb[:, i] Cj = 1. * CIb[:, j] CIb[:, i] = cs * Ci + ss * Cj CIb[:, j] = -ss * Ci + cs * Cj fGrad = fGrad**.5 log.debug(" {0:5d} {1:12.8f} {2:11.2e} {3:8.2f}".format( it + 1, L**(1. / exponent), fGrad, time() - StartTime)) if fGrad < grad_tol: Converged = True break Note = "IB/P%i/2x2, %i iter; Final gradient %.2e" % (exponent, it + 1, fGrad) if not Converged: log.note( "\nWARNING: Iterative localization failed to converge!" "\n %s", Note) else: log.note(" Iterative localization: %s", Note) log.debug( " Localized orbitals deviation from orthogonality: %8.2e", numpy.linalg.norm(numpy.dot(CIb.T, CIb) - numpy.eye(numOccOrbitals))) # Note CIb is not unitary matrix (although very close to unitary matrix) # because the projection <IAO|OccOrb> does not give unitary matrix. return numpy.dot(iaos, (orth.vec_lowdin(CIb)))
def ibo(mol, orbocc, iaos=None, exponent=4, grad_tol=1e-8, max_iter=200, verbose=logger.NOTE): '''Intrinsic Bonding Orbitals. [Ref. JCTC, 9, 4834] This implementation follows Knizia's implementation execept that the resultant IBOs are symmetrically orthogonalized. Note the IBOs of this implementation do not strictly maximize the IAO Mulliken charges. IBOs can also be generated by another implementation (see function pyscf.lo.ibo.PM). In that function, PySCF builtin Pipek-Mezey localization module was used to maximize the IAO Mulliken charges. Args: mol : the molecule or cell object orbocc : 2D array or a list of 2D array occupied molecular orbitals or crystal orbitals for each k-point Kwargs: iaos : 2D array the array of IAOs exponent : integer Localization power in PM scheme grad_tol : float convergence tolerance for norm of gradients Returns: IBOs in the big basis (the basis defined in mol object). ''' log = logger.new_logger(mol, verbose) assert(exponent in (2, 4)) if getattr(mol, 'pbc_intor', None): # whether mol object is a cell if isinstance(orbocc, numpy.ndarray) and orbocc.ndim == 2: ovlpS = mol.pbc_intor('int1e_ovlp', hermi=1) else: raise NotImplementedError('k-points crystal orbitals') else: ovlpS = mol.intor_symmetric('int1e_ovlp') if iaos is None: iaos = iao.iao(mol, orbocc) # Symmetrically orthogonalization of the IAO orbitals as Knizia's # implementation. The IAO returned by iao.iao function is not orthogonal. iaos = orth.vec_lowdin(iaos, ovlpS) #static variables StartTime = time() L = 0 # initialize a value of the localization function for safety #max_iter = 20000 #for some reason the convergence of solid is slower #fGradConv = 1e-10 #this ought to be pumped up to about 1e-8 but for testing purposes it's fine swapGradTolerance = 1e-12 #dynamic variables Converged = False Atoms = [mol.atom_symbol(i) for i in range(mol.natm)] #generates the parameters we need about the atomic structure nAtoms = len(Atoms) AtomOffsets = MakeAtomIbOffsets(Atoms)[0] iAtSl = [slice(AtomOffsets[A],AtomOffsets[A+1]) for A in range(nAtoms)] #converts the occupied MOs to the IAO basis CIb = reduce(numpy.dot, (iaos.T, ovlpS , orbocc)) numOccOrbitals = CIb.shape[1] log.debug(" {0:^5s} {1:^14s} {2:^11s} {3:^8s}" .format("ITER.","LOC(Orbital)","GRADIENT", "TIME")) for it in range(max_iter): fGrad = 0.00 #calculate L for convergence checking L = 0. for A in range(nAtoms): for i in range(numOccOrbitals): CAi = CIb[iAtSl[A],i] L += numpy.dot(CAi,CAi)**exponent # loop over the occupied orbitals pairs i,j for i in range(numOccOrbitals): for j in range(i): # I eperimented with exponentially falling off random noise Aij = 0.0 #numpy.random.random() * numpy.exp(-1*it) Bij = 0.0 #numpy.random.random() * numpy.exp(-1*it) for k in range(nAtoms): CIbA = CIb[iAtSl[k],:] Cii = numpy.dot(CIbA[:,i], CIbA[:,i]) Cij = numpy.dot(CIbA[:,i], CIbA[:,j]) Cjj = numpy.dot(CIbA[:,j], CIbA[:,j]) #now I calculate Aij and Bij for the gradient search if exponent == 2: Aij += 4.*Cij**2 - (Cii - Cjj)**2 Bij += 4.*Cij*(Cii - Cjj) else: Bij += 4.*Cij*(Cii**3-Cjj**3) Aij += -Cii**4 - Cjj**4 + 6*(Cii**2 + Cjj**2)*Cij**2 + Cii**3 * Cjj + Cii*Cjj**3 if (Aij**2 + Bij**2 < swapGradTolerance) and False: continue #this saves us from replacing already fine orbitals else: #THE BELOW IS TAKEN DIRECLTY FROMG KNIZIA's FREE CODE # Calculate 2x2 rotation angle phi. # This correspond to [2] (12)-(15), re-arranged and simplified. phi = .25*numpy.arctan2(Bij,-Aij) fGrad += Bij**2 # ^- Bij is the actual gradient. Aij is effectively # the second derivative at phi=0. # 2x2 rotation form; that's what PM suggest. it works # fine, but I don't like the asymmetry. cs = numpy.cos(phi) ss = numpy.sin(phi) Ci = 1. * CIb[:,i] Cj = 1. * CIb[:,j] CIb[:,i] = cs * Ci + ss * Cj CIb[:,j] = -ss * Ci + cs * Cj fGrad = fGrad**.5 log.debug(" {0:5d} {1:12.8f} {2:11.2e} {3:8.2f}" .format(it+1, L**(1./exponent), fGrad, time()-StartTime)) if fGrad < grad_tol: Converged = True break Note = "IB/P%i/2x2, %i iter; Final gradient %.2e" % (exponent, it+1, fGrad) if not Converged: log.note("\nWARNING: Iterative localization failed to converge!" "\n %s", Note) else: log.note(" Iterative localization: %s", Note) log.debug(" Localized orbitals deviation from orthogonality: %8.2e", numpy.linalg.norm(numpy.dot(CIb.T, CIb) - numpy.eye(numOccOrbitals))) # Note CIb is not unitary matrix (although very close to unitary matrix) # because the projection <IAO|OccOrb> does not give unitary matrix. return numpy.dot(iaos, (orth.vec_lowdin(CIb)))
def kernel(mf, aolabels, threshold=.2, minao='minao', with_iao=False, openshelloption=2, canonicalize=True, verbose=None): '''AVAS method to construct mcscf active space. Ref. arXiv:1701.07862 [physics.chem-ph] Args: mf : an :class:`SCF` object aolabels : string or a list of strings AO labels for AO active space Kwargs: threshold : float Tructing threshold of the AO-projector above which AOs are kept in the active space. minao : str A reference AOs for AVAS. with_iao : bool Whether to use IAO localization to construct the reference active AOs. openshelloption : int How to handle singly-occupied orbitals in the active space. The singly-occupied orbitals are projected as part of alpha orbitals if openshelloption=2, or completely kept in active space if openshelloption=3. See Section III.E option 2 or 3 of the reference paper for more details. canonicalize : bool Orbitals defined in AVAS method are local orbitals. Symmetrizing the core, active and virtual space. Returns: active-space-size, #-active-electrons, orbital-initial-guess-for-CASCI/CASSCF Examples: >>> from pyscf import gto, scf, mcscf >>> from pyscf.mcscf import avas >>> mol = gto.M(atom='Cr 0 0 0; Cr 0 0 1.6', basis='ccpvtz') >>> mf = scf.RHF(mol).run() >>> ncas, nelecas, mo = avas.avas(mf, ['Cr 3d', 'Cr 4s']) >>> mc = mcscf.CASSCF(mf, ncas, nelecas).run(mo) ''' if isinstance(verbose, logger.Logger): log = verbose elif verbose is not None: log = logger.Logger(mf.stdout, verbose) else: log = logger.Logger(mf.stdout, mf.verbose) mol = mf.mol log.info('\n** AVAS **') if isinstance(mf, scf.uhf.UHF): log.note('UHF/UKS object is found. AVAS takes alpha orbitals only') mo_coeff = mf.mo_coeff[0] mo_occ = mf.mo_occ[0] mo_energy = mf.mo_energy[0] assert (openshelloption != 1) else: mo_coeff = mf.mo_coeff mo_occ = mf.mo_occ mo_energy = mf.mo_energy nocc = numpy.count_nonzero(mo_occ != 0) ovlp = mol.intor_symmetric('cint1e_ovlp_sph') log.info(' Total number of HF MOs is equal to %d', mo_coeff.shape[1]) log.info(' Number of occupied HF MOs is equal to %d', nocc) mol = mf.mol pmol = mol.copy() pmol.atm = mol._atm pmol.basis = minao pmol.build(False, False) if isinstance(aolabels, str): aolabels = re.sub(' +', ' ', aolabels.strip(), count=1) baslst = [ i for i, s in enumerate(pmol.spherical_labels(1)) if aolabels in s ] elif isinstance(aolabels[0], str): aolabels = [re.sub(' +', ' ', x.strip(), count=1) for x in aolabels] baslst = [ i for i, s in enumerate(pmol.spherical_labels(1)) if any(x in s for x in aolabels) ] else: raise RuntimeError baslst = numpy.asarray(baslst) log.info('reference AO indices for %s %s: %s', minao, aolabels, baslst) if with_iao: from pyscf.lo import iao c = iao.iao(mol, mo_coeff[:, :nocc], minao)[:, baslst] s2 = reduce(numpy.dot, (c.T, ovlp, c)) s21 = reduce(numpy.dot, (c.T, ovlp, mo_coeff)) else: s2 = pmol.intor_symmetric('cint1e_ovlp_sph')[baslst][:, baslst] s21 = gto.intor_cross('cint1e_ovlp_sph', pmol, mol)[baslst] s21 = numpy.dot(s21, mo_coeff) sa = s21.T.dot(scipy.linalg.solve(s2, s21, sym_pos=True)) if openshelloption == 2: wocc, u = numpy.linalg.eigh(sa[:nocc, :nocc]) log.info('Option 2: threshold %s', threshold) ncas_occ = (wocc > threshold).sum() nelecas = mol.nelectron - (wocc < threshold).sum() * 2 mocore = mo_coeff[:, :nocc].dot(u[:, wocc < threshold]) mocas = mo_coeff[:, :nocc].dot(u[:, wocc > threshold]) wvir, u = numpy.linalg.eigh(sa[nocc:, nocc:]) ncas_vir = (wvir > threshold).sum() mocas = numpy.hstack( (mocas, mo_coeff[:, nocc:].dot(u[:, wvir > threshold]))) movir = mo_coeff[:, nocc:].dot(u[:, wvir < threshold]) ncas = mocas.shape[1] elif openshelloption == 3: docc = nocc - mol.spin wocc, u = numpy.linalg.eigh(sa[:docc, :docc]) log.info('Option 3: threshold %s, num open shell %d', threshold, mol.spin) ncas_occ = (wocc > threshold).sum() nelecas = mol.nelectron - (wocc < threshold).sum() * 2 mocore = mo_coeff[:, :docc].dot(u[:, wocc < threshold]) mocas = mo_coeff[:, :docc].dot(u[:, wocc > threshold]) wvir, u = numpy.linalg.eigh(sa[nocc:, nocc:]) ncas_vir = (wvir > threshold).sum() mocas = numpy.hstack((mocas, mo_coeff[:, docc:nocc], mo_coeff[:, nocc:].dot(u[:, wvir > threshold]))) movir = mo_coeff[:, nocc:].dot(u[:, wvir < threshold]) ncas = mocas.shape[1] log.debug('projected occ eig %s', wocc[::-1]) log.debug('projected vir eig %s', wvir[::-1]) log.info('Active from occupied = %d , eig %s', ncas_occ, wocc[wocc > threshold][::-1]) log.info('Inactive from occupied = %d', mocore.shape[1]) log.info('Active from unoccupied = %d , eig %s', ncas_vir, wvir[wvir > threshold][::-1]) log.info('Inactive from unoccupied = %d', movir.shape[1]) log.info('Dimensions of active %d', ncas) nalpha = (nelecas + mol.spin) // 2 nbeta = nelecas - nalpha log.info('# of alpha electrons %d', nalpha) log.info('# of beta electrons %d', nbeta) if canonicalize: from pyscf.mcscf import dmet_cas def trans(c): if c.shape[1] == 0: return c else: csc = reduce(numpy.dot, (c.T, ovlp, mo_coeff)) fock = numpy.dot(csc * mo_energy, csc.T) e, u = scipy.linalg.eigh(fock) return dmet_cas.symmetrize(mol, e, numpy.dot(c, u), ovlp, log) mo = numpy.hstack([trans(mocore), trans(mocas), trans(movir)]) else: mo = numpy.hstack((mocore, mocas, movir)) return ncas, nelecas, mo
def _kernel(avas_obj): mf = avas_obj._scf mol = mf.mol log = logger.new_logger(avas_obj) log.info('\n** AVAS **') assert avas_obj.openshell_option != 1 if isinstance(mf, scf.uhf.UHF): log.note('UHF/UKS object is found. AVAS takes alpha orbitals only') mo_coeff = mf.mo_coeff[0] mo_occ = mf.mo_occ[0] mo_energy = mf.mo_energy[0] else: mo_coeff = mf.mo_coeff mo_occ = mf.mo_occ mo_energy = mf.mo_energy ncore = avas_obj.ncore nocc = numpy.count_nonzero(mo_occ != 0) ovlp = mol.intor_symmetric('int1e_ovlp') log.info(' Total number of HF MOs is equal to %d' ,mo_coeff.shape[1]) log.info(' Number of occupied HF MOs is equal to %d', nocc) mol = mf.mol pmol = mol.copy() pmol.atom = mol._atom pmol.unit = 'B' pmol.symmetry = False pmol.basis = avas_obj.minao pmol.build(False, False) baslst = pmol.search_ao_label(avas_obj.aolabels) log.info('reference AO indices for %s %s: %s', avas_obj.minao, avas_obj.aolabels, baslst) if avas_obj.with_iao: from pyscf.lo import iao c = iao.iao(mol, mo_coeff[:,ncore:nocc], avas_obj.minao)[:,baslst] s2 = reduce(numpy.dot, (c.T, ovlp, c)) s21 = reduce(numpy.dot, (c.T, ovlp, mo_coeff[:, ncore:])) else: s2 = pmol.intor_symmetric('int1e_ovlp')[baslst][:,baslst] s21 = gto.intor_cross('int1e_ovlp', pmol, mol)[baslst] s21 = numpy.dot(s21, mo_coeff[:, ncore:]) sa = s21.T.dot(scipy.linalg.solve(s2, s21, sym_pos=True)) threshold = avas_obj.threshold if avas_obj.openshell_option == 2: wocc, u = numpy.linalg.eigh(sa[:(nocc-ncore), :(nocc-ncore)]) log.info('Option 2: threshold %s', threshold) ncas_occ = (wocc > threshold).sum() nelecas = (mol.nelectron - ncore * 2) - (wocc < threshold).sum() * 2 mocore = mo_coeff[:,ncore:nocc].dot(u[:,wocc<threshold]) mocas = mo_coeff[:,ncore:nocc].dot(u[:,wocc>=threshold]) wvir, u = numpy.linalg.eigh(sa[(nocc-ncore):,(nocc-ncore):]) ncas_vir = (wvir > threshold).sum() mocas = numpy.hstack((mocas, mo_coeff[:,nocc:].dot(u[:,wvir>=threshold]))) movir = mo_coeff[:,nocc:].dot(u[:,wvir<threshold]) ncas = mocas.shape[1] occ_weights = numpy.hstack([wocc[wocc<threshold], wocc[wocc>=threshold]]) vir_weights = numpy.hstack([wvir[wvir>=threshold], wvir[wvir<threshold]]) elif avas_obj.openshell_option == 3: docc = nocc - mol.spin wocc, u = numpy.linalg.eigh(sa[:(docc-ncore),:(docc-ncore)]) log.info('Option 3: threshold %s, num open shell %d', threshold, mol.spin) ncas_occ = (wocc > threshold).sum() nelecas = (mol.nelectron - ncore * 2) - (wocc < threshold).sum() * 2 mocore = mo_coeff[:,ncore:docc].dot(u[:,wocc<threshold]) mocas = mo_coeff[:,ncore:docc].dot(u[:,wocc>=threshold]) wvir, u = numpy.linalg.eigh(sa[(nocc-ncore):,(nocc-ncore):]) ncas_vir = (wvir > threshold).sum() mocas = numpy.hstack((mocas, mo_coeff[:,docc:nocc], mo_coeff[:,nocc:].dot(u[:,wvir>=threshold]))) movir = mo_coeff[:,nocc:].dot(u[:,wvir<threshold]) ncas = mocas.shape[1] occ_weights = numpy.hstack([wocc[wocc<threshold], numpy.ones(nocc-docc), wocc[wocc>=threshold]]) vir_weights = numpy.hstack([wvir[wvir>=threshold], wvir[wvir<threshold]]) else: raise RuntimeError(f'Unknown option openshell_option {avas_obj.openshell_option}') log.debug('projected occ eig %s', occ_weights) log.debug('projected vir eig %s', vir_weights) log.info('Active from occupied = %d , eig %s', ncas_occ, occ_weights[occ_weights>=threshold]) log.info('Inactive from occupied = %d', mocore.shape[1]) log.info('Active from unoccupied = %d , eig %s', ncas_vir, vir_weights[vir_weights>=threshold]) log.info('Inactive from unoccupied = %d', movir.shape[1]) log.info('Dimensions of active %d', ncas) nalpha = (nelecas + mol.spin) // 2 nbeta = nelecas - nalpha log.info('# of alpha electrons %d', nalpha) log.info('# of beta electrons %d', nbeta) mofreeze = mo_coeff[:,:ncore] if avas_obj.canonicalize: from pyscf.mcscf import dmet_cas def trans(c): if c.shape[1] == 0: return c else: csc = reduce(numpy.dot, (c.T, ovlp, mo_coeff)) fock = numpy.dot(csc*mo_energy, csc.T) e, u = scipy.linalg.eigh(fock) return dmet_cas.symmetrize(mol, e, numpy.dot(c, u), ovlp, log) if ncore > 0: mofreeze = trans(mofreeze) mocore = trans(mocore) mocas = trans(mocas) movir = trans(movir) mo = numpy.hstack((mofreeze, mocore, mocas, movir)) return ncas, nelecas, mo, occ_weights, vir_weights