def test_mo_1to1map(self): mol1 = gto.M(atom = '''O 0 0 0; O 0 0 1''', basis='6-31g') mol2 = gto.M(atom = '''O 0 0 0; O 0 0 1''', basis='ccpvdz') s = gto.intor_cross('cint1e_ovlp_sph', mol1, mol2) idx = mo_mapping.mo_1to1map(s) self.assertTrue(numpy.allclose(idx, [0, 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 17, 18, 19, 20, 21, 22]))
def cas_natorb(mc, mo_coeff=None, ci=None, eris=None, sort=False, casdm1=None, verbose=None, with_meta_lowdin=WITH_META_LOWDIN): '''Transform active orbitals to natrual orbitals, and update the CI wfn Args: mc : a CASSCF/CASCI object or RHF object Kwargs: sort : bool Sort natural orbitals wrt the occupancy. Returns: A tuple, the first item is natural orbitals, the second is updated CI coefficients, the third is the natural occupancy associated to the natural orbitals. ''' from pyscf.lo import orth from pyscf.tools import dump_mat from pyscf.tools.mo_mapping import mo_1to1map if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci log = logger.new_logger(mc, verbose) ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas if casdm1 is None: casdm1 = mc.fcisolver.make_rdm1(ci, ncas, nelecas) # orbital symmetry is reserved in this _eig call occ, ucas = mc._eig(-casdm1, ncore, nocc) if sort: casorb_idx = numpy.argsort(occ.round(9), kind='mergesort') occ = occ[casorb_idx] ucas = ucas[:, casorb_idx] occ = -occ mo_occ = numpy.zeros(mo_coeff.shape[1]) mo_occ[:ncore] = 2 mo_occ[ncore:nocc] = occ mo_coeff1 = mo_coeff.copy() mo_coeff1[:, ncore:nocc] = numpy.dot(mo_coeff[:, ncore:nocc], ucas) if getattr(mo_coeff, 'orbsym', None) is not None: orbsym = numpy.copy(mo_coeff.orbsym) if sort: orbsym[ncore:nocc] = orbsym[ncore:nocc][casorb_idx] mo_coeff1 = lib.tag_array(mo_coeff1, orbsym=orbsym) if isinstance(ci, numpy.ndarray): fcivec = fci.addons.transform_ci_for_orbital_rotation( ci, ncas, nelecas, ucas) elif isinstance(ci, (tuple, list)) and isinstance(ci[0], numpy.ndarray): # for state-average eigenfunctions fcivec = [ fci.addons.transform_ci_for_orbital_rotation( x, ncas, nelecas, ucas) for x in ci ] else: log.info('FCI vector not available, call CASCI for wavefunction') mocas = mo_coeff1[:, ncore:nocc] hcore = mc.get_hcore() dm_core = numpy.dot(mo_coeff1[:, :ncore] * 2, mo_coeff1[:, :ncore].T) ecore = mc.energy_nuc() ecore += numpy.einsum('ij,ji', hcore, dm_core) h1eff = reduce(numpy.dot, (mocas.T, hcore, mocas)) if getattr(eris, 'ppaa', None) is not None: ecore += eris.vhf_c[:ncore, :ncore].trace() h1eff += reduce(numpy.dot, (ucas.T, eris.vhf_c[ncore:nocc, ncore:nocc], ucas)) aaaa = ao2mo.restore(4, eris.ppaa[ncore:nocc, ncore:nocc, :, :], ncas) aaaa = ao2mo.incore.full(aaaa, ucas) else: if getattr(mc, 'with_df', None): raise NotImplementedError('cas_natorb for DFCASCI/DFCASSCF') corevhf = mc.get_veff(mc.mol, dm_core) ecore += numpy.einsum('ij,ji', dm_core, corevhf) * .5 h1eff += reduce(numpy.dot, (mocas.T, corevhf, mocas)) aaaa = ao2mo.kernel(mc.mol, mocas) # See label_symmetry_ function in casci_symm.py which initialize the # orbital symmetry information in fcisolver. This orbital symmetry # labels should be reordered to match the sorted active space orbitals. if sort and getattr(mo_coeff1, 'orbsym', None) is not None: mc.fcisolver.orbsym = mo_coeff1.orbsym[ncore:nocc] max_memory = max(400, mc.max_memory - lib.current_memory()[0]) e, fcivec = mc.fcisolver.kernel(h1eff, aaaa, ncas, nelecas, ecore=ecore, max_memory=max_memory, verbose=log) log.debug('In Natural orbital, CASCI energy = %s', e) if log.verbose >= logger.INFO: ovlp_ao = mc._scf.get_ovlp() # where_natorb gives the new locations of the natural orbitals where_natorb = mo_1to1map(ucas) log.debug('where_natorb %s', str(where_natorb)) log.info('Natural occ %s', str(occ)) if with_meta_lowdin: log.info( 'Natural orbital (expansion on meta-Lowdin AOs) in CAS space') label = mc.mol.ao_labels() orth_coeff = orth.orth_ao(mc.mol, 'meta_lowdin', s=ovlp_ao) mo_cas = reduce(numpy.dot, (orth_coeff.T, ovlp_ao, mo_coeff1[:, ncore:nocc])) else: log.info('Natural orbital (expansion on AOs) in CAS space') label = mc.mol.ao_labels() mo_cas = mo_coeff1[:, ncore:nocc] dump_mat.dump_rec(log.stdout, mo_cas, label, start=1) if mc._scf.mo_coeff is not None: s = reduce(numpy.dot, (mo_coeff1[:, ncore:nocc].T, mc._scf.get_ovlp(), mc._scf.mo_coeff)) idx = numpy.argwhere(abs(s) > .4) for i, j in idx: log.info('<CAS-nat-orb|mo-hf> %d %d %12.8f', ncore + i + 1, j + 1, s[i, j]) return mo_coeff1, fcivec, mo_occ
def cas_natorb(mc, mo_coeff=None, ci=None, eris=None, sort=False, casdm1=None, verbose=None): '''Transform active orbitals to natrual orbitals, and update the CI wfn Args: mc : a CASSCF/CASCI object or RHF object Kwargs: sort : bool Sort natural orbitals wrt the occupancy. Returns: A tuple, the first item is natural orbitals, the second is updated CI coefficients, the third is the natural occupancy associated to the natural orbitals. ''' from pyscf.lo import orth from pyscf.tools import dump_mat from pyscf.tools.mo_mapping import mo_1to1map if isinstance(verbose, logger.Logger): log = verbose else: log = logger.Logger(mc.stdout, mc.verbose) if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas if casdm1 is None: casdm1 = mc.fcisolver.make_rdm1(ci, ncas, nelecas) # orbital symmetry is reserved in this _eig call occ, ucas = mc._eig(-casdm1, ncore, nocc) if sort: casorb_idx = numpy.argsort(occ) occ = occ[casorb_idx] ucas = ucas[:, casorb_idx] # restore phase # where_natorb gives the location of the natural orbital for the input cas # orbitals. gen_strings4orblist map thes sorted strings (on CAS orbital) to # the unsorted determinant strings (on natural orbital). e.g. (3o,2e) system # CAS orbital 1 2 3 # natural orbital 3 1 2 <= by mo_1to1map # CASorb-strings 0b011, 0b101, 0b110 # == (1,2), (1,3), (2,3) # natorb-strings (3,1), (3,2), (1,2) # == 0B101, 0B110, 0B011 <= by gen_strings4orblist # then argsort to translate the string representation to the address # [2(=0B011), 0(=0B101), 1(=0B110)] # to indicate which CASorb-strings address to be loaded in each natorb-strings slot where_natorb = mo_1to1map(ucas) for i, k in enumerate(where_natorb): if ucas[i, k] < 0: ucas[:, k] *= -1 occ = -occ mo_occ = numpy.zeros(mo_coeff.shape[1]) mo_occ[:ncore] = 2 mo_occ[ncore:nocc] = occ mo_coeff1 = mo_coeff.copy() mo_coeff1[:, ncore:nocc] = numpy.dot(mo_coeff[:, ncore:nocc], ucas) if hasattr(mo_coeff, 'orbsym'): orbsym = numpy.copy(mo_coeff.orbsym) if sort: orbsym[ncore:nocc] = orbsym[ncore:nocc][casorb_idx] mo_coeff1 = lib.tag_array(mo_coeff1, orbsym=orbsym) if isinstance(ci, numpy.ndarray): fcivec = fci.addons.transform_ci_for_orbital_rotation( ci, ncas, nelecas, ucas) elif isinstance(ci, (tuple, list)) and isinstance(ci[0], numpy.ndarray): # for state-average eigenfunctions fcivec = [ fci.addons.transform_ci_for_orbital_rotation( x, ncas, nelecas, ucas) for x in ci ] else: log.info('FCI vector not available, call CASCI for wavefunction') mocas = mo_coeff1[:, ncore:nocc] hcore = mc.get_hcore() dm_core = numpy.dot(mo_coeff1[:, :ncore] * 2, mo_coeff1[:, :ncore].T) ecore = mc._scf.energy_nuc() ecore += numpy.einsum('ij,ji', hcore, dm_core) h1eff = reduce(numpy.dot, (mocas.T, hcore, mocas)) if eris is not None and hasattr(eris, 'ppaa'): ecore += eris.vhf_c[:ncore, :ncore].trace() h1eff += reduce(numpy.dot, (ucas.T, eris.vhf_c[ncore:nocc, ncore:nocc], ucas)) aaaa = ao2mo.restore(4, eris.ppaa[ncore:nocc, ncore:nocc, :, :], ncas) aaaa = ao2mo.incore.full(aaaa, ucas) else: corevhf = mc.get_veff(mc.mol, dm_core) ecore += numpy.einsum('ij,ji', dm_core, corevhf) * .5 h1eff += reduce(numpy.dot, (mocas.T, corevhf, mocas)) aaaa = ao2mo.kernel(mc.mol, mocas) # See label_symmetry_ function in casci_symm.py which initialize the # orbital symmetry information in fcisolver. This orbital symmetry # labels should be reordered to match the sorted active space orbitals. if hasattr(mo_coeff1, 'orbsym') and sort: mc.fcisolver.orbsym = mo_coeff1.orbsym[ncore:nocc] max_memory = max(400, mc.max_memory - lib.current_memory()[0]) e, fcivec = mc.fcisolver.kernel(h1eff, aaaa, ncas, nelecas, ecore=ecore, max_memory=max_memory, verbose=log) log.debug('In Natural orbital, CASCI energy = %.12g', e) if log.verbose >= logger.INFO: ovlp_ao = mc._scf.get_ovlp() log.debug('where_natorb %s', str(where_natorb)) log.info('Natural occ %s', str(occ)) log.info('Natural orbital (expansion on meta-Lowdin AOs) in CAS space') label = mc.mol.ao_labels() orth_coeff = orth.orth_ao(mc.mol, 'meta_lowdin', s=ovlp_ao) mo_cas = reduce(numpy.dot, (orth_coeff.T, ovlp_ao, mo_coeff1[:, ncore:nocc])) dump_mat.dump_rec(log.stdout, mo_cas, label, start=1) if mc._scf.mo_coeff is not None: s = reduce(numpy.dot, (mo_coeff1[:, ncore:nocc].T, mc._scf.get_ovlp(), mc._scf.mo_coeff)) idx = numpy.argwhere(abs(s) > .4) for i, j in idx: log.info('<CAS-nat-orb|mo-hf> %d %d %12.8f', ncore + i + 1, j + 1, s[i, j]) return mo_coeff1, fcivec, mo_occ
def kernel(localizer, mo_coeff=None, callback=None, verbose=None): from pyscf.tools import mo_mapping if mo_coeff is not None: localizer.mo_coeff = numpy.asarray(mo_coeff, order='C') if localizer.mo_coeff.shape[1] <= 1: return localizer.mo_coeff if localizer.verbose >= logger.WARN: localizer.check_sanity() localizer.dump_flags() cput0 = (time.clock(), time.time()) log = logger.new_logger(localizer, verbose=verbose) if localizer.conv_tol_grad is None: conv_tol_grad = numpy.sqrt(localizer.conv_tol * .1) log.info('Set conv_tol_grad to %g', conv_tol_grad) else: conv_tol_grad = localizer.conv_tol_grad if mo_coeff is None: if getattr(localizer, 'mol', None) and localizer.mol.natm == 0: # For customized Hamiltonian u0 = localizer.get_init_guess('random') else: u0 = localizer.get_init_guess(localizer.init_guess) else: u0 = localizer.get_init_guess(None) rotaiter = ciah.rotate_orb_cc(localizer, u0, conv_tol_grad, verbose=log) u, g_orb, stat = next(rotaiter) cput1 = log.timer('initializing CIAH', *cput0) tot_kf = stat.tot_kf tot_hop = stat.tot_hop conv = False e_last = 0 for imacro in range(localizer.max_cycle): norm_gorb = numpy.linalg.norm(g_orb) u0 = lib.dot(u0, u) e = localizer.cost_function(u0) e_last, de = e, e - e_last log.info('macro= %d f(x)= %.14g delta_f= %g |g|= %g %d KF %d Hx', imacro + 1, e, de, norm_gorb, stat.tot_kf + 1, stat.tot_hop) cput1 = log.timer('cycle= %d' % (imacro + 1), *cput1) if (norm_gorb < conv_tol_grad and abs(de) < localizer.conv_tol): conv = True if callable(callback): callback(locals()) if conv: break u, g_orb, stat = rotaiter.send(u0) tot_kf += stat.tot_kf tot_hop += stat.tot_hop rotaiter.close() log.info('macro X = %d f(x)= %.14g |g|= %g %d intor %d KF %d Hx', imacro + 1, e, norm_gorb, (imacro + 1) * 2, tot_kf + imacro + 1, tot_hop) # Sort the localized orbitals, to make each localized orbitals as close as # possible to the corresponding input orbitals sorted_idx = mo_mapping.mo_1to1map(u0) localizer.mo_coeff = lib.dot(localizer.mo_coeff, u0[:, sorted_idx]) return localizer.mo_coeff
def cas_natorb(mc, mo_coeff=None, ci=None, eris=None, sort=False, casdm1=None, verbose=None): '''Transform active orbitals to natrual orbitals, and update the CI wfn Args: mc : a CASSCF/CASCI object or RHF object Kwargs: sort : bool Sort natural orbitals wrt the occupancy. Be careful with this option since the resultant natural orbitals might have the different symmetry to the irreps indicated by CASSCF.orbsym Returns: A tuple, the first item is natural orbitals, the second is updated CI coefficients, the third is the natural occupancy associated to the natural orbitals. ''' from pyscf.lo import orth from pyscf.tools import dump_mat from pyscf.tools.mo_mapping import mo_1to1map if isinstance(verbose, logger.Logger): log = verbose else: log = logger.Logger(mc.stdout, mc.verbose) if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas if casdm1 is None: casdm1 = mc.fcisolver.make_rdm1(ci, ncas, nelecas) # orbital symmetry is reserved in this _eig call occ, ucas = mc._eig(-casdm1, ncore, nocc) if sort: idx = numpy.argsort(occ) occ = occ[idx] ucas = ucas[:,idx] # restore phase # where_natorb gives the location of the natural orbital for the input cas # orbitals. gen_strings4orblist map thes sorted strings (on CAS orbital) to # the unsorted determinant strings (on natural orbital). e.g. (3o,2e) system # CAS orbital 1 2 3 # natural orbital 3 1 2 <= by mo_1to1map # CASorb-strings 0b011, 0b101, 0b110 # == (1,2), (1,3), (2,3) # natorb-strings (3,1), (3,2), (1,2) # == 0B101, 0B110, 0B011 <= by gen_strings4orblist # then argsort to translate the string representation to the address # [2(=0B011), 0(=0B101), 1(=0B110)] # to indicate which CASorb-strings address to be loaded in each natorb-strings slot where_natorb = mo_1to1map(ucas) for i, k in enumerate(where_natorb): if ucas[i,k] < 0: ucas[:,k] *= -1 occ = -occ mo_occ = numpy.zeros(mo_coeff.shape[1]) mo_occ[:ncore] = 2 mo_occ[ncore:nocc] = occ if isinstance(ci, numpy.ndarray): fcivec = fci.addons.transform_ci_for_orbital_rotation(ci, ncas, nelecas, ucas) elif isinstance(ci, (tuple, list)) and isinstance(ci[0], numpy.ndarray): # for state-average eigenfunctions fcivec = [fci.addons.transform_ci_for_orbital_rotation(x, ncas, nelecas, ucas) for x in ci] else: log.info('FCI vector not available, call CASCI for wavefunction') mocas = mo_coeff1[:,ncore:nocc] h1eff = reduce(numpy.dot, (mocas.T, mc.get_hcore(), mocas)) if eris is not None and hasattr(eris, 'ppaa'): h1eff += reduce(numpy.dot, (ucas.T, eris.vhf_c[ncore:nocc,ncore:nocc], ucas)) aaaa = ao2mo.restore(4, eris.ppaa[ncore:nocc,ncore:nocc,:,:], ncas) aaaa = ao2mo.incore.full(aaaa, ucas) else: dm_core = numpy.dot(mo_coeff[:,:ncore]*2, mo_coeff[:,:ncore].T) vj, vk = mc._scf.get_jk(mc.mol, dm_core) h1eff += reduce(numpy.dot, (mocas.T, vj-vk*.5, mocas)) aaaa = ao2mo.kernel(mc.mol, mocas) max_memory = max(400, mc.max_memory-lib.current_memory()[0]) e_cas, fcivec = mc.fcisolver.kernel(h1eff, aaaa, ncas, nelecas, max_memory=max_memory, verbose=log) log.debug('In Natural orbital, CI energy = %.12g', e_cas) mo_coeff1 = mo_coeff.copy() mo_coeff1[:,ncore:nocc] = numpy.dot(mo_coeff[:,ncore:nocc], ucas) if log.verbose >= logger.INFO: ovlp_ao = mc._scf.get_ovlp() log.debug('where_natorb %s', str(where_natorb)) log.info('Natural occ %s', str(occ)) log.info('Natural orbital (expansion on meta-Lowdin AOs) in CAS space') label = mc.mol.spheric_labels(True) orth_coeff = orth.orth_ao(mc.mol, 'meta_lowdin', s=ovlp_ao) mo_cas = reduce(numpy.dot, (orth_coeff.T, ovlp_ao, mo_coeff1[:,ncore:nocc])) dump_mat.dump_rec(log.stdout, mo_cas, label, start=1) if mc._scf.mo_coeff is not None: s = reduce(numpy.dot, (mo_coeff1[:,ncore:nocc].T, mc._scf.get_ovlp(), mc._scf.mo_coeff)) idx = numpy.argwhere(abs(s)>.4) for i,j in idx: log.info('<CAS-nat-orb|mo-hf> %d %d %12.8f', ncore+i+1, j+1, s[i,j]) return mo_coeff1, fcivec, mo_occ
def cas_natorb(mc, mo_coeff=None, ci=None, eris=None, sort=False, casdm1=None, verbose=None): """Transform active orbitals to natrual orbitals, and update the CI wfn Args: mc : a CASSCF/CASCI object or RHF object Kwargs: sort : bool Sort natural orbitals wrt the occupancy. Be careful with this option since the resultant natural orbitals might have the different symmetry to the irreps indicated by CASSCF.orbsym Returns: A tuple, the first item is natural orbitals, the second is updated CI coefficients, the third is the natural occupancy associated to the natural orbitals. """ from pyscf.mcscf import mc_ao2mo from pyscf.tools import dump_mat if isinstance(verbose, logger.Logger): log = verbose else: log = logger.Logger(mc.stdout, mc.verbose) if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas if casdm1 is None: casdm1 = mc.fcisolver.make_rdm1(ci, ncas, nelecas) occ, ucas = mc._eig(-casdm1, ncore, nocc) if sort: idx = numpy.argsort(occ) occ = occ[idx] ucas = ucas[:, idx] if hasattr(mc, "orbsym"): # for casci_symm mc.orbsym[ncore:nocc] = mc.orbsym[ncore:nocc][idx] mc.fcisolver.orbsym = mc.orbsym[ncore:nocc] occ = -occ # where_natorb gives the location of the natural orbital for the input cas # orbitals. gen_strings4orblist map thes sorted strings (on CAS orbital) to # the unsorted determinant strings (on natural orbital). e.g. (3o,2e) system # CAS orbital 1 2 3 # natural orbital 3 1 2 <= by mo_1to1map # CASorb-strings 0b011, 0b101, 0b110 # == (1,2), (1,3), (2,3) # natorb-strings (3,1), (3,2), (1,2) # == 0B101, 0B110, 0B011 <= by gen_strings4orblist # then argsort to translate the string representation to the address # [2(=0B011), 0(=0B101), 1(=0B110)] # to indicate which CASorb-strings address to be loaded in each natorb-strings slot where_natorb = mo_1to1map(ucas) # guide_stringsa = fci.cistring.gen_strings4orblist(where_natorb, nelecas[0]) # guide_stringsb = fci.cistring.gen_strings4orblist(where_natorb, nelecas[1]) # old_det_idxa = numpy.argsort(guide_stringsa) # old_det_idxb = numpy.argsort(guide_stringsb) # ci0 = ci[old_det_idxa[:,None],old_det_idxb] if isinstance(ci, numpy.ndarray): ci0 = fci.addons.reorder(ci, nelecas, where_natorb) elif isinstance(ci, (tuple, list)) and isinstance(ci[0], numpy.ndarray): # for state-average eigenfunctions ci0 = [fci.addons.reorder(x, nelecas, where_natorb) for x in ci] else: log.info("FCI vector not available, so not using old wavefunction as initial guess") ci0 = None # restore phase, to ensure the reordered ci vector is the correct initial guess for i, k in enumerate(where_natorb): if ucas[i, k] < 0: ucas[:, k] *= -1 mo_coeff1 = mo_coeff.copy() mo_coeff1[:, ncore:nocc] = numpy.dot(mo_coeff[:, ncore:nocc], ucas) if log.verbose >= logger.INFO: log.debug("where_natorb %s", str(where_natorb)) log.info("Natural occ %s", str(occ)) log.info("Natural orbital in CAS space") label = mc.mol.spheric_labels(True) dump_mat.dump_rec(log.stdout, mo_coeff1[:, ncore:nocc], label, start=1) if mc._scf.mo_coeff is not None: s = reduce(numpy.dot, (mo_coeff1[:, ncore:nocc].T, mc._scf.get_ovlp(), mc._scf.mo_coeff)) idx = numpy.argwhere(abs(s) > 0.4) for i, j in idx: log.info("<CAS-nat-orb|mo-hf> %d %d %12.8f", ncore + i + 1, j + 1, s[i, j]) mocas = mo_coeff1[:, ncore:nocc] h1eff = reduce(numpy.dot, (mocas.T, mc.get_hcore(), mocas)) if eris is not None and hasattr(eris, "ppaa"): h1eff += reduce(numpy.dot, (ucas.T, eris.vhf_c[ncore:nocc, ncore:nocc], ucas)) aaaa = ao2mo.restore(4, eris.ppaa[ncore:nocc, ncore:nocc, :, :], ncas) aaaa = ao2mo.incore.full(aaaa, ucas) else: dm_core = numpy.dot(mo_coeff[:, :ncore] * 2, mo_coeff[:, :ncore].T) vj, vk = mc._scf.get_jk(mc.mol, dm_core) h1eff += reduce(numpy.dot, (mocas.T, vj - vk * 0.5, mocas)) aaaa = ao2mo.kernel(mc.mol, mocas) e_cas, fcivec = mc.fcisolver.kernel(h1eff, aaaa, ncas, nelecas, ci0=ci0) log.debug("In Natural orbital, CI energy = %.12g", e_cas) return mo_coeff1, fcivec, occ
def cas_natorb(mc, mo_coeff=None, ci=None, eris=None, sort=False, casdm1=None, verbose=None, with_meta_lowdin=WITH_META_LOWDIN): '''Transform active orbitals to natrual orbitals, and update the CI wfn Args: mc : a CASSCF/CASCI object or RHF object Kwargs: sort : bool Sort natural orbitals wrt the occupancy. Returns: A tuple, the first item is natural orbitals, the second is updated CI coefficients, the third is the natural occupancy associated to the natural orbitals. ''' from pyscf.lo import orth from pyscf.tools import dump_mat from pyscf.tools.mo_mapping import mo_1to1map if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci log = logger.new_logger(mc, verbose) ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas if casdm1 is None: casdm1 = mc.fcisolver.make_rdm1(ci, ncas, nelecas) # orbital symmetry is reserved in this _eig call occ, ucas = mc._eig(-casdm1, ncore, nocc) if sort: casorb_idx = numpy.argsort(occ.round(9), kind='mergesort') occ = occ[casorb_idx] ucas = ucas[:,casorb_idx] occ = -occ mo_occ = numpy.zeros(mo_coeff.shape[1]) mo_occ[:ncore] = 2 mo_occ[ncore:nocc] = occ mo_coeff1 = mo_coeff.copy() mo_coeff1[:,ncore:nocc] = numpy.dot(mo_coeff[:,ncore:nocc], ucas) if getattr(mo_coeff, 'orbsym', None) is not None: orbsym = numpy.copy(mo_coeff.orbsym) if sort: orbsym[ncore:nocc] = orbsym[ncore:nocc][casorb_idx] mo_coeff1 = lib.tag_array(mo_coeff1, orbsym=orbsym) if isinstance(ci, numpy.ndarray): fcivec = fci.addons.transform_ci_for_orbital_rotation(ci, ncas, nelecas, ucas) elif isinstance(ci, (tuple, list)) and isinstance(ci[0], numpy.ndarray): # for state-average eigenfunctions fcivec = [fci.addons.transform_ci_for_orbital_rotation(x, ncas, nelecas, ucas) for x in ci] else: log.info('FCI vector not available, call CASCI for wavefunction') mocas = mo_coeff1[:,ncore:nocc] hcore = mc.get_hcore() dm_core = numpy.dot(mo_coeff1[:,:ncore]*2, mo_coeff1[:,:ncore].T) ecore = mc.energy_nuc() ecore+= numpy.einsum('ij,ji', hcore, dm_core) h1eff = reduce(numpy.dot, (mocas.T, hcore, mocas)) if getattr(eris, 'ppaa', None) is not None: ecore += eris.vhf_c[:ncore,:ncore].trace() h1eff += reduce(numpy.dot, (ucas.T, eris.vhf_c[ncore:nocc,ncore:nocc], ucas)) aaaa = ao2mo.restore(4, eris.ppaa[ncore:nocc,ncore:nocc,:,:], ncas) aaaa = ao2mo.incore.full(aaaa, ucas) else: if getattr(mc, 'with_df', None): raise NotImplementedError('cas_natorb for DFCASCI/DFCASSCF') corevhf = mc.get_veff(mc.mol, dm_core) ecore += numpy.einsum('ij,ji', dm_core, corevhf) * .5 h1eff += reduce(numpy.dot, (mocas.T, corevhf, mocas)) aaaa = ao2mo.kernel(mc.mol, mocas) # See label_symmetry_ function in casci_symm.py which initialize the # orbital symmetry information in fcisolver. This orbital symmetry # labels should be reordered to match the sorted active space orbitals. if sort and getattr(mo_coeff1, 'orbsym', None) is not None: mc.fcisolver.orbsym = mo_coeff1.orbsym[ncore:nocc] max_memory = max(400, mc.max_memory-lib.current_memory()[0]) e, fcivec = mc.fcisolver.kernel(h1eff, aaaa, ncas, nelecas, ecore=ecore, max_memory=max_memory, verbose=log) log.debug('In Natural orbital, CASCI energy = %s', e) if log.verbose >= logger.INFO: ovlp_ao = mc._scf.get_ovlp() # where_natorb gives the new locations of the natural orbitals where_natorb = mo_1to1map(ucas) log.debug('where_natorb %s', str(where_natorb)) log.info('Natural occ %s', str(occ)) if with_meta_lowdin: log.info('Natural orbital (expansion on meta-Lowdin AOs) in CAS space') label = mc.mol.ao_labels() orth_coeff = orth.orth_ao(mc.mol, 'meta_lowdin', s=ovlp_ao) mo_cas = reduce(numpy.dot, (orth_coeff.T, ovlp_ao, mo_coeff1[:,ncore:nocc])) else: log.info('Natural orbital (expansion on AOs) in CAS space') label = mc.mol.ao_labels() mo_cas = mo_coeff1[:,ncore:nocc] dump_mat.dump_rec(log.stdout, mo_cas, label, start=1) if mc._scf.mo_coeff is not None: s = reduce(numpy.dot, (mo_coeff1[:,ncore:nocc].T, mc._scf.get_ovlp(), mc._scf.mo_coeff)) idx = numpy.argwhere(abs(s)>.4) for i,j in idx: log.info('<CAS-nat-orb|mo-hf> %d %d %12.8f', ncore+i+1, j+1, s[i,j]) return mo_coeff1, fcivec, mo_occ
def symmetrize_space(mol, mo, s=None, check=True): '''Symmetrize the given orbital space. This function is different to the :func:`symmetrize_orb`: In this function, the given orbitals are mixed to reveal the symmtery; :func:`symmetrize_orb` projects out non-symmetric components for each orbital. Args: mo : 2D float array The orbital space to symmetrize Kwargs: s : 2D float array Overlap matrix. If not given, overlap is computed with the input mol. Returns: 2D orbital coefficients Examples: >>> from pyscf import gto, symm, scf >>> mol = gto.M(atom = 'C 0 0 0; H 1 1 1; H -1 -1 1; H 1 -1 -1; H -1 1 -1', ... basis = 'sto3g') >>> mf = scf.RHF(mol).run() >>> mol.build(0, 0, symmetry='D2') >>> mo = symm.symmetrize_space(mol, mf.mo_coeff) >>> print(symm.label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mo)) ['A', 'A', 'A', 'B1', 'B1', 'B2', 'B2', 'B3', 'B3'] ''' from pyscf.tools import mo_mapping if s is None: s = mol.intor_symmetric('cint1e_ovlp_sph') nmo = mo.shape[1] mo_s = numpy.dot(mo.T, s) if check: assert (numpy.allclose(numpy.dot(mo_s, mo), numpy.eye(nmo))) mo1 = [] for i, csym in enumerate(mol.symm_orb): moso = numpy.dot(mo_s, csym) ovlpso = reduce(numpy.dot, (csym.T, s, csym)) # excluding orbitals which are already symmetrized try: diag = numpy.einsum('ik,ki->i', moso, lib.cho_solve(ovlpso, moso.T)) except: ovlpso[numpy.diag_indices(csym.shape[1])] += 1e-12 diag = numpy.einsum('ik,ki->i', moso, lib.cho_solve(ovlpso, moso.T)) idx = abs(1 - diag) < 1e-8 orb_exclude = mo[:, idx] mo1.append(orb_exclude) moso1 = moso[~idx] dm = numpy.dot(moso1.T, moso1) if dm.trace() > 1e-8: e, u = scipy.linalg.eigh(dm, ovlpso) mo1.append(numpy.dot(csym, u[:, abs(1 - e) < 1e-6])) mo1 = numpy.hstack(mo1) if mo1.shape[1] != nmo: raise ValueError('The input orbital space is not symmetrized.\n It is ' 'probably because the input mol and orbitals are of ' 'different orientation.') snorm = numpy.linalg.norm( reduce(numpy.dot, (mo1.T, s, mo1)) - numpy.eye(nmo)) if check and snorm > 1e-6: raise ValueError('Orbitals are not orthogonalized') idx = mo_mapping.mo_1to1map(reduce(numpy.dot, (mo.T, s, mo1))) return mo1[:, idx]
def symmetrize_space(mol, mo, s=None, check=getattr(__config__, 'symm_addons_symmetrize_space_check', True), tol=getattr(__config__, 'symm_addons_symmetrize_space_tol', 1e-7)): '''Symmetrize the given orbital space. This function is different to the :func:`symmetrize_orb`: In this function, the given orbitals are mixed to reveal the symmtery; :func:`symmetrize_orb` projects out non-symmetric components for each orbital. Args: mo : 2D float array The orbital space to symmetrize Kwargs: s : 2D float array Overlap matrix. If not given, overlap is computed with the input mol. Returns: 2D orbital coefficients Examples: >>> from pyscf import gto, symm, scf >>> mol = gto.M(atom = 'C 0 0 0; H 1 1 1; H -1 -1 1; H 1 -1 -1; H -1 1 -1', ... basis = 'sto3g') >>> mf = scf.RHF(mol).run() >>> mol.build(0, 0, symmetry='D2') >>> mo = symm.symmetrize_space(mol, mf.mo_coeff) >>> print(symm.label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mo)) ['A', 'A', 'A', 'B1', 'B1', 'B2', 'B2', 'B3', 'B3'] ''' from pyscf.tools import mo_mapping if s is None: s = mol.intor_symmetric('int1e_ovlp') nmo = mo.shape[1] s_mo = numpy.dot(s, mo) if check and abs(numpy.dot(mo.conj().T, s_mo) - numpy.eye(nmo)).max() > tol: raise ValueError('Orbitals are not orthogonalized') mo1 = [] for i, csym in enumerate(mol.symm_orb): moso = numpy.dot(csym.T.conj(), s_mo) ovlpso = reduce(numpy.dot, (csym.T.conj(), s, csym)) # excluding orbitals which are already symmetrized try: diag = numpy.einsum('ki,ki->i', moso.conj(), lib.cho_solve(ovlpso, moso)) except numpy.linalg.LinAlgError: ovlpso[numpy.diag_indices(csym.shape[1])] += 1e-12 diag = numpy.einsum('ki,ki->i', moso.conj(), lib.cho_solve(ovlpso, moso)) idx = abs(1 - diag) < 1e-8 orb_exclude = mo[:, idx] mo1.append(orb_exclude) moso1 = moso[:, ~idx] dm = numpy.dot(moso1, moso1.T.conj()) if dm.trace() > 1e-8: e, u = scipy.linalg.eigh(dm, ovlpso) mo1.append(numpy.dot(csym, u[:, abs(1 - e) < 1e-6])) mo1 = numpy.hstack(mo1) if mo1.shape[1] != nmo: raise ValueError('The input orbital space is not symmetrized.\n One ' 'possible reason is that the input mol and orbitals ' 'are of different orientation.') if (check and abs(reduce(numpy.dot, (mo1.conj().T, s, mo1)) - numpy.eye(nmo)).max() > tol): raise ValueError('Orbitals are not orthogonalized') idx = mo_mapping.mo_1to1map(reduce(numpy.dot, (mo.T.conj(), s, mo1))) return mo1[:, idx]
def cas_natorb(mc, mo_coeff=None, ci=None, eris=None, sort=False, casdm1=None, verbose=None): '''Transform active orbitals to natrual orbitals, and update the CI wfn Args: mc : a CASSCF/CASCI object or RHF object Kwargs: sort : bool Sort natural orbitals wrt the occupancy. Be careful with this option since the resultant natural orbitals might have the different symmetry to the irreps indicated by CASSCF.orbsym Returns: A tuple, the first item is natural orbitals, the second is updated CI coefficients, the third is the natural occupancy associated to the natural orbitals. ''' from pyscf.lo import orth from pyscf.tools import dump_mat from pyscf.tools.mo_mapping import mo_1to1map if isinstance(verbose, logger.Logger): log = verbose else: log = logger.Logger(mc.stdout, mc.verbose) if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas if casdm1 is None: casdm1 = mc.fcisolver.make_rdm1(ci, ncas, nelecas) occ, ucas = mc._eig(-casdm1, ncore, nocc) if sort: idx = numpy.argsort(occ) occ = occ[idx] ucas = ucas[:,idx] occ = -occ # where_natorb gives the location of the natural orbital for the input cas # orbitals. gen_strings4orblist map thes sorted strings (on CAS orbital) to # the unsorted determinant strings (on natural orbital). e.g. (3o,2e) system # CAS orbital 1 2 3 # natural orbital 3 1 2 <= by mo_1to1map # CASorb-strings 0b011, 0b101, 0b110 # == (1,2), (1,3), (2,3) # natorb-strings (3,1), (3,2), (1,2) # == 0B101, 0B110, 0B011 <= by gen_strings4orblist # then argsort to translate the string representation to the address # [2(=0B011), 0(=0B101), 1(=0B110)] # to indicate which CASorb-strings address to be loaded in each natorb-strings slot where_natorb = mo_1to1map(ucas) #guide_stringsa = fci.cistring.gen_strings4orblist(where_natorb, nelecas[0]) #guide_stringsb = fci.cistring.gen_strings4orblist(where_natorb, nelecas[1]) #old_det_idxa = numpy.argsort(guide_stringsa) #old_det_idxb = numpy.argsort(guide_stringsb) #ci0 = ci[old_det_idxa[:,None],old_det_idxb] if isinstance(ci, numpy.ndarray): ci0 = fci.addons.reorder(ci, nelecas, where_natorb) elif isinstance(ci, (tuple, list)) and isinstance(ci[0], numpy.ndarray): # for state-average eigenfunctions ci0 = [fci.addons.reorder(x, nelecas, where_natorb) for x in ci] else: log.info('FCI vector not available, so not using old wavefunction as initial guess') ci0 = None # restore phase, to ensure the reordered ci vector is the correct initial guess for i, k in enumerate(where_natorb): if ucas[i,k] < 0: ucas[:,k] *= -1 mo_coeff1 = mo_coeff.copy() mo_coeff1[:,ncore:nocc] = numpy.dot(mo_coeff[:,ncore:nocc], ucas) if log.verbose >= logger.INFO: log.debug('where_natorb %s', str(where_natorb)) log.info('Natural occ %s', str(occ)) log.info('Natural orbital (expansion on meta-Lowdin AOs) in CAS space') label = mc.mol.spheric_labels(True) orth_coeff = orth.orth_ao(mc.mol, 'meta_lowdin', s=ovlp_ao) mo_cas = reduce(numpy.dot, (orth_coeff.T, ovlp_ao, mo_coeff1[:,ncore:nocc])) dump_mat.dump_rec(log.stdout, mo_cas, label, start=1) if mc._scf.mo_coeff is not None: s = reduce(numpy.dot, (mo_coeff1[:,ncore:nocc].T, mc._scf.get_ovlp(), mc._scf.mo_coeff)) idx = numpy.argwhere(abs(s)>.4) for i,j in idx: log.info('<CAS-nat-orb|mo-hf> %d %d %12.8f', ncore+i+1, j+1, s[i,j]) mocas = mo_coeff1[:,ncore:nocc] h1eff = reduce(numpy.dot, (mocas.T, mc.get_hcore(), mocas)) if eris is not None and hasattr(eris, 'ppaa'): h1eff += reduce(numpy.dot, (ucas.T, eris.vhf_c[ncore:nocc,ncore:nocc], ucas)) aaaa = ao2mo.restore(4, eris.ppaa[ncore:nocc,ncore:nocc,:,:], ncas) aaaa = ao2mo.incore.full(aaaa, ucas) else: dm_core = numpy.dot(mo_coeff[:,:ncore]*2, mo_coeff[:,:ncore].T) vj, vk = mc._scf.get_jk(mc.mol, dm_core) h1eff += reduce(numpy.dot, (mocas.T, vj-vk*.5, mocas)) aaaa = ao2mo.kernel(mc.mol, mocas) e_cas, fcivec = mc.fcisolver.kernel(h1eff, aaaa, ncas, nelecas, ci0=ci0) log.debug('In Natural orbital, CI energy = %.12g', e_cas) return mo_coeff1, fcivec, occ
def cas_natorb(mc, mo_coeff=None, ci=None, eris=None, sort=False, casdm1=None, verbose=None, with_meta_lowdin=WITH_META_LOWDIN): '''Transform active orbitals to natrual orbitals, and update the CI wfn accordingly Args: mc : a CASSCF/CASCI object or RHF object Kwargs: sort : bool Sort natural orbitals wrt the occupancy. Returns: A tuple, the first item is natural orbitals, the second is updated CI coefficients, the third is the natural occupancy associated to the natural orbitals. ''' from pyscf.lo import orth from pyscf.tools import dump_mat from pyscf.tools.mo_mapping import mo_1to1map if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci log = logger.new_logger(mc, verbose) ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas nmo = mo_coeff.shape[1] if casdm1 is None: casdm1 = mc.fcisolver.make_rdm1(ci, ncas, nelecas) # orbital symmetry is reserved in this _eig call cas_occ, ucas = mc._eig(-casdm1, ncore, nocc) if sort: casorb_idx = numpy.argsort(cas_occ.round(9), kind='mergesort') cas_occ = cas_occ[casorb_idx] ucas = ucas[:,casorb_idx] cas_occ = -cas_occ mo_occ = numpy.zeros(mo_coeff.shape[1]) mo_occ[:ncore] = 2 mo_occ[ncore:nocc] = cas_occ mo_coeff1 = mo_coeff.copy() mo_coeff1[:,ncore:nocc] = numpy.dot(mo_coeff[:,ncore:nocc], ucas) if getattr(mo_coeff, 'orbsym', None) is not None: orbsym = numpy.copy(mo_coeff.orbsym) if sort: orbsym[ncore:nocc] = orbsym[ncore:nocc][casorb_idx] mo_coeff1 = lib.tag_array(mo_coeff1, orbsym=orbsym) else: orbsym = numpy.zeros(nmo, dtype=int) # When occupancies of active orbitals equal to 2 or 0, these orbitals # need to be canonicalized along with inactive(core or virtual) orbitals # using general Fock matrix. Because they are strongly coupled with # inactive orbitals, the 0th order Hamiltonian of MRPT methods can be # strongly affected. Numerical uncertainty may be found in the perturbed # correlation energy. # See issue https://github.com/pyscf/pyscf/issues/1041 occ2_idx = numpy.where(2 - cas_occ < FRAC_OCC_THRESHOLD)[0] occ0_idx = numpy.where(cas_occ < FRAC_OCC_THRESHOLD)[0] if occ2_idx.size > 0 or occ0_idx.size > 0: fock_ao = mc.get_fock(mo_coeff, ci, eris, casdm1, verbose) def _diag_subfock_(idx): c = mo_coeff1[:,idx] fock = reduce(numpy.dot, (c.conj().T, fock_ao, c)) w, c = mc._eig(fock, None, None, orbsym[idx]) mo_coeff1[:,idx] = mo_coeff1[:,idx].dot(c) if occ2_idx.size > 0: log.warn('Active orbitals %s (occs = %s) are canonicalized with core orbitals', occ2_idx, cas_occ[occ2_idx]) full_occ2_idx = numpy.append(numpy.arange(ncore), ncore + occ2_idx) _diag_subfock_(full_occ2_idx) if occ0_idx.size > 0: log.warn('Active orbitals %s (occs = %s) are canonicalized with external orbitals', occ0_idx, cas_occ[occ0_idx]) full_occ0_idx = numpy.append(ncore + occ0_idx, numpy.arange(nocc, nmo)) _diag_subfock_(full_occ0_idx) # Rotate CI according to the unitary coefficients ucas if applicable fcivec = None if getattr(mc.fcisolver, 'transform_ci_for_orbital_rotation', None): if isinstance(ci, numpy.ndarray): fcivec = mc.fcisolver.transform_ci_for_orbital_rotation(ci, ncas, nelecas, ucas) elif (isinstance(ci, (list, tuple)) and all(isinstance(x[0], numpy.ndarray) for x in ci)): fcivec = [mc.fcisolver.transform_ci_for_orbital_rotation(x, ncas, nelecas, ucas) for x in ci] elif getattr(mc.fcisolver, 'states_transform_ci_for_orbital_rotation', None): fcivec = mc.fcisolver.states_transform_ci_for_orbital_rotation(ci, ncas, nelecas, ucas) # Rerun fcisolver to get wavefunction if it cannot be transformed from # existed one. if fcivec is None: log.info('FCI vector not available, call CASCI to update wavefunction') mocas = mo_coeff1[:,ncore:nocc] hcore = mc.get_hcore() dm_core = numpy.dot(mo_coeff1[:,:ncore]*2, mo_coeff1[:,:ncore].conj().T) ecore = mc.energy_nuc() ecore+= numpy.einsum('ij,ji', hcore, dm_core) h1eff = reduce(numpy.dot, (mocas.conj().T, hcore, mocas)) if getattr(eris, 'ppaa', None) is not None: ecore += eris.vhf_c[:ncore,:ncore].trace() h1eff += reduce(numpy.dot, (ucas.conj().T, eris.vhf_c[ncore:nocc,ncore:nocc], ucas)) aaaa = ao2mo.restore(4, eris.ppaa[ncore:nocc,ncore:nocc,:,:], ncas) aaaa = ao2mo.incore.full(aaaa, ucas) else: if getattr(mc, 'with_df', None): aaaa = mc.with_df.ao2mo(mocas) else: aaaa = ao2mo.kernel(mc.mol, mocas) corevhf = mc.get_veff(mc.mol, dm_core) ecore += numpy.einsum('ij,ji', dm_core, corevhf) * .5 h1eff += reduce(numpy.dot, (mocas.conj().T, corevhf, mocas)) # See label_symmetry_ function in casci_symm.py which initialize the # orbital symmetry information in fcisolver. This orbital symmetry # labels should be reordered to match the sorted active space orbitals. if sort and getattr(mo_coeff1, 'orbsym', None) is not None: mc.fcisolver.orbsym = mo_coeff1.orbsym[ncore:nocc] max_memory = max(400, mc.max_memory-lib.current_memory()[0]) e, fcivec = mc.fcisolver.kernel(h1eff, aaaa, ncas, nelecas, ecore=ecore, max_memory=max_memory, verbose=log) log.debug('In Natural orbital, CASCI energy = %s', e) if log.verbose >= logger.INFO: ovlp_ao = mc._scf.get_ovlp() # where_natorb gives the new locations of the natural orbitals where_natorb = mo_1to1map(ucas) log.debug('where_natorb %s', str(where_natorb)) log.info('Natural occ %s', str(cas_occ)) if with_meta_lowdin: log.info('Natural orbital (expansion on meta-Lowdin AOs) in CAS space') label = mc.mol.ao_labels() orth_coeff = orth.orth_ao(mc.mol, 'meta_lowdin', s=ovlp_ao) mo_cas = reduce(numpy.dot, (orth_coeff.conj().T, ovlp_ao, mo_coeff1[:,ncore:nocc])) else: log.info('Natural orbital (expansion on AOs) in CAS space') label = mc.mol.ao_labels() mo_cas = mo_coeff1[:,ncore:nocc] dump_mat.dump_rec(log.stdout, mo_cas, label, start=1) if mc._scf.mo_coeff is not None: s = reduce(numpy.dot, (mo_coeff1[:,ncore:nocc].conj().T, mc._scf.get_ovlp(), mc._scf.mo_coeff)) idx = numpy.argwhere(abs(s)>.4) for i,j in idx: log.info('<CAS-nat-orb|mo-hf> %d %d %12.8f', ncore+i+1, j+1, s[i,j]) return mo_coeff1, fcivec, mo_occ
def kernel(localizer, mo_coeff=None, callback=None, verbose=None): from pyscf.tools import mo_mapping if mo_coeff is not None: localizer.mo_coeff = numpy.asarray(mo_coeff, order='C') if localizer.mo_coeff.shape[1] <= 1: return localizer.mo_coeff if localizer.verbose >= logger.WARN: localizer.check_sanity() localizer.dump_flags() cput0 = (time.clock(), time.time()) log = logger.new_logger(localizer, verbose=verbose) if localizer.conv_tol_grad is None: conv_tol_grad = numpy.sqrt(localizer.conv_tol*.1) log.info('Set conv_tol_grad to %g', conv_tol_grad) else: conv_tol_grad = localizer.conv_tol_grad if mo_coeff is None: if getattr(localizer, 'mol', None) and localizer.mol.natm == 0: # For customized Hamiltonian u0 = localizer.get_init_guess('random') else: u0 = localizer.get_init_guess(localizer.init_guess) else: u0 = localizer.get_init_guess(None) rotaiter = ciah.rotate_orb_cc(localizer, u0, conv_tol_grad, verbose=log) u, g_orb, stat = next(rotaiter) cput1 = log.timer('initializing CIAH', *cput0) tot_kf = stat.tot_kf tot_hop = stat.tot_hop conv = False e_last = 0 for imacro in range(localizer.max_cycle): norm_gorb = numpy.linalg.norm(g_orb) u0 = lib.dot(u0, u) e = localizer.cost_function(u0) e_last, de = e, e-e_last log.info('macro= %d f(x)= %.14g delta_f= %g |g|= %g %d KF %d Hx', imacro+1, e, de, norm_gorb, stat.tot_kf+1, stat.tot_hop) cput1 = log.timer('cycle= %d'%(imacro+1), *cput1) if (norm_gorb < conv_tol_grad and abs(de) < localizer.conv_tol): conv = True if callable(callback): callback(locals()) if conv: break u, g_orb, stat = rotaiter.send(u0) tot_kf += stat.tot_kf tot_hop += stat.tot_hop rotaiter.close() log.info('macro X = %d f(x)= %.14g |g|= %g %d intor %d KF %d Hx', imacro+1, e, norm_gorb, (imacro+1)*2, tot_kf+imacro+1, tot_hop) # Sort the localized orbitals, to make each localized orbitals as close as # possible to the corresponding input orbitals sorted_idx = mo_mapping.mo_1to1map(u0) localizer.mo_coeff = lib.dot(localizer.mo_coeff, u0[:,sorted_idx]) return localizer.mo_coeff
def symmetrize_space(mol, mo, s=None, check=True): """Symmetrize the given orbital space. This function is different to the :func:`symmetrize_orb`: In this function, the given orbitals are mixed to reveal the symmtery; :func:`symmetrize_orb` projects out non-symmetric components for each orbital. Args: mo : 2D float array The orbital space to symmetrize Kwargs: s : 2D float array Overlap matrix. If not given, overlap is computed with the input mol. Returns: 2D orbital coefficients Examples: >>> from pyscf import gto, symm, scf >>> mol = gto.M(atom = 'C 0 0 0; H 1 1 1; H -1 -1 1; H 1 -1 -1; H -1 1 -1', ... basis = 'sto3g') >>> mf = scf.RHF(mol).run() >>> mol.build(0, 0, symmetry='D2') >>> mo = symm.symmetrize_space(mol, mf.mo_coeff) >>> print(symm.label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mo)) ['A', 'A', 'A', 'B1', 'B1', 'B2', 'B2', 'B3', 'B3'] """ from pyscf.tools import mo_mapping if s is None: s = mol.intor_symmetric("cint1e_ovlp_sph") nmo = mo.shape[1] mo_s = numpy.dot(mo.T, s) if check: assert numpy.allclose(numpy.dot(mo_s, mo), numpy.eye(nmo)) mo1 = [] for i, csym in enumerate(mol.symm_orb): moso = numpy.dot(mo_s, csym) ovlpso = reduce(numpy.dot, (csym.T, s, csym)) # excluding orbitals which are already symmetrized try: diag = numpy.einsum("ik,ki->i", moso, lib.cho_solve(ovlpso, moso.T)) except: ovlpso[numpy.diag_indices(csym.shape[1])] += 1e-12 diag = numpy.einsum("ik,ki->i", moso, lib.cho_solve(ovlpso, moso.T)) idx = abs(1 - diag) < 1e-8 orb_exclude = mo[:, idx] mo1.append(orb_exclude) moso1 = moso[~idx] dm = numpy.dot(moso1.T, moso1) if dm.trace() > 1e-8: e, u = scipy.linalg.eigh(dm, ovlpso) mo1.append(numpy.dot(csym, u[:, abs(1 - e) < 1e-6])) mo1 = numpy.hstack(mo1) if mo1.shape[1] != nmo: raise ValueError( "The input orbital space is not symmetrized.\n It is " "probably because the input mol and orbitals are of " "different orientation." ) snorm = numpy.linalg.norm(reduce(numpy.dot, (mo1.T, s, mo1)) - numpy.eye(nmo)) if check and snorm > 1e-6: raise ValueError("Orbitals are not orthogonalized") idx = mo_mapping.mo_1to1map(reduce(numpy.dot, (mo.T, s, mo1))) return mo1[:, idx]
def cas_natorb(mc, mo_coeff=None, ci=None, eris=None, sort=False, verbose=None): '''Transform active orbitals to natrual orbitals, and update the CI wfn Args: mc : a CASSCF/CASCI object or RHF object sort : bool Sort natural orbitals wrt the occupancy. Be careful with this option since the resultant natural orbitals might have the different symmetry to the irreps indicated by CASSCF.orbsym Returns: A tuple, the first item is natural orbitals, the second is updated CI coefficients, the third is the natural occupancy associated to the natural orbitals. ''' from pyscf.mcscf import mc_ao2mo from pyscf.tools import dump_mat if isinstance(verbose, logger.Logger): log = verbose else: log = logger.Logger(mc.stdout, mc.verbose) if mo_coeff is None: mo_coeff = mc.mo_coeff if ci is None: ci = mc.ci if eris is None: eris = mc_ao2mo._ERIS(mc, mo_coeff, approx=2) ncore = mc.ncore ncas = mc.ncas nocc = ncore + ncas nelecas = mc.nelecas casdm1 = mc.fcisolver.make_rdm1(ci, ncas, nelecas) occ, ucas = mc._eig(-casdm1, ncore, nocc) if sort: idx = numpy.argsort(occ) occ = occ[idx] ucas = ucas[:,idx] if hasattr(mc, 'orbsym'): # for casci_symm mc.orbsym[ncore:nocc] = mc.orbsym[ncore:nocc][idx] mc.fcisolver.orbsym = mc.orbsym[ncore:nocc] occ = -occ # where_natorb gives the location of the natural orbital for the input cas # orbitals. gen_strings4orblist map thes sorted strings (on CAS orbital) to # the unsorted determinant strings (on natural orbital). e.g. (3o,2e) system # CAS orbital 1 2 3 # natural orbital 3 1 2 <= by mo_1to1map # CASorb-strings 0b011, 0b101, 0b110 # == (1,2), (1,3), (2,3) # natorb-strings (3,1), (3,2), (1,2) # == 0B101, 0B110, 0B011 <= by gen_strings4orblist # then argsort to translate the string representation to the address # [2(=0B011), 0(=0B101), 1(=0B110)] # to indicate which CASorb-strings address to be loaded in each natorb-strings slot where_natorb = mo_1to1map(ucas) #guide_stringsa = fci.cistring.gen_strings4orblist(where_natorb, nelecas[0]) #guide_stringsb = fci.cistring.gen_strings4orblist(where_natorb, nelecas[1]) #old_det_idxa = numpy.argsort(guide_stringsa) #old_det_idxb = numpy.argsort(guide_stringsb) #ci0 = ci[old_det_idxa[:,None],old_det_idxb] ci0 = fci.addons.reorder(ci, nelecas, where_natorb) # restore phase, to ensure the reordered ci vector is the correct initial guess for i, k in enumerate(where_natorb): if ucas[i,k] < 0: ucas[:,k] *= -1 mo_coeff1 = mo_coeff.copy() mo_coeff1[:,ncore:nocc] = numpy.dot(mo_coeff[:,ncore:nocc], ucas) if log.verbose >= logger.INFO: log.debug('where_natorb %s', str(where_natorb)) log.info('Natural occ %s', str(occ)) log.info('Natural orbital in CAS space') label = ['%d%3s %s%-4s' % x for x in mc.mol.spheric_labels()] dump_mat.dump_rec(log.stdout, mo_coeff1[:,ncore:nocc], label, start=1) s = reduce(numpy.dot, (mo_coeff1[:,ncore:nocc].T, mc._scf.get_ovlp(), mc._scf.mo_coeff)) idx = numpy.argwhere(abs(s)>.4) for i,j in idx: log.info('<CAS-nat-orb|mo-hf> %d %d %12.8f', ncore+i+1, j+1, s[i,j]) h1eff =(reduce(numpy.dot, (mo_coeff[:,ncore:nocc].T, mc.get_hcore(), mo_coeff[:,ncore:nocc])) + eris.vhf_c[ncore:nocc,ncore:nocc]) h1eff = reduce(numpy.dot, (ucas.T, h1eff, ucas)) aaaa = eris.aapp[:,:,ncore:nocc,ncore:nocc].copy() aaaa = ao2mo.incore.full(ao2mo.restore(8, aaaa, ncas), ucas) e_cas, fcivec = mc.fcisolver.kernel(h1eff, aaaa, ncas, nelecas, ci0=ci0) log.debug('In Natural orbital, CI energy = %.12g', e_cas) return mo_coeff1, fcivec, occ
def symmetrize_space(mol, mo, s=None, check=getattr(__config__, 'symm_addons_symmetrize_space_check', True), tol=getattr(__config__, 'symm_addons_symmetrize_space_tol', 1e-7)): '''Symmetrize the given orbital space. This function is different to the :func:`symmetrize_orb`: In this function, the given orbitals are mixed to reveal the symmtery; :func:`symmetrize_orb` projects out non-symmetric components for each orbital. Args: mo : 2D float array The orbital space to symmetrize Kwargs: s : 2D float array Overlap matrix. If not given, overlap is computed with the input mol. Returns: 2D orbital coefficients Examples: >>> from pyscf import gto, symm, scf >>> mol = gto.M(atom = 'C 0 0 0; H 1 1 1; H -1 -1 1; H 1 -1 -1; H -1 1 -1', ... basis = 'sto3g') >>> mf = scf.RHF(mol).run() >>> mol.build(0, 0, symmetry='D2') >>> mo = symm.symmetrize_space(mol, mf.mo_coeff) >>> print(symm.label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mo)) ['A', 'A', 'A', 'B1', 'B1', 'B2', 'B2', 'B3', 'B3'] ''' from pyscf.tools import mo_mapping if s is None: s = mol.intor_symmetric('int1e_ovlp') nmo = mo.shape[1] s_mo = numpy.dot(s, mo) if check and abs(numpy.dot(mo.conj().T, s_mo) - numpy.eye(nmo)).max() > tol: raise ValueError('Orbitals are not orthogonalized') mo1 = [] for i, csym in enumerate(mol.symm_orb): moso = numpy.dot(csym.T, s_mo) ovlpso = reduce(numpy.dot, (csym.T, s, csym)) # excluding orbitals which are already symmetrized try: diag = numpy.einsum('ki,ki->i', moso.conj(), lib.cho_solve(ovlpso, moso)) except: ovlpso[numpy.diag_indices(csym.shape[1])] += 1e-12 diag = numpy.einsum('ki,ki->i', moso.conj(), lib.cho_solve(ovlpso, moso)) idx = abs(1-diag) < 1e-8 orb_exclude = mo[:,idx] mo1.append(orb_exclude) moso1 = moso[:,~idx] dm = numpy.dot(moso1, moso1.T.conj()) if dm.trace() > 1e-8: e, u = scipy.linalg.eigh(dm, ovlpso) mo1.append(numpy.dot(csym, u[:,abs(1-e) < 1e-6])) mo1 = numpy.hstack(mo1) if mo1.shape[1] != nmo: raise ValueError('The input orbital space is not symmetrized.\n One ' 'possible reason is that the input mol and orbitals ' 'are of different orientation.') if (check and abs(reduce(numpy.dot, (mo1.conj().T, s, mo1)) - numpy.eye(nmo)).max() > tol): raise ValueError('Orbitals are not orthogonalized') idx = mo_mapping.mo_1to1map(reduce(numpy.dot, (mo.T, s, mo1))) return mo1[:,idx]